From 5f2d933d44aa02d8968cda62aa51677c46c7bd83 Mon Sep 17 00:00:00 2001 From: Sorin Ionescu Date: Fri, 18 Nov 2011 10:20:34 -0500 Subject: [PATCH 001/513] Added self variable highlighting. --- syntax/python.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/syntax/python.vim b/syntax/python.vim index 0e0bb126..209f6fb2 100644 --- a/syntax/python.vim +++ b/syntax/python.vim @@ -247,6 +247,7 @@ if exists("python_highlight_builtin_objs") && python_highlight_builtin_objs != 0 " Builtin objects and types syn keyword pythonBuiltinObj True False Ellipsis None NotImplemented syn keyword pythonBuiltinObj __debug__ __doc__ __file__ __name__ __package__ + syn keyword pythonBuiltinObj self endif if exists("python_highlight_builtin_funcs") && python_highlight_builtin_funcs != 0 From 1af544c732977cc2575e41e841b68ce97634622b Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 18 Nov 2011 19:53:47 +0400 Subject: [PATCH 002/513] Version 0.4.5 --- Changelog.rst | 5 +++++ plugin/pymode.vim | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index 95642d5b..d2994919 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2011-11-18 0.4.5 +------------------- +* Add 'g:pymode_syntax' option +* Highlight 'self' keyword + ## 2011-11-16 0.4.4 ------------------- * Minor fixes diff --git a/plugin/pymode.vim b/plugin/pymode.vim index e67de660..60ae0682 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.4.4" +let g:pymode_version = "0.4.5" command! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From cc12b0e79277ba8f27cc3061e864e120c328666a Mon Sep 17 00:00:00 2001 From: tramchamploo Date: Mon, 21 Nov 2011 12:36:25 +0800 Subject: [PATCH 003/513] modified: pymode.vim --- ftplugin/python/pymode.vim | 2 -- 1 file changed, 2 deletions(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 75104e28..0df8564c 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -5,8 +5,6 @@ endif " Syntax highlight if !pymode#Default('g:pymode_syntax', 1) || g:pymode_syntax let python_highlight_all=1 - let python_highlight_exceptions=1 - let python_highlight_builtins=1 endif " Python indent options From 3c10423aa09ca13fc6f38dd7a8c31ddfa77a2480 Mon Sep 17 00:00:00 2001 From: tramchamploo Date: Mon, 21 Nov 2011 12:36:25 +0800 Subject: [PATCH 004/513] modified: pymode.vim modified: pymode.vim modified: pymode.vim fix syntax highlight --- ftplugin/python/pymode.vim | 2 -- 1 file changed, 2 deletions(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 75104e28..0df8564c 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -5,8 +5,6 @@ endif " Syntax highlight if !pymode#Default('g:pymode_syntax', 1) || g:pymode_syntax let python_highlight_all=1 - let python_highlight_exceptions=1 - let python_highlight_builtins=1 endif " Python indent options From b629319c3711e1ed2c29e3b3418d2cec132cc3c7 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 23 Nov 2011 21:22:01 +0400 Subject: [PATCH 005/513] Fix escaping --- plugin/pymode.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 60ae0682..06067b29 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -105,7 +105,7 @@ def pylint(): bufnr = vim.current.buffer.number, lnum = test.group(1), type = test.group(2), - text = test.group(3), + text = test.group(3).replace("'", "\""), )) vim.command('let b:qf_list = %s' % repr(qf)) From 8c4e468141b79fe89555ff564592c1a8fc66d023 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 23 Nov 2011 22:56:58 +0400 Subject: [PATCH 006/513] Enable all highlighting. --- README.rst | 46 ++++ doc/pymode.txt | 4 + syntax/python.vim | 621 ++++++++++++++++++++-------------------------- 3 files changed, 320 insertions(+), 351 deletions(-) diff --git a/README.rst b/README.rst index ee0136e3..8fc40a6f 100644 --- a/README.rst +++ b/README.rst @@ -222,9 +222,51 @@ Default values: :: " Set default pymode python other options let g:pymode_options_other = 1 + +Syntax highlight +---------------- + +Default values: :: + " Enable pymode's custom syntax highlighting let g:pymode_syntax = 1 + " Enable all python highlightings + let g:pymode_syntax_all = 1 + + " Highlight "print" as function + leg g:pymode_syntax_print_as_function = 0 + + " Highlight indentation errors + leg g:pymode_syntax_indent_errors = g:pymode_syntax_all + + " Highlight trailing spaces + leg g:pymode_syntax_space_errors = g:pymode_syntax_all + + " Highlight string formatting + leg g:pymode_syntax_string_formatting = g:pymode_syntax_all + + " Highlight str.format syntax + leg g:pymode_syntax_string_format = g:pymode_syntax_all + + " Highlight string.Template syntax + let g:pymode_syntax_string_templates = g:pymode_syntax_all + + " Highlight doc-tests + let g:pymode_syntax_doctests = g:pymode_syntax_all + + " Highlight builtin objects (__doc__, self, etc) + let g:pymode_syntax_builtin_objs = g:pymode_syntax_all + + " Highlight builtin functions + let g:pymode_syntax_builtin_funcs = g:pymode_syntax_all + + " Highlight exceptions + let g:pymode_syntax_highlight_exceptions = g:pymode_syntax_all + + " For fast machines + let g:pymode_syntax_slow_sync = 0 + Default keys ============ @@ -329,6 +371,10 @@ Copyright (C) 2011 Kirill Klenov (klen_) Copyright (c) 2005 Divmod, Inc. http://www.divmod.com/ + **Python syntax for vim** + Copyright (c) 2010 Dmitry Vasiliev + http://www.hlabs.spb.ru/vim/python.vim + License ======= diff --git a/doc/pymode.txt b/doc/pymode.txt index 4a920782..e223e363 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -373,6 +373,10 @@ You may set |exrc| and |secure| in your |vimrc| for auto set custom settings fro Copyright (c) 2005 Divmod, Inc. http://www.divmod.com/ + Python syntax for vim: + Copyright (c) 2010 Dmitry Vasiliev + http://www.hlabs.spb.ru/vim/python.vim + ============================================================================== 7. License ~ diff --git a/syntax/python.vim b/syntax/python.vim index 1dcfe4e6..a470061f 100644 --- a/syntax/python.vim +++ b/syntax/python.vim @@ -1,377 +1,296 @@ -" Vim syntax file -" Language: Python -" Maintainer: Dmitry Vasiliev -" URL: http://www.hlabs.spb.ru/vim/python.vim -" Last Change: 2010-04-09 -" Filenames: *.py -" Version: 2.6.6 -" -" Based on python.vim (from Vim 6.1 distribution) -" by Neil Schemenauer -" -" Thanks: -" -" Jeroen Ruigrok van der Werven -" for the idea to highlight erroneous operators -" Pedro Algarvio -" for the patch to enable spell checking only for the right spots -" (strings and comments) -" John Eikenberry -" for the patch fixing small typo -" Caleb Adamantine -" for the patch fixing highlighting for decorators -" Andrea Riciputi -" for the patch with new configuration options - -" -" Options: -" -" For set option do: let OPTION_NAME = 1 -" For clear option do: let OPTION_NAME = 0 -" -" Option names: -" -" For highlight builtin functions and objects: -" python_highlight_builtins -" -" For highlight builtin objects: -" python_highlight_builtin_objs -" -" For highlight builtin funtions: -" python_highlight_builtin_funcs -" -" For highlight standard exceptions: -" python_highlight_exceptions -" -" For highlight string formatting: -" python_highlight_string_formatting -" -" For highlight str.format syntax: -" python_highlight_string_format -" -" For highlight string.Template syntax: -" python_highlight_string_templates -" -" For highlight indentation errors: -" python_highlight_indent_errors -" -" For highlight trailing spaces: -" python_highlight_space_errors -" -" For highlight doc-tests: -" python_highlight_doctests -" -" If you want all Python highlightings above: -" python_highlight_all -" (This option not override previously set options) -" -" For fast machines: -" python_slow_sync -" -" For "print" builtin as function: -" python_print_as_function +" vim: ft=vim:fdm=marker + +" DESC: Disable script loading +if pymode#Default('b:current_syntax', 'python') || !g:pymode_syntax + finish +endif " For version 5.x: Clear all syntax items -" For version 6.x: Quit when a syntax file was already loaded if version < 600 - syntax clear -elseif exists("b:current_syntax") - finish -elseif exists("g:pymode_syntax") && g:pymode_syntax == 0 - finish + syntax clear endif -if exists("python_highlight_all") && python_highlight_all != 0 - " Not override previously set options - if !exists("python_highlight_builtins") - if !exists("python_highlight_builtin_objs") - let python_highlight_builtin_objs = 1 +" Highlight all +call pymode#Default('g:pymode_syntax_all', 1) + +" Keywords {{{ +" ============ + + syn keyword pythonStatement break continue del + syn keyword pythonStatement exec return + syn keyword pythonStatement pass raise + syn keyword pythonStatement global assert + syn keyword pythonStatement lambda yield + syn keyword pythonStatement with + syn keyword pythonStatement def class nextgroup=pythonFunction skipwhite + syn match pythonFunction "[a-zA-Z_][a-zA-Z0-9_]*" display contained + syn keyword pythonRepeat for while + syn keyword pythonConditional if elif else + syn keyword pythonPreCondit import from as + syn keyword pythonException try except finally + syn keyword pythonOperator and in is not or + + if !pymode#Default("g:pymode_syntax_print_as_function", 0) || g:pymode_syntax_print_as_function + syn keyword pythonStatement print endif - if !exists("python_highlight_builtin_funcs") - let python_highlight_builtin_funcs = 1 + +" }}} + + +" Decorators {{{ +" ============== + + syn match pythonDecorator "@" display nextgroup=pythonDottedName skipwhite + syn match pythonDottedName "[a-zA-Z_][a-zA-Z0-9_]*\(\.[a-zA-Z_][a-zA-Z0-9_]*\)*" display contained + syn match pythonDot "\." display containedin=pythonDottedName + +" }}} + + +" Comments {{{ +" ============ + + syn match pythonComment "#.*$" display contains=pythonTodo,@Spell + syn match pythonRun "\%^#!.*$" + syn match pythonCoding "\%^.*\(\n.*\)\?#.*coding[:=]\s*[0-9A-Za-z-_.]\+.*$" + syn keyword pythonTodo TODO FIXME XXX contained + +" }}} + + +" Errors {{{ +" ========== + + syn match pythonError "\<\d\+\D\+\>" display + syn match pythonError "[$?]" display + syn match pythonError "[&|]\{2,}" display + syn match pythonError "[=]\{3,}" display + + " Indent errors (mix space and tabs) + if !pymode#Default('g:pymode_syntax_indent_errors', g:pymode_syntax_all) || g:pymode_syntax_indent_errors + syn match pythonIndentError "^\s*\( \t\|\t \)\s*\S"me=e-1 display endif - endif - if !exists("python_highlight_exceptions") - let python_highlight_exceptions = 1 - endif - if !exists("python_highlight_string_formatting") - let python_highlight_string_formatting = 1 - endif - if !exists("python_highlight_string_format") - let python_highlight_string_format = 1 - endif - if !exists("python_highlight_string_templates") - let python_highlight_string_templates = 1 - endif - if !exists("python_highlight_indent_errors") - let python_highlight_indent_errors = 1 - endif - if !exists("python_highlight_space_errors") - let python_highlight_space_errors = 1 - endif - if !exists("python_highlight_doctests") - let python_highlight_doctests = 1 - endif -endif -" Keywords -syn keyword pythonStatement break continue del -syn keyword pythonStatement exec return -syn keyword pythonStatement pass raise -syn keyword pythonStatement global assert -syn keyword pythonStatement lambda yield -syn keyword pythonStatement with -syn keyword pythonStatement def class nextgroup=pythonFunction skipwhite -syn match pythonFunction "[a-zA-Z_][a-zA-Z0-9_]*" display contained -syn keyword pythonRepeat for while -syn keyword pythonConditional if elif else -syn keyword pythonPreCondit import from as -syn keyword pythonException try except finally -syn keyword pythonOperator and in is not or - -if !exists("python_print_as_function") || python_print_as_function == 0 - syn keyword pythonStatement print -endif + " Trailing space errors + if !pymode#Default('g:pymode_syntax_space_errors', g:pymode_syntax_all) || g:pymode_syntax_space_errors + syn match pythonSpaceError "\s\+$" display + endif -" Decorators (new in Python 2.4) -syn match pythonDecorator "@" display nextgroup=pythonDottedName skipwhite -syn match pythonDottedName "[a-zA-Z_][a-zA-Z0-9_]*\(\.[a-zA-Z_][a-zA-Z0-9_]*\)*" display contained -syn match pythonDot "\." display containedin=pythonDottedName - -" Comments -syn match pythonComment "#.*$" display contains=pythonTodo,@Spell -syn match pythonRun "\%^#!.*$" -syn match pythonCoding "\%^.*\(\n.*\)\?#.*coding[:=]\s*[0-9A-Za-z-_.]\+.*$" -syn keyword pythonTodo TODO FIXME XXX contained - -" Errors -syn match pythonError "\<\d\+\D\+\>" display -syn match pythonError "[$?]" display -syn match pythonError "[&|]\{2,}" display -syn match pythonError "[=]\{3,}" display - -" TODO: Mixing spaces and tabs also may be used for pretty formatting multiline -" statements. For now I don't know how to work around this. -if exists("python_highlight_indent_errors") && python_highlight_indent_errors != 0 - syn match pythonIndentError "^\s*\( \t\|\t \)\s*\S"me=e-1 display -endif +" }}} + + +" Strings {{{ +" =========== + + syn region pythonString start=+[bB]\='+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonEscape,pythonEscapeError,@Spell + syn region pythonString start=+[bB]\="+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonEscape,pythonEscapeError,@Spell + syn region pythonString start=+[bB]\="""+ end=+"""+ keepend contains=pythonEscape,pythonEscapeError,pythonDocTest2,pythonSpaceError,@Spell + syn region pythonString start=+[bB]\='''+ end=+'''+ keepend contains=pythonEscape,pythonEscapeError,pythonDocTest,pythonSpaceError,@Spell + + syn match pythonEscape +\\[abfnrtv'"\\]+ display contained + syn match pythonEscape "\\\o\o\=\o\=" display contained + syn match pythonEscapeError "\\\o\{,2}[89]" display contained + syn match pythonEscape "\\x\x\{2}" display contained + syn match pythonEscapeError "\\x\x\=\X" display contained + syn match pythonEscape "\\$" + + " Unicode + syn region pythonUniString start=+[uU]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,@Spell + syn region pythonUniString start=+[uU]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,@Spell + syn region pythonUniString start=+[uU]"""+ end=+"""+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,pythonDocTest2,pythonSpaceError,@Spell + syn region pythonUniString start=+[uU]'''+ end=+'''+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,pythonDocTest,pythonSpaceError,@Spell + + syn match pythonUniEscape "\\u\x\{4}" display contained + syn match pythonUniEscapeError "\\u\x\{,3}\X" display contained + syn match pythonUniEscape "\\U\x\{8}" display contained + syn match pythonUniEscapeError "\\U\x\{,7}\X" display contained + syn match pythonUniEscape "\\N{[A-Z ]\+}" display contained + syn match pythonUniEscapeError "\\N{[^A-Z ]\+}" display contained + + " Raw strings + syn region pythonRawString start=+[rR]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonRawEscape,@Spell + syn region pythonRawString start=+[rR]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonRawEscape,@Spell + syn region pythonRawString start=+[rR]"""+ end=+"""+ keepend contains=pythonDocTest2,pythonSpaceError,@Spell + syn region pythonRawString start=+[rR]'''+ end=+'''+ keepend contains=pythonDocTest,pythonSpaceError,@Spell + + syn match pythonRawEscape +\\['"]+ display transparent contained + + " Unicode raw strings + syn region pythonUniRawString start=+[uU][rR]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonRawEscape,pythonUniRawEscape,pythonUniRawEscapeError,@Spell + syn region pythonUniRawString start=+[uU][rR]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonRawEscape,pythonUniRawEscape,pythonUniRawEscapeError,@Spell + syn region pythonUniRawString start=+[uU][rR]"""+ end=+"""+ keepend contains=pythonUniRawEscape,pythonUniRawEscapeError,pythonDocTest2,pythonSpaceError,@Spell + syn region pythonUniRawString start=+[uU][rR]'''+ end=+'''+ keepend contains=pythonUniRawEscape,pythonUniRawEscapeError,pythonDocTest,pythonSpaceError,@Spell + + syn match pythonUniRawEscape "\([^\\]\(\\\\\)*\)\@<=\\u\x\{4}" display contained + syn match pythonUniRawEscapeError "\([^\\]\(\\\\\)*\)\@<=\\u\x\{,3}\X" display contained + + " String formatting + if !pymode#Default('g:pymode_syntax_string_formatting', g:pymode_syntax_all) || g:pymode_syntax_string_formatting + syn match pythonStrFormatting "%\(([^)]\+)\)\=[-#0 +]*\d*\(\.\d\+\)\=[hlL]\=[diouxXeEfFgGcrs%]" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrFormatting "%[-#0 +]*\(\*\|\d\+\)\=\(\.\(\*\|\d\+\)\)\=[hlL]\=[diouxXeEfFgGcrs%]" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + endif -" Trailing space errors -if exists("python_highlight_space_errors") && python_highlight_space_errors != 0 - syn match pythonSpaceError "\s\+$" display -endif + " Str.format syntax + if !pymode#Default('g:pymode_syntax_string_format', g:pymode_syntax_all) || g:pymode_syntax_string_format + syn match pythonStrFormat "{{\|}}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrFormat "{\([a-zA-Z_][a-zA-Z0-9_]*\|\d\+\)\(\.[a-zA-Z_][a-zA-Z0-9_]*\|\[\(\d\+\|[^!:\}]\+\)\]\)*\(![rs]\)\=\(:\({\([a-zA-Z_][a-zA-Z0-9_]*\|\d\+\)}\|\([^}]\=[<>=^]\)\=[ +-]\=#\=0\=\d*\(\.\d\+\)\=[bcdeEfFgGnoxX%]\=\)\=\)\=}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + endif -" Strings -syn region pythonString start=+[bB]\='+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonEscape,pythonEscapeError,@Spell -syn region pythonString start=+[bB]\="+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonEscape,pythonEscapeError,@Spell -syn region pythonString start=+[bB]\="""+ end=+"""+ keepend contains=pythonEscape,pythonEscapeError,pythonDocTest2,pythonSpaceError,@Spell -syn region pythonString start=+[bB]\='''+ end=+'''+ keepend contains=pythonEscape,pythonEscapeError,pythonDocTest,pythonSpaceError,@Spell - -syn match pythonEscape +\\[abfnrtv'"\\]+ display contained -syn match pythonEscape "\\\o\o\=\o\=" display contained -syn match pythonEscapeError "\\\o\{,2}[89]" display contained -syn match pythonEscape "\\x\x\{2}" display contained -syn match pythonEscapeError "\\x\x\=\X" display contained -syn match pythonEscape "\\$" - -" Unicode strings -syn region pythonUniString start=+[uU]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,@Spell -syn region pythonUniString start=+[uU]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,@Spell -syn region pythonUniString start=+[uU]"""+ end=+"""+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,pythonDocTest2,pythonSpaceError,@Spell -syn region pythonUniString start=+[uU]'''+ end=+'''+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,pythonDocTest,pythonSpaceError,@Spell - -syn match pythonUniEscape "\\u\x\{4}" display contained -syn match pythonUniEscapeError "\\u\x\{,3}\X" display contained -syn match pythonUniEscape "\\U\x\{8}" display contained -syn match pythonUniEscapeError "\\U\x\{,7}\X" display contained -syn match pythonUniEscape "\\N{[A-Z ]\+}" display contained -syn match pythonUniEscapeError "\\N{[^A-Z ]\+}" display contained - -" Raw strings -syn region pythonRawString start=+[rR]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonRawEscape,@Spell -syn region pythonRawString start=+[rR]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonRawEscape,@Spell -syn region pythonRawString start=+[rR]"""+ end=+"""+ keepend contains=pythonDocTest2,pythonSpaceError,@Spell -syn region pythonRawString start=+[rR]'''+ end=+'''+ keepend contains=pythonDocTest,pythonSpaceError,@Spell - -syn match pythonRawEscape +\\['"]+ display transparent contained - -" Unicode raw strings -syn region pythonUniRawString start=+[uU][rR]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonRawEscape,pythonUniRawEscape,pythonUniRawEscapeError,@Spell -syn region pythonUniRawString start=+[uU][rR]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonRawEscape,pythonUniRawEscape,pythonUniRawEscapeError,@Spell -syn region pythonUniRawString start=+[uU][rR]"""+ end=+"""+ keepend contains=pythonUniRawEscape,pythonUniRawEscapeError,pythonDocTest2,pythonSpaceError,@Spell -syn region pythonUniRawString start=+[uU][rR]'''+ end=+'''+ keepend contains=pythonUniRawEscape,pythonUniRawEscapeError,pythonDocTest,pythonSpaceError,@Spell - -syn match pythonUniRawEscape "\([^\\]\(\\\\\)*\)\@<=\\u\x\{4}" display contained -syn match pythonUniRawEscapeError "\([^\\]\(\\\\\)*\)\@<=\\u\x\{,3}\X" display contained - -if exists("python_highlight_string_formatting") && python_highlight_string_formatting != 0 - " String formatting - syn match pythonStrFormatting "%\(([^)]\+)\)\=[-#0 +]*\d*\(\.\d\+\)\=[hlL]\=[diouxXeEfFgGcrs%]" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString - syn match pythonStrFormatting "%[-#0 +]*\(\*\|\d\+\)\=\(\.\(\*\|\d\+\)\)\=[hlL]\=[diouxXeEfFgGcrs%]" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString -endif + " String templates + if !pymode#Default('g:pymode_syntax_string_templates', g:pymode_syntax_all) || g:pymode_syntax_string_templates + syn match pythonStrTemplate "\$\$" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrTemplate "\${[a-zA-Z_][a-zA-Z0-9_]*}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrTemplate "\$[a-zA-Z_][a-zA-Z0-9_]*" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + endif -if exists("python_highlight_string_format") && python_highlight_string_format != 0 - " str.format syntax - syn match pythonStrFormat "{{\|}}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString - syn match pythonStrFormat "{\([a-zA-Z_][a-zA-Z0-9_]*\|\d\+\)\(\.[a-zA-Z_][a-zA-Z0-9_]*\|\[\(\d\+\|[^!:\}]\+\)\]\)*\(![rs]\)\=\(:\({\([a-zA-Z_][a-zA-Z0-9_]*\|\d\+\)}\|\([^}]\=[<>=^]\)\=[ +-]\=#\=0\=\d*\(\.\d\+\)\=[bcdeEfFgGnoxX%]\=\)\=\)\=}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString -endif + " DocTests + if !pymode#Default('g:pymode_syntax_doctests', g:pymode_syntax_all) || g:pymode_syntax_doctests + syn region pythonDocTest start="^\s*>>>" end=+'''+he=s-1 end="^\s*$" contained + syn region pythonDocTest2 start="^\s*>>>" end=+"""+he=s-1 end="^\s*$" contained + endif -if exists("python_highlight_string_templates") && python_highlight_string_templates != 0 - " String templates - syn match pythonStrTemplate "\$\$" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString - syn match pythonStrTemplate "\${[a-zA-Z_][a-zA-Z0-9_]*}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString - syn match pythonStrTemplate "\$[a-zA-Z_][a-zA-Z0-9_]*" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString -endif +" }}} -if exists("python_highlight_doctests") && python_highlight_doctests != 0 - " DocTests - syn region pythonDocTest start="^\s*>>>" end=+'''+he=s-1 end="^\s*$" contained - syn region pythonDocTest2 start="^\s*>>>" end=+"""+he=s-1 end="^\s*$" contained -endif +" Numbers {{{ +" =========== -" Numbers (ints, longs, floats, complex) -syn match pythonHexError "\<0[xX]\x*[g-zG-Z]\x*[lL]\=\>" display + syn match pythonHexError "\<0[xX]\x*[g-zG-Z]\x*[lL]\=\>" display + syn match pythonHexNumber "\<0[xX]\x\+[lL]\=\>" display + syn match pythonOctNumber "\<0[oO]\o\+[lL]\=\>" display + syn match pythonBinNumber "\<0[bB][01]\+[lL]\=\>" display + syn match pythonNumber "\<\d\+[lLjJ]\=\>" display + syn match pythonFloat "\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>" display + syn match pythonFloat "\<\d\+[eE][+-]\=\d\+[jJ]\=\>" display + syn match pythonFloat "\<\d\+\.\d*\([eE][+-]\=\d\+\)\=[jJ]\=" display + syn match pythonOctError "\<0[oO]\=\o*[8-9]\d*[lL]\=\>" display + syn match pythonBinError "\<0[bB][01]*[2-9]\d*[lL]\=\>" display -syn match pythonHexNumber "\<0[xX]\x\+[lL]\=\>" display -syn match pythonOctNumber "\<0[oO]\o\+[lL]\=\>" display -syn match pythonBinNumber "\<0[bB][01]\+[lL]\=\>" display +" }}} -syn match pythonNumber "\<\d\+[lLjJ]\=\>" display +" Builtins {{{ +" ============ -syn match pythonFloat "\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>" display -syn match pythonFloat "\<\d\+[eE][+-]\=\d\+[jJ]\=\>" display -syn match pythonFloat "\<\d\+\.\d*\([eE][+-]\=\d\+\)\=[jJ]\=" display + " Builtin objects and types + if !pymode#Default('g:pymode_syntax_builtin_objs', g:pymode_syntax_all) || g:pymode_syntax_builtin_objs + syn keyword pythonBuiltinObj True False Ellipsis None NotImplemented + syn keyword pythonBuiltinObj __debug__ __doc__ __file__ __name__ __package__ + syn keyword pythonBuiltinObj self + endif -syn match pythonOctError "\<0[oO]\=\o*[8-9]\d*[lL]\=\>" display -syn match pythonBinError "\<0[bB][01]*[2-9]\d*[lL]\=\>" display + " Builtin functions + if !pymode#Default('g:pymode_syntax_builtin_funcs', g:pymode_syntax_all) || g:pymode_syntax_builtin_funcs + syn keyword pythonBuiltinFunc __import__ abs all any apply + syn keyword pythonBuiltinFunc basestring bin bool buffer bytearray bytes callable + syn keyword pythonBuiltinFunc chr classmethod cmp coerce compile complex + syn keyword pythonBuiltinFunc delattr dict dir divmod enumerate eval + syn keyword pythonBuiltinFunc execfile file filter float format frozenset getattr + syn keyword pythonBuiltinFunc globals hasattr hash help hex id + syn keyword pythonBuiltinFunc input int intern isinstance + syn keyword pythonBuiltinFunc issubclass iter len list locals long map max + syn keyword pythonBuiltinFunc min next object oct open ord + syn keyword pythonBuiltinFunc pow property range + syn keyword pythonBuiltinFunc raw_input reduce reload repr + syn keyword pythonBuiltinFunc reversed round set setattr + syn keyword pythonBuiltinFunc slice sorted staticmethod str sum super tuple + syn keyword pythonBuiltinFunc type unichr unicode vars xrange zip + + if pymode#Default('g:pymode_syntax_print_as_function', 0) && g:pymode_syntax_print_as_function + syn keyword pythonBuiltinFunc print + endif -if exists("python_highlight_builtin_objs") && python_highlight_builtin_objs != 0 - " Builtin objects and types - syn keyword pythonBuiltinObj True False Ellipsis None NotImplemented - syn keyword pythonBuiltinObj __debug__ __doc__ __file__ __name__ __package__ - syn keyword pythonBuiltinObj self -endif + endif -if exists("python_highlight_builtin_funcs") && python_highlight_builtin_funcs != 0 - " Builtin functions - syn keyword pythonBuiltinFunc __import__ abs all any apply - syn keyword pythonBuiltinFunc basestring bin bool buffer bytearray bytes callable - syn keyword pythonBuiltinFunc chr classmethod cmp coerce compile complex - syn keyword pythonBuiltinFunc delattr dict dir divmod enumerate eval - syn keyword pythonBuiltinFunc execfile file filter float format frozenset getattr - syn keyword pythonBuiltinFunc globals hasattr hash help hex id - syn keyword pythonBuiltinFunc input int intern isinstance - syn keyword pythonBuiltinFunc issubclass iter len list locals long map max - syn keyword pythonBuiltinFunc min next object oct open ord - syn keyword pythonBuiltinFunc pow property range - syn keyword pythonBuiltinFunc raw_input reduce reload repr - syn keyword pythonBuiltinFunc reversed round set setattr - syn keyword pythonBuiltinFunc slice sorted staticmethod str sum super tuple - syn keyword pythonBuiltinFunc type unichr unicode vars xrange zip - - if exists("python_print_as_function") && python_print_as_function != 0 - syn keyword pythonBuiltinFunc print - endif -endif + " Builtin exceptions and warnings + if !pymode#Default('g:pymode_syntax_highlight_exceptions', g:pymode_syntax_all) || g:pymode_syntax_highlight_exceptions + syn keyword pythonExClass BaseException + syn keyword pythonExClass Exception StandardError ArithmeticError + syn keyword pythonExClass LookupError EnvironmentError + syn keyword pythonExClass AssertionError AttributeError BufferError EOFError + syn keyword pythonExClass FloatingPointError GeneratorExit IOError + syn keyword pythonExClass ImportError IndexError KeyError + syn keyword pythonExClass KeyboardInterrupt MemoryError NameError + syn keyword pythonExClass NotImplementedError OSError OverflowError + syn keyword pythonExClass ReferenceError RuntimeError StopIteration + syn keyword pythonExClass SyntaxError IndentationError TabError + syn keyword pythonExClass SystemError SystemExit TypeError + syn keyword pythonExClass UnboundLocalError UnicodeError + syn keyword pythonExClass UnicodeEncodeError UnicodeDecodeError + syn keyword pythonExClass UnicodeTranslateError ValueError VMSError + syn keyword pythonExClass WindowsError ZeroDivisionError + syn keyword pythonExClass Warning UserWarning BytesWarning DeprecationWarning + syn keyword pythonExClass PendingDepricationWarning SyntaxWarning + syn keyword pythonExClass RuntimeWarning FutureWarning + syn keyword pythonExClass ImportWarning UnicodeWarning + endif -if exists("python_highlight_exceptions") && python_highlight_exceptions != 0 - " Builtin exceptions and warnings - syn keyword pythonExClass BaseException - syn keyword pythonExClass Exception StandardError ArithmeticError - syn keyword pythonExClass LookupError EnvironmentError - - syn keyword pythonExClass AssertionError AttributeError BufferError EOFError - syn keyword pythonExClass FloatingPointError GeneratorExit IOError - syn keyword pythonExClass ImportError IndexError KeyError - syn keyword pythonExClass KeyboardInterrupt MemoryError NameError - syn keyword pythonExClass NotImplementedError OSError OverflowError - syn keyword pythonExClass ReferenceError RuntimeError StopIteration - syn keyword pythonExClass SyntaxError IndentationError TabError - syn keyword pythonExClass SystemError SystemExit TypeError - syn keyword pythonExClass UnboundLocalError UnicodeError - syn keyword pythonExClass UnicodeEncodeError UnicodeDecodeError - syn keyword pythonExClass UnicodeTranslateError ValueError VMSError - syn keyword pythonExClass WindowsError ZeroDivisionError - - syn keyword pythonExClass Warning UserWarning BytesWarning DeprecationWarning - syn keyword pythonExClass PendingDepricationWarning SyntaxWarning - syn keyword pythonExClass RuntimeWarning FutureWarning - syn keyword pythonExClass ImportWarning UnicodeWarning -endif +" }}} -if exists("python_slow_sync") && python_slow_sync != 0 - syn sync minlines=2000 -else - " This is fast but code inside triple quoted strings screws it up. It - " is impossible to fix because the only way to know if you are inside a - " triple quoted string is to start from the beginning of the file. - syn sync match pythonSync grouphere NONE "):$" - syn sync maxlines=200 -endif -if version >= 508 || !exists("did_python_syn_inits") - if version <= 508 - let did_python_syn_inits = 1 - command -nargs=+ HiLink hi link - else - command -nargs=+ HiLink hi def link - endif - - HiLink pythonStatement Statement - HiLink pythonPreCondit Statement - HiLink pythonFunction Function - HiLink pythonConditional Conditional - HiLink pythonRepeat Repeat - HiLink pythonException Exception - HiLink pythonOperator Operator - - HiLink pythonDecorator Define - HiLink pythonDottedName Function - HiLink pythonDot Normal - - HiLink pythonComment Comment - HiLink pythonCoding Special - HiLink pythonRun Special - HiLink pythonTodo Todo - - HiLink pythonError Error - HiLink pythonIndentError Error - HiLink pythonSpaceError Error - - HiLink pythonString String - HiLink pythonUniString String - HiLink pythonRawString String - HiLink pythonUniRawString String - - HiLink pythonEscape Special - HiLink pythonEscapeError Error - HiLink pythonUniEscape Special - HiLink pythonUniEscapeError Error - HiLink pythonUniRawEscape Special - HiLink pythonUniRawEscapeError Error - - HiLink pythonStrFormatting Special - HiLink pythonStrFormat Special - HiLink pythonStrTemplate Special - - HiLink pythonDocTest Special - HiLink pythonDocTest2 Special - - HiLink pythonNumber Number - HiLink pythonHexNumber Number - HiLink pythonOctNumber Number - HiLink pythonBinNumber Number - HiLink pythonFloat Float - HiLink pythonOctError Error - HiLink pythonHexError Error - HiLink pythonBinError Error - - HiLink pythonBuiltinObj Structure - HiLink pythonBuiltinFunc Function - - HiLink pythonExClass Structure - - delcommand HiLink +if !pymode#Default('g:pymode_syntax_slow_sync', 0) || g:pymode_syntax_slow_sync + syn sync minlines=2000 +else + " This is fast but code inside triple quoted strings screws it up. It + " is impossible to fix because the only way to know if you are inside a + " triple quoted string is to start from the beginning of the file. + syn sync match pythonSync grouphere NONE "):$" + syn sync maxlines=200 endif -let b:current_syntax = "python" +" Highlight {{{ +" ============= + + hi def link pythonStatement Statement + hi def link pythonPreCondit Statement + hi def link pythonFunction Function + hi def link pythonConditional Conditional + hi def link pythonRepeat Repeat + hi def link pythonException Exception + hi def link pythonOperator Operator + + hi def link pythonDecorator Define + hi def link pythonDottedName Function + hi def link pythonDot Normal + + hi def link pythonComment Comment + hi def link pythonCoding Special + hi def link pythonRun Special + hi def link pythonTodo Todo + + hi def link pythonError Error + hi def link pythonIndentError Error + hi def link pythonSpaceError Error + + hi def link pythonString String + hi def link pythonUniString String + hi def link pythonRawString String + hi def link pythonUniRawString String + + hi def link pythonEscape Special + hi def link pythonEscapeError Error + hi def link pythonUniEscape Special + hi def link pythonUniEscapeError Error + hi def link pythonUniRawEscape Special + hi def link pythonUniRawEscapeError Error + + hi def link pythonStrFormatting Special + hi def link pythonStrFormat Special + hi def link pythonStrTemplate Special + + hi def link pythonDocTest Special + hi def link pythonDocTest2 Special + + hi def link pythonNumber Number + hi def link pythonHexNumber Number + hi def link pythonOctNumber Number + hi def link pythonBinNumber Number + hi def link pythonFloat Float + hi def link pythonOctError Error + hi def link pythonHexError Error + hi def link pythonBinError Error + + hi def link pythonBuiltinObj Structure + hi def link pythonBuiltinFunc Function + + hi def link pythonExClass Structure + +" }}} From 2664fb5e3e29cdeebc5816f9fdec5357b4346aca Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 23 Nov 2011 22:57:14 +0400 Subject: [PATCH 007/513] Rename default pylint settings. --- pylintrc => pylint.ini | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pylintrc => pylint.ini (100%) diff --git a/pylintrc b/pylint.ini similarity index 100% rename from pylintrc rename to pylint.ini From daad0caedec087a8252c2a5c599c5912b6d52fc9 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 23 Nov 2011 22:57:28 +0400 Subject: [PATCH 008/513] Version 0.4.6 --- Changelog.rst | 10 ++++++++++ plugin/pymode.vim | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index d2994919..a6595011 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,16 @@ Changelog ========= +## 2011-11-23 0.4.6 +------------------- +* Enable all syntax highlighting + For old settings set in your vimrc: + let g:pymode_syntax_builtin_objs = 0 + let g:pymode_syntax_builtin_funcs = 0 + +* Change namespace of syntax variables + See README + ## 2011-11-18 0.4.5 ------------------- * Add 'g:pymode_syntax' option diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 06067b29..ef2b67bf 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.4.5" +let g:pymode_version = "0.4.6" command! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version @@ -66,7 +66,7 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " DESC: Set default pylint configuration if !filereadable(g:pymode_lint_config) - let g:pymode_lint_config = expand(":p:h:h") . "/pylintrc" + let g:pymode_lint_config = expand(":p:h:h") . "/pylint.ini" endif python << EOF From 843223a2c76d734c95752d991ac75f866966413e Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 23 Nov 2011 22:59:48 +0400 Subject: [PATCH 009/513] Add last changelog to readme --- README.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.rst b/README.rst index 8fc40a6f..9f18e3e2 100644 --- a/README.rst +++ b/README.rst @@ -24,6 +24,21 @@ See screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my first s .. contents:: +Changelog +========= + +## 2011-11-23 0.4.6 +------------------- +* Enable all syntax highlighting + For old settings set in your vimrc: :: + + let g:pymode_syntax_builtin_objs = 0 + let g:pymode_syntax_builtin_funcs = 0 + +* Change namespace of syntax variables + See README + + Requirements ============ From 742d8f5354bfc53bf26281d122fbddf0075b798f Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sun, 27 Nov 2011 22:37:21 +0400 Subject: [PATCH 010/513] Add PyLintToggleWindow --- Changelog.rst | 5 +++++ autoload/pymode/lint.vim | 19 ++++++++++++++++--- ftplugin/python/pymode.vim | 1 + plugin/pymode.vim | 4 ++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index a6595011..7cfa0e2e 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2011-11-27 0.4.7 +------------------- +* Add `PyLintWindowToggle` command +* Fix some bugs + ## 2011-11-23 0.4.6 ------------------- * Enable all syntax highlighting diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index a1ca50d5..b4fa0050 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -21,13 +21,26 @@ endfunction fun! pymode#lint#Toggle() "{{{ let g:pymode_lint = g:pymode_lint ? 0 : 1 if g:pymode_lint - echomsg "PyLint enabled." + echomsg "Pymode lint enabled." + botright cwindow else - echomsg "PyLint disabled." + echomsg "Pymode lint disabled." + cclose + endif +endfunction "}}} + +fun! pymode#lint#ToggleWindow() "{{{ + let g:pymode_lint_cwindow = g:pymode_lint_cwindow ? 0 : 1 + if g:pymode_lint_cwindow + echomsg "Pymode lint cwindow enabled." + botright cwindow + else + echomsg "Pymode lint cwindow disabled." + cclose endif endfunction "}}} fun! pymode#lint#ToggleChecker() "{{{ let g:pymode_lint_checker = g:pymode_lint_checker == "pylint" ? "pyflakes" : "pylint" - echomsg "PyLint checker: " . g:pymode_lint_checker + echomsg "Pymode lint checker: " . g:pymode_lint_checker endfunction "}}} diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 0df8564c..8cd4887c 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -65,6 +65,7 @@ if g:pymode_lint " DESC: Set commands command! -buffer -nargs=0 PyLintToggle :call pymode#lint#Toggle() + command! -buffer -nargs=0 PyLintWindowToggle :call pymode#lint#ToggleWindow() command! -buffer -nargs=0 PyLintCheckerToggle :call pymode#lint#ToggleChecker() command! -buffer -nargs=0 PyLint :call pymode#lint#Check() diff --git a/plugin/pymode.vim b/plugin/pymode.vim index ef2b67bf..4bf310fb 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,6 +1,6 @@ -let g:pymode_version = "0.4.6" +let g:pymode_version = "0.4.7" -command! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version +com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version " OPTION: g:pymode -- bool. Run pymode. if pymode#Default('g:pymode', 1) || !g:pymode From 93f5da1446f65aede01349624558e55399f941df Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 28 Nov 2011 11:16:46 +0400 Subject: [PATCH 011/513] Fix PyLintWindowToggle --- Changelog.rst | 2 +- autoload/pymode/lint.vim | 29 +++++++++++++++-------------- plugin/pymode.vim | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 7cfa0e2e..9fee8846 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -## 2011-11-27 0.4.7 +## 2011-11-27 0.4.8 ------------------- * Add `PyLintWindowToggle` command * Fix some bugs diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index b4fa0050..99758a22 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -20,27 +20,28 @@ endfunction fun! pymode#lint#Toggle() "{{{ let g:pymode_lint = g:pymode_lint ? 0 : 1 - if g:pymode_lint - echomsg "Pymode lint enabled." - botright cwindow - else - echomsg "Pymode lint disabled." - cclose - endif + call pymode#lint#toggle_win(g:pymode_lint, "Pymode lint") endfunction "}}} fun! pymode#lint#ToggleWindow() "{{{ let g:pymode_lint_cwindow = g:pymode_lint_cwindow ? 0 : 1 - if g:pymode_lint_cwindow - echomsg "Pymode lint cwindow enabled." - botright cwindow - else - echomsg "Pymode lint cwindow disabled." - cclose - endif + call pymode#lint#toggle_win(g:pymode_lint_cwindow, "Pymode lint cwindow") endfunction "}}} fun! pymode#lint#ToggleChecker() "{{{ let g:pymode_lint_checker = g:pymode_lint_checker == "pylint" ? "pyflakes" : "pylint" echomsg "Pymode lint checker: " . g:pymode_lint_checker endfunction "}}} + +fun! pymode#lint#toggle_win(toggle, msg) "{{{ + if a:toggle + echomsg a:msg." enabled" + botright cwindow + if &buftype == "quickfix" + wincmd p + endif + else + echomsg a:msg." disabled" + cclose + endif +endfunction "}}} diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 4bf310fb..5df5d759 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.4.7" +let g:pymode_version = "0.4.8" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 3562021910ed98c4fad5fcd37b5e3d38b37bf73f Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 30 Nov 2011 19:09:11 +0400 Subject: [PATCH 012/513] Add python objects and motions --- Changelog.rst | 5 ++++ README.rst | 40 +++++++++++++++++++--------- after/ftplugin/python.vim | 30 +++++++++++++++++++++ autoload/pymode/motion.vim | 54 ++++++++++++++++++++++++++++++++++++++ doc/pymode.txt | 21 +++++++++++++++ ftplugin/python/pymode.vim | 22 +++++++++++++--- plugin/pymode.vim | 2 +- 7 files changed, 157 insertions(+), 17 deletions(-) create mode 100644 autoload/pymode/motion.vim diff --git a/Changelog.rst b/Changelog.rst index 9fee8846..bb637b55 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2011-11-30 0.5.0 +------------------- +* Add python objects and motions (beta) + :h pymode_motion + ## 2011-11-27 0.4.8 ------------------- * Add `PyLintWindowToggle` command diff --git a/README.rst b/README.rst index 9f18e3e2..af12c6b6 100644 --- a/README.rst +++ b/README.rst @@ -9,6 +9,7 @@ There is no need to install the pylint_, rope_ or any used python library on you - Highlight syntax errors - Highlight and auto fix unused imports +- Python objects and motion (]], ]m, vac, vim, dim, ...) - Strong code completion - Code refactoring - Python documentation @@ -27,16 +28,10 @@ See screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my first s Changelog ========= -## 2011-11-23 0.4.6 +## 2011-11-30 0.5.0 ------------------- -* Enable all syntax highlighting - For old settings set in your vimrc: :: - - let g:pymode_syntax_builtin_objs = 0 - let g:pymode_syntax_builtin_funcs = 0 - -* Change namespace of syntax variables - See README +* Add python objects and motions (beta) + :h pymode_motion Requirements @@ -216,6 +211,9 @@ Other stuff Default values: :: + " Load motion plugin + let g:pymode_motion = 1 + " Load breakpoints plugin let g:pymode_breakpoint = 1 @@ -291,13 +289,29 @@ Default keys ============== ============= Keys Command ============== ============= -**K** Show python docs +**K** Show python docs (g:pymode_doc enabled) +-------------- ------------- +**** Rope autocomplete (g:pymode_rope enabled) +-------------- ------------- +**r** Run python (g:pymode_run enabled) +-------------- ------------- +**b** Set, unset breakpoint (g:pymode_breakpoint enabled) +-------------- ------------- +[[ Jump on previous class or function (normal, visual, operator modes) +-------------- ------------- +]] Jump on next class or function (normal, visual, operator modes) +-------------- ------------- +[m Jump on previous class or method (normal, visual, operator modes) +-------------- ------------- +]m Jump on next class or method (normal, visual, operator modes) +-------------- ------------- +ac Select a class. Ex: vac, dac, yac, cac (normal, operator modes) -------------- ------------- -**** Rope autocomplete +ic Select inner class. Ex: vic, dic, yic, cic (normal, operator modes) -------------- ------------- -**r** Run python +am Select a function or method. Ex: vam, dam, yam, cam (normal, operator modes) -------------- ------------- -**b** Set, unset breakpoint +im Select inner function or method. Ex: vim, dim, yim, cim (normal, operator modes) ============== ============= .. note:: See also ``:help ropevim.txt`` diff --git a/after/ftplugin/python.vim b/after/ftplugin/python.vim index e8cea841..7390da87 100644 --- a/after/ftplugin/python.vim +++ b/after/ftplugin/python.vim @@ -2,3 +2,33 @@ if g:pymode && g:pymode_rope && g:pymode_rope_vim_completion setlocal omnifunc=RopeOmni endif + +" Motion {{{ + + if !pymode#Default('g:pymode_motion', 1) || g:pymode_motion + + nnoremap ]] :call pymode#motion#move('^\(class\\|def\)\s', '') + nnoremap [[ :call pymode#motion#move('^\(class\\|def\)\s', 'b') + nnoremap ]m :call pymode#motion#move('^\s*\(class\\|def\)\s', '') + nnoremap [m :call pymode#motion#move('^\s*\(class\\|def\)\s', 'b') + onoremap ]] :call pymode#motion#move('^\(class\\|def\)\s', '') + onoremap [[ :call pymode#motion#move('^\(class\\|def\)\s', 'b') + onoremap ]m :call pymode#motion#move('^\s*\(class\\|def\)\s', '') + onoremap [m :call pymode#motion#move('^\s*\(class\\|def\)\s', 'b') + vnoremap ]] :call pymode#motion#vmove('^\(class\\|def\)\s', '') + vnoremap [[ :call pymode#motion#vmove('^\(class\\|def\)\s', 'b') + vnoremap ]m :call pymode#motion#vmove('^\s*\(class\\|def\)\s', '') + vnoremap [m :call pymode#motion#vmove('^\s*\(class\\|def\)\s', 'b') + + nnoremap vac :call pymode#motion#select('^\s*\(class\)\s', 0) + nnoremap vic :call pymode#motion#select('^\s*\(class\)\s', 1) + nnoremap vam :call pymode#motion#select('^\s*\(def\)\s', 0) + nnoremap vim :call pymode#motion#select('^\s*\(def\)\s', 1) + onoremap am :call pymode#motion#select('^\s*\(def\)\s', 0) + onoremap im :call pymode#motion#select('^\s*\(def\)\s', 1) + onoremap ac :call pymode#motion#select('^\s*\(class\)\s', 0) + onoremap ic :call pymode#motion#select('^\s*\(class\)\s', 1) + + endif + +" }}} diff --git a/autoload/pymode/motion.vim b/autoload/pymode/motion.vim new file mode 100644 index 00000000..c922f5c6 --- /dev/null +++ b/autoload/pymode/motion.vim @@ -0,0 +1,54 @@ +fun! pymode#motion#block(lnum) "{{{ + let start = indent(a:lnum) + let num = a:lnum + while num + let num = nextnonblank(num + 1) + if num && indent(num) <= start + return num - 1 + endif + endwhile + return line('$') +endfunction "}}} + + +fun! pymode#motion#move(pattern, flags) "{{{ + let i = v:count1 + while i > 0 + let result = searchpos(a:pattern, a:flags.'W') + let i = i - 1 + endwhile + return result +endfunction "}}} + + +fun! pymode#motion#vmove(pattern, flags) "{{{ + let end = pymode#motion#move(a:pattern, a:flags) + normal! gv + call cursor(end) +endfunction "}}} + + +fun! pymode#motion#pos_le(pos1, pos2) "{{{ + return ((a:pos1[0] < a:pos2[0]) || (a:pos1[0] == a:pos2[0] && a:pos1[1] <= a:pos2[1])) +endfunction "}}} + + +fun! pymode#motion#select(pattern, inner) "{{{ + let orig = getpos('.')[1:2] + let start = pymode#motion#move(a:pattern, 'bW') + let eline = pymode#motion#block(start[0]) + let end = [eline, len(getline(eline))] + call cursor(orig) + + if pymode#motion#pos_le(start, orig) && pymode#motion#pos_le(orig, end) + if a:inner + let start = [start[0] + 1, start[1]] + let eline = prevnonblank(end[0]) + let end = [eline, len(getline(eline))] + endif + normal! v + call cursor(start) + normal! o + call cursor(end) + endif +endfunction "}}} diff --git a/doc/pymode.txt b/doc/pymode.txt index e223e363..a88dfe50 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -86,6 +86,7 @@ PythonMode. These options should be set in your vimrc. |'pymode_options_other'| Set default pymode options for python codding +|'pymode_motion'| Enable pymode motion stuff Note: Also see |ropevim.txt| @@ -285,6 +286,26 @@ If this option is set to 1, pymode enable next options for python buffers: > setlocal nowrap setlocal textwidth=80 < +------------------------------------------------------------------------------ + *'pymode_motion'* +Values: 0 or 1. +Default: 1. + +If this option is set to 1, pymode enable some python motions. Pymode-motion +is beta. + +================ ============================ +Key Command +================ ============================ +[[ Jump on previous class or function (normal, visual, operator modes) +]] Jump on next class or function (normal, visual, operator modes) +[m Jump on previous class or method (normal, visual, operator modes) +]m Jump on next class or method (normal, visual, operator modes) +ac Select a class. Ex: vac, dac, yac, cac (normal, operator modes) +ic Select inner class. Ex: vic, dic, yic, cic (normal, operator modes) +am Select a function or method. Ex: vam, dam, yam, cam (normal, operator modes) +im Select inner function or method. Ex: vim, dim, yim, cim (normal, operator modes) +================ ============================ ============================================================================== diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 8cd4887c..9ce4cf7e 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -7,6 +7,9 @@ if !pymode#Default('g:pymode_syntax', 1) || g:pymode_syntax let python_highlight_all=1 endif + +" Options {{{ + " Python indent options if !pymode#Default('g:pymode_options_indent', 1) || g:pymode_options_indent setlocal cinwords=if,elif,else,for,while,try,except,finally,def,class @@ -27,7 +30,7 @@ if !pymode#Default('g:pymode_options_fold', 1) || g:pymode_options_fold setlocal foldlevel=99 setlocal foldmethod=indent endif - + " Python other options if !pymode#Default('g:pymode_options_other', 1) || g:pymode_options_other setlocal complete+=t @@ -37,6 +40,11 @@ if !pymode#Default('g:pymode_options_other', 1) || g:pymode_options_other setlocal textwidth=80 endif +" }}} + + +" Paths {{{ + " Fix path for project if g:pymode py curpath = vim.eval('getcwd()') @@ -48,7 +56,11 @@ if g:pymode_virtualenv && exists("$VIRTUAL_ENV") call pymode#virtualenv#Activate() endif -" Python documentation +" }}} + + +" Documentation {{{ + if g:pymode_doc " DESC: Set commands @@ -59,6 +71,8 @@ if g:pymode_doc endif +" }}} + " PyLint if g:pymode_lint @@ -86,7 +100,7 @@ if g:pymode_rope noremap m :emenu Rope. inoremap =RopeLuckyAssistInsertMode() - let s:prascm = g:pymode_rope_always_show_complete_menu ? "" : "" + let s:prascm = g:pymode_rope_always_show_complete_menu ? "" : "" exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm @@ -118,3 +132,5 @@ call pymode#Default("g:pymode_utils_whitespaces", 1) if g:pymode_utils_whitespaces au BufWritePre :call setline(1,map(getline(1,"$"),'substitute(v:val,"\\s\\+$","","")')) endif + +" vim: fdm=marker:fdl=0 diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 5df5d759..1aac51c9 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.4.8" +let g:pymode_version = "0.5.0" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 830f91caabe2658bbcf43f3188c16ded8525d12f Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 19 Dec 2011 17:07:17 +0400 Subject: [PATCH 013/513] Fix README --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index af12c6b6..4697724f 100644 --- a/README.rst +++ b/README.rst @@ -248,19 +248,19 @@ Default values: :: let g:pymode_syntax_all = 1 " Highlight "print" as function - leg g:pymode_syntax_print_as_function = 0 + let g:pymode_syntax_print_as_function = 0 " Highlight indentation errors - leg g:pymode_syntax_indent_errors = g:pymode_syntax_all + let g:pymode_syntax_indent_errors = g:pymode_syntax_all " Highlight trailing spaces - leg g:pymode_syntax_space_errors = g:pymode_syntax_all + let g:pymode_syntax_space_errors = g:pymode_syntax_all " Highlight string formatting - leg g:pymode_syntax_string_formatting = g:pymode_syntax_all + let g:pymode_syntax_string_formatting = g:pymode_syntax_all " Highlight str.format syntax - leg g:pymode_syntax_string_format = g:pymode_syntax_all + let g:pymode_syntax_string_format = g:pymode_syntax_all " Highlight string.Template syntax let g:pymode_syntax_string_templates = g:pymode_syntax_all From 5bf884d8974dda8525e75ccbefab640b01dc6b3c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 19 Dec 2011 17:07:58 +0400 Subject: [PATCH 014/513] Fix README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 4697724f..a772f795 100644 --- a/README.rst +++ b/README.rst @@ -189,7 +189,7 @@ Default values: :: let g:pymode_rope_extended_complete = 1 - let g:pymode_rope_autoimport_modules = ["os","shutil","datetime"]) + let g:pymode_rope_autoimport_modules = ["os","shutil","datetime"] let g:pymode_rope_confirm_saving = 1 From c7f7aec480dac2866d14980c5aec2e1a093f52d7 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 19 Dec 2011 17:22:32 +0400 Subject: [PATCH 015/513] Escape whitespaces in filenames --- pylibs/ropevim.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 3890b500..19c950e0 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -203,6 +203,7 @@ def find_file(self, filename, readonly=False, other=False, force=False): if filename != self.filename() or force: if other: vim.command('new') + filename = '\\ '.join(s.rstrip() for s in filename.split()) vim.command('e %s' % filename) if readonly: vim.command('set nomodifiable') From 3073ac2425bb5dd7712a09aba6e1fdf744c418bc Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 6 Jan 2012 19:14:53 +0400 Subject: [PATCH 016/513] Motion and postware info. --- README.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index a772f795..8edafec2 100644 --- a/README.rst +++ b/README.rst @@ -7,9 +7,9 @@ features like python code looking for bugs, refactoring and some other useful th This plugin allow you create python code in vim very easily. There is no need to install the pylint_, rope_ or any used python library on your system. +- Python objects and motion (]], 3[[, ]]m, vac, vim, dac, cim, ...) - Highlight syntax errors - Highlight and auto fix unused imports -- Python objects and motion (]], ]m, vac, vim, dim, ...) - Strong code completion - Code refactoring - Python documentation @@ -19,7 +19,7 @@ There is no need to install the pylint_, rope_ or any used python library on you - Virtualenv support - And more... -See screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my first screencast) +See (old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my first screencast) .. contents:: @@ -305,11 +305,11 @@ Keys Command -------------- ------------- ]m Jump on next class or method (normal, visual, operator modes) -------------- ------------- -ac Select a class. Ex: vac, dac, yac, cac (normal, operator modes) +ac c Select a class. Ex: vac, dac, dc, yac, yc, cac, cc (normal, operator modes) -------------- ------------- ic Select inner class. Ex: vic, dic, yic, cic (normal, operator modes) -------------- ------------- -am Select a function or method. Ex: vam, dam, yam, cam (normal, operator modes) +am m Select a function or method. Ex: vam, dam, dm, yam, ym, cam, cm (normal, operator modes) -------------- ------------- im Select inner function or method. Ex: vim, dim, yim, cim (normal, operator modes) ============== ============= @@ -410,6 +410,10 @@ License Licensed under a `GNU lesser general public license`_. +If you like this plugin, you can send me postcard :) +My address is here: Russia, 143400, Krasnogorsk, Shkolnaya 1-19 +Thanks for support! + .. _GNU lesser general public license: http://www.gnu.org/copyleft/lesser.html .. _klen: http://klen.github.com/ From 51d72ee8bc500ca74d9e8e6d82978eca47990631 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 6 Jan 2012 19:15:28 +0400 Subject: [PATCH 017/513] Fix motion. --- after/ftplugin/python.vim | 47 +++++++++++++++++++++----------------- autoload/pymode/motion.vim | 28 +++++++++++++++-------- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/after/ftplugin/python.vim b/after/ftplugin/python.vim index 7390da87..c5ac59f4 100644 --- a/after/ftplugin/python.vim +++ b/after/ftplugin/python.vim @@ -7,27 +7,32 @@ endif if !pymode#Default('g:pymode_motion', 1) || g:pymode_motion - nnoremap ]] :call pymode#motion#move('^\(class\\|def\)\s', '') - nnoremap [[ :call pymode#motion#move('^\(class\\|def\)\s', 'b') - nnoremap ]m :call pymode#motion#move('^\s*\(class\\|def\)\s', '') - nnoremap [m :call pymode#motion#move('^\s*\(class\\|def\)\s', 'b') - onoremap ]] :call pymode#motion#move('^\(class\\|def\)\s', '') - onoremap [[ :call pymode#motion#move('^\(class\\|def\)\s', 'b') - onoremap ]m :call pymode#motion#move('^\s*\(class\\|def\)\s', '') - onoremap [m :call pymode#motion#move('^\s*\(class\\|def\)\s', 'b') - vnoremap ]] :call pymode#motion#vmove('^\(class\\|def\)\s', '') - vnoremap [[ :call pymode#motion#vmove('^\(class\\|def\)\s', 'b') - vnoremap ]m :call pymode#motion#vmove('^\s*\(class\\|def\)\s', '') - vnoremap [m :call pymode#motion#vmove('^\s*\(class\\|def\)\s', 'b') - - nnoremap vac :call pymode#motion#select('^\s*\(class\)\s', 0) - nnoremap vic :call pymode#motion#select('^\s*\(class\)\s', 1) - nnoremap vam :call pymode#motion#select('^\s*\(def\)\s', 0) - nnoremap vim :call pymode#motion#select('^\s*\(def\)\s', 1) - onoremap am :call pymode#motion#select('^\s*\(def\)\s', 0) - onoremap im :call pymode#motion#select('^\s*\(def\)\s', 1) - onoremap ac :call pymode#motion#select('^\s*\(class\)\s', 0) - onoremap ic :call pymode#motion#select('^\s*\(class\)\s', 1) + nnoremap ]] :call pymode#motion#move2('^\(class\\|def\)\s', '') + nnoremap [[ :call pymode#motion#move2('^\(class\\|def\)\s', 'b') + nnoremap ]m :call pymode#motion#move2('^\s*def\s', '') + nnoremap [m :call pymode#motion#move2('^\s*def\s', 'b') + + onoremap ]] :call pymode#motion#move2('^\(class\\|def\)\s', '') + onoremap [[ :call pymode#motion#move2('^\(class\\|def\)\s', 'b') + onoremap ]m :call pymode#motion#move2('^\s*def\s', '') + onoremap [m :call pymode#motion#move2('^\s*def\s', 'b') + + vnoremap ]] :call pymode#motion#vmove('^\(class\\|def\)\s', '') + vnoremap [[ :call pymode#motion#vmove('^\(class\\|def\)\s', 'b') + vnoremap ]m :call pymode#motion#vmove('^\s*def\s', '') + vnoremap [m :call pymode#motion#vmove('^\s*def\s', 'b') + + onoremap c :call pymode#motion#select('^\s*class\s', 0) + onoremap ac :call pymode#motion#select('^\s*class\s', 0) + onoremap ic :call pymode#motion#select('^\s*class\s', 1) + vnoremap ac :call pymode#motion#select('^\s*class\s', 0) + vnoremap ic :call pymode#motion#select('^\s*class\s', 1) + + onoremap m :call pymode#motion#select('^\s*def\s', 0) + onoremap am :call pymode#motion#select('^\s*def\s', 0) + onoremap im :call pymode#motion#select('^\s*def\s', 1) + vnoremap am :call pymode#motion#select('^\s*def\s', 0) + vnoremap im :call pymode#motion#select('^\s*def\s', 1) endif diff --git a/autoload/pymode/motion.vim b/autoload/pymode/motion.vim index c922f5c6..d2c005f9 100644 --- a/autoload/pymode/motion.vim +++ b/autoload/pymode/motion.vim @@ -1,3 +1,6 @@ +" Check indentation level on motion +" dC dM + fun! pymode#motion#block(lnum) "{{{ let start = indent(a:lnum) let num = a:lnum @@ -11,18 +14,25 @@ fun! pymode#motion#block(lnum) "{{{ endfunction "}}} -fun! pymode#motion#move(pattern, flags) "{{{ - let i = v:count1 - while i > 0 - let result = searchpos(a:pattern, a:flags.'W') - let i = i - 1 +fun! pymode#motion#move2(pattern, flags, ...) "{{{ + let cnt = v:count1 - 1 + let [line, column] = searchpos(a:pattern, a:flags . 'W') + let indent = indent(line) + + while l:cnt && l:line + let [line, column] = searchpos(a:pattern, a:flags . 'W') + if indent(line) == l:indent + let cnt = l:cnt - 1 + endif endwhile - return result -endfunction "}}} + + return [line, column] + +endfunction "}}} fun! pymode#motion#vmove(pattern, flags) "{{{ - let end = pymode#motion#move(a:pattern, a:flags) + let end = pymode#motion#move2(a:pattern, a:flags) normal! gv call cursor(end) endfunction "}}} @@ -35,7 +45,7 @@ endfunction "}}} fun! pymode#motion#select(pattern, inner) "{{{ let orig = getpos('.')[1:2] - let start = pymode#motion#move(a:pattern, 'bW') + let start = pymode#motion#move2(a:pattern, 'cb') let eline = pymode#motion#block(start[0]) let end = [eline, len(getline(eline))] call cursor(orig) From 6b03c3d96691cbeede14cdca901ba598d2e86e15 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 6 Jan 2012 19:24:19 +0400 Subject: [PATCH 018/513] Set history. --- autoload/pymode/motion.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/autoload/pymode/motion.vim b/autoload/pymode/motion.vim index d2c005f9..1273f29e 100644 --- a/autoload/pymode/motion.vim +++ b/autoload/pymode/motion.vim @@ -15,6 +15,7 @@ endfunction "}}} fun! pymode#motion#move2(pattern, flags, ...) "{{{ + normal! m' let cnt = v:count1 - 1 let [line, column] = searchpos(a:pattern, a:flags . 'W') let indent = indent(line) From 728067de07818c72c85780706911736595c1729c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 6 Jan 2012 19:29:06 +0400 Subject: [PATCH 019/513] Version 0.5.1 --- Changelog.rst | 5 +++++ README.rst | 8 ++++---- doc/pymode.txt | 25 +++++++++++++++++++++++ ftplugin/python/pymode.vim | 30 +++++++++++++++++++++------- plugin/pymode.vim | 41 ++++++++++++++++++++++++++++++++++++-- 5 files changed, 96 insertions(+), 13 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index bb637b55..d4060444 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2012-01-06 0.5.1 +------------------- +* Happy new year! +* Objects and motion fixes + ## 2011-11-30 0.5.0 ------------------- * Add python objects and motions (beta) diff --git a/README.rst b/README.rst index 8edafec2..83093b3d 100644 --- a/README.rst +++ b/README.rst @@ -28,10 +28,10 @@ See (old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my f Changelog ========= -## 2011-11-30 0.5.0 +## 2012-01-06 0.5.1 ------------------- -* Add python objects and motions (beta) - :h pymode_motion +* Happy new year! +* Objects and motion fixes Requirements @@ -211,7 +211,7 @@ Other stuff Default values: :: - " Load motion plugin + " Load python objects and motion let g:pymode_motion = 1 " Load breakpoints plugin diff --git a/doc/pymode.txt b/doc/pymode.txt index a88dfe50..b96c3103 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -1,5 +1,13 @@ *pymode.txt* *python-mode.txt* Python-mode for vim! + ____ _ _ ____ _ _ _____ _ _ __ __ _____ ____ ____ ~ + ( _ \( \/ )(_ _)( )_( )( _ )( \( )___( \/ )( _ )( _ \( ___) ~ + )___/ \ / )( ) _ ( )(_)( ) ((___)) ( )(_)( )(_) ))__) ~ + (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ + + + Version: 0.5.1 + ============================================================================== CONTENTS *Python-mode-contents* @@ -321,6 +329,18 @@ K Show python docs for current word under cursor C-Space Rope code assist r Run current buffer b Set breakpoints +[[ Jump on previous class or function (normal, visual, operator modes) +]] Jump on next class or function (normal, visual, operator modes) +[m Jump on previous class or method (normal, visual, operator modes) +]m Jump on next class or method (normal, visual, operator modes) +ac c Operation with a class. + Ex: vac, dac, dc, yac, yc, cac, cc (normal, operator modes) +ic Operation with inner class. + Ex: vic, dic, yic, cic (normal, operator modes) +am m Operation with function or method. + Ex: vam, dam, dm, yam, ym, cam, cm (normal, operator modes) +im Operation with inner function or method. + Ex: vim, dim, yim, cim (normal, operator modes) ================ ============================ Note: @@ -406,6 +426,11 @@ You may set |exrc| and |secure| in your |vimrc| for auto set custom settings fro The Python-mode is released under the GNU lesser general public license. See: http://www.gnu.org/copyleft/lesser.html +If you like this plugin, you can send me postcard :) +My address is here: Russia, 143400, Krasnogorsk, Shkolnaya 1-19 +Thanks for support! + + ------------------------------------------------------------------------------ vim:tw=78:ts=8:ft=help:norl: diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 9ce4cf7e..6dcd2248 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -74,7 +74,8 @@ endif " }}} -" PyLint +" Lint {{{ + if g:pymode_lint " DESC: Set commands @@ -90,7 +91,11 @@ if g:pymode_lint endif -" Rope +" }}} + + +" Rope {{{ + if g:pymode_rope " DESC: Set keys @@ -106,7 +111,11 @@ if g:pymode_rope endif -" Run code +" }}} + + +" Execution {{{ + if g:pymode_run " DESC: Set commands @@ -117,7 +126,11 @@ if g:pymode_run endif -" Set breakpoints +" }}} + + +" Breakpoints {{{ + if g:pymode_breakpoint " DESC: Set keys @@ -125,12 +138,15 @@ if g:pymode_breakpoint endif -" OPTION: g:pymode_utils_whitespaces -- bool. Remove unused whitespaces on save -call pymode#Default("g:pymode_utils_whitespaces", 1) +" }}} + + +" Utils {{{ -" Utils whitespaces if g:pymode_utils_whitespaces au BufWritePre :call setline(1,map(getline(1,"$"),'substitute(v:val,"\\s\\+$","","")')) endif +" }}} + " vim: fdm=marker:fdl=0 diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 1aac51c9..1e176f04 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.5.0" +let g:pymode_version = "0.5.1" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version @@ -11,7 +11,7 @@ endif " DESC: Check python support if !has('python') echoerr expand(":t") . " required vim compiled with +python." - echoerr "Pymode pylint and rope plugins will be disabled." + echoerr "Pymode rope, pylint and virtualenv plugins will be disabled." let g:pymode_lint = 0 let g:pymode_rope = 0 let g:pymode_path = 0 @@ -30,6 +30,9 @@ sys.path = [ EOF endif + +" Lint {{{ + if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " OPTION: g:pymode_lint_write -- bool. Check code every save. @@ -147,6 +150,11 @@ def pyflakes(): EOF endif +" }}} + + +" Breakpoints {{{ + if !pymode#Default("g:pymode_breakpoint", 1) || g:pymode_breakpoint " OPTION: g:pymode_breakpoint_key -- string. Key for set/unset breakpoint. @@ -156,6 +164,11 @@ if !pymode#Default("g:pymode_breakpoint", 1) || g:pymode_breakpoint endif +" }}} + + +" Documentation {{{ + if !pymode#Default("g:pymode_doc", 1) || g:pymode_doc if !pymode#CheckProgram("pydoc", "or disable pymode_doc.") @@ -167,12 +180,22 @@ if !pymode#Default("g:pymode_doc", 1) || g:pymode_doc endif +" }}} + + +" Virtualenv {{{ + if !pymode#Default("g:pymode_virtualenv", 1) || g:pymode_virtualenv call pymode#Default("g:pymode_virtualenv_enabled", []) endif +" }}} + + +" Execution {{{ + if !pymode#Default("g:pymode_run", 1) || g:pymode_run if !pymode#CheckProgram("python", "or disable pymode_run.") @@ -184,6 +207,11 @@ if !pymode#Default("g:pymode_run", 1) || g:pymode_run endif +" }}} + + +" Rope {{{ + if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope " OPTION: g:pymode_rope_auto_project -- bool. Auto open ropeproject @@ -277,3 +305,12 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope menu Rope.UseFunction :RopeUseFunction endif + +" }}} + + +" OPTION: g:pymode_utils_whitespaces -- bool. Remove unused whitespaces on save +call pymode#Default("g:pymode_utils_whitespaces", 1) + +" vim: fdm=marker:fdl=0 + From 6fb8353d4234a18c53a0f9101526e1cf51737983 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sun, 8 Jan 2012 19:38:24 +0400 Subject: [PATCH 020/513] Version 0.5.2 --- Changelog.rst | 5 +++++ README.rst | 6 +++--- doc/pymode.txt | 2 +- plugin/pymode.vim | 9 ++++----- pylibs/ropemode/interface.py | 9 ++++++++- pylibs/ropevim.py | 33 +++++++++++++++++++++++++-------- 6 files changed, 46 insertions(+), 18 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index d4060444..60fb51b2 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2012-01-08 0.5.2 +------------------- +* Fix ropeomnicompletion +* Add preview documentation + ## 2012-01-06 0.5.1 ------------------- * Happy new year! diff --git a/README.rst b/README.rst index 83093b3d..1be45df6 100644 --- a/README.rst +++ b/README.rst @@ -28,10 +28,10 @@ See (old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my f Changelog ========= -## 2012-01-06 0.5.1 +## 2012-01-08 0.5.2 ------------------- -* Happy new year! -* Objects and motion fixes +* Fix ropeomnicompletion +* Add preview documentation Requirements diff --git a/doc/pymode.txt b/doc/pymode.txt index b96c3103..4203fc23 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.5.1 + Version: 0.5.2 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 1e176f04..6c3e14e2 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.5.1" +let g:pymode_version = "0.5.2" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version @@ -273,10 +273,9 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope endfunction "}}} fun! RopeOmni(findstart, base) "{{{ - " TODO: Fix omni - if a:findstart == 1 - let start = col('.') - 1 - return start + if a:findstart + py ropevim._interface._find_start() + return g:pymode_offset else call RopeOmniComplete() return g:pythoncomplete_completions diff --git a/pylibs/ropemode/interface.py b/pylibs/ropemode/interface.py index 05c562e3..551e60a4 100644 --- a/pylibs/ropemode/interface.py +++ b/pylibs/ropemode/interface.py @@ -13,6 +13,7 @@ def __init__(self, env): self.project = None self.old_content = None self.env = env + self._assist = None self._prepare_refactorings() self.autoimport = None @@ -271,7 +272,13 @@ def lucky_assist(self, prefix): @decorators.local_command(prefix='P') def omni_complete(self, prefix): - _CodeAssist(self, self.env).omni_complete(prefix) + self._assist.omni_complete(prefix) + + def _find_start(self): + self._assist = _CodeAssist(self, self.env) + start = (self.env.cursor[1] - self.env.get_offset() + + self._assist.starting_offset) + self.env._command('let g:pymode_offset = %s' % start) @decorators.local_command('a') def auto_import(self): diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 19c950e0..fc4f8d78 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -12,6 +12,11 @@ class VimUtils(ropemode.environment.Environment): + def __init__(self, *args, **kwargs): + super(VimUtils, self).__init__(*args, **kwargs) + self.completeopt = vim.eval('&completeopt') + self.preview = 'preview' in self.completeopt + def ask(self, prompt, default=None, starting=None): if starting is None: starting = '' @@ -46,6 +51,9 @@ def ask_directory(self, prompt, default=None, starting=None): return call('input("%s", ".", "dir")' % prompt) def _update_proposals(self, values): + self.completeopt = vim.eval('&completeopt') + self.preview = 'preview' in self.completeopt + if not self.get('extended_complete'): return u','.join(u"'%s'" % self._completion_text(proposal) for proposal in values) @@ -299,20 +307,28 @@ def _extended_completion(self, proposal): # we are using extended complete and return dicts instead of strings. # `ci` means "completion item". see `:help complete-items` word, _, menu = map(lambda x: x.strip(), proposal.name.partition(':')) - ci = dict(word = word, menu = menu or '') - kind = ''.join(s if s not in 'aeyuo' else '' for s in proposal.type)[:3] + ci = dict( + word = word, + info = '', + kind = ''.join(s if s not in 'aeyuo' else '' for s in proposal.type)[:3], + menu = menu or '') if proposal.scope == 'parameter_keyword': default = proposal.get_default() ci["menu"] += '*' if default is None else '= %s' % default - if menu is '': - obj_doc = proposal.get_doc() - ci["menu"] = self._docstring_re.match(obj_doc).group(1) if obj_doc else '' + if self.preview and not ci['menu']: + doc = proposal.get_doc() + ci['info'] = self._docstring_re.match(doc).group(1) if doc else '' - ci['kind'] = kind - ci['menu'] = menu.replace('"', '\\"') - return repr(ci).replace(": u'", ": '") + return self._conv(ci) + + def _conv(self, obj): + if isinstance(obj, dict): + return u'{' + u','.join([ + u"%s:%s" % (self._conv(key), self._conv(value)) + for key, value in obj.iteritems()]) + u'}' + return u'"%s"' % str(obj).replace(u'"', u'\\"') def _vim_name(name): @@ -377,6 +393,7 @@ def __call__(self, arg_lead, cmd_line, cursor_pos): vim.command('let s:completions = %s' % result) + ropemode.decorators.logger.message = echo ropemode.decorators.logger.only_short = True From 475b1a0ab3677a1edf70f91f4db72fd8801d5b59 Mon Sep 17 00:00:00 2001 From: Dirk Wallenstein Date: Mon, 9 Jan 2012 15:09:22 +0100 Subject: [PATCH 021/513] Override configuration with mandatory values Prevent the configuration from breaking python-mode. The output-format has to be 'parsable'. --- plugin/pymode.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 6c3e14e2..71ee174f 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -88,9 +88,9 @@ linter = lint.PyLinter() pylint_re = re.compile('^[^:]+:(\d+): \[([EWRCI]+)[^\]]*\] (.*)$') checkers.initialize(linter) +linter.load_file_configuration(vim.eval("g:pymode_lint_config")) linter.set_option("output-format", "parseable") linter.set_option("reports", 0) -linter.load_file_configuration(vim.eval("g:pymode_lint_config")) # Pyflakes setup From 71064082f3f579c0992503d02ff4c86ccbf6e9a3 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 10 Jan 2012 01:28:06 +0400 Subject: [PATCH 022/513] Version 0.5.3 --- Changelog.rst | 6 +++++- README.rst | 6 +++--- doc/pymode.txt | 4 ++-- plugin/pymode.vim | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 60fb51b2..945d078f 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2012-01-09 0.5.3 +------------------- +* Prevent the configuration from breaking python-mode + (c) Dirk Wallenstein + ## 2012-01-08 0.5.2 ------------------- * Fix ropeomnicompletion @@ -69,7 +74,6 @@ Changelog * Add 'g:pymode_rope_always_show_complete_menu' option * Some pylint fixes - ## 2011-10-25 0.3.0 ------------------- * Add g:pymode_lint_minheight and g:pymode_lint_maxheight diff --git a/README.rst b/README.rst index 1be45df6..6d7784e3 100644 --- a/README.rst +++ b/README.rst @@ -28,10 +28,10 @@ See (old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my f Changelog ========= -## 2012-01-08 0.5.2 +## 2012-01-09 0.5.3 ------------------- -* Fix ropeomnicompletion -* Add preview documentation +* Prevent the configuration from breaking python-mode + (c) Dirk Wallenstein Requirements diff --git a/doc/pymode.txt b/doc/pymode.txt index 4203fc23..cc66aab7 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -2,11 +2,11 @@ ____ _ _ ____ _ _ _____ _ _ __ __ _____ ____ ____ ~ ( _ \( \/ )(_ _)( )_( )( _ )( \( )___( \/ )( _ )( _ \( ___) ~ - )___/ \ / )( ) _ ( )(_)( ) ((___)) ( )(_)( )(_) ))__) ~ + )___/ \ / )( ) _ ( )(_)( ) ((___)) ( )(_)( )(_) ))__) ~ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.5.2 + Version: 0.5.3 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 71ee174f..07db082b 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.5.2" +let g:pymode_version = "0.5.3" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 83763595d2243ee20e1132534c35da40e20a7cef Mon Sep 17 00:00:00 2001 From: Fredrik Henrysson Date: Fri, 13 Jan 2012 08:27:56 +0100 Subject: [PATCH 023/513] Add sign marker for lint Info --- plugin/pymode.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 07db082b..26f71b6e 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -64,6 +64,7 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint sign define C text=CC texthl=Comment sign define R text=RR texthl=Visual sign define E text=EE texthl=Error + sign define I text=II texthl=Info endif From b8c9eec3c0d66fcc11e6651ad08ae81d430aa720 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 17 Jan 2012 23:37:51 +0400 Subject: [PATCH 024/513] Version 0.5.4 --- Changelog.rst | 6 ++++++ README.rst | 14 +++++++------- after/ftplugin/python.vim | 40 +++++++++++++++++++++------------------ doc/pymode.txt | 34 ++++++++++++++++----------------- plugin/pymode.vim | 2 +- 5 files changed, 53 insertions(+), 43 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 945d078f..8daa2f47 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,12 @@ Changelog ========= +## 2012-01-17 0.5.4 +------------------- +* Add a sign for info messages from pylint. + (c) Fredrik Henrysson +* Change motion keys: vic - viC, dam - daM and etc + ## 2012-01-09 0.5.3 ------------------- * Prevent the configuration from breaking python-mode diff --git a/README.rst b/README.rst index 6d7784e3..da323aa5 100644 --- a/README.rst +++ b/README.rst @@ -7,7 +7,7 @@ features like python code looking for bugs, refactoring and some other useful th This plugin allow you create python code in vim very easily. There is no need to install the pylint_, rope_ or any used python library on your system. -- Python objects and motion (]], 3[[, ]]m, vac, vim, dac, cim, ...) +- Python objects and motion (]], 3[[, ]]M, vaC, viM, daC, ciM, ...) - Highlight syntax errors - Highlight and auto fix unused imports - Strong code completion @@ -301,17 +301,17 @@ Keys Command -------------- ------------- ]] Jump on next class or function (normal, visual, operator modes) -------------- ------------- -[m Jump on previous class or method (normal, visual, operator modes) +[M Jump on previous class or method (normal, visual, operator modes) -------------- ------------- -]m Jump on next class or method (normal, visual, operator modes) +]M Jump on next class or method (normal, visual, operator modes) -------------- ------------- -ac c Select a class. Ex: vac, dac, dc, yac, yc, cac, cc (normal, operator modes) +aC C Select a class. Ex: vaC, daC, dC, yaC, yC, caC, cC (normal, operator modes) -------------- ------------- -ic Select inner class. Ex: vic, dic, yic, cic (normal, operator modes) +iC Select inner class. Ex: viC, diC, yiC, ciC (normal, operator modes) -------------- ------------- -am m Select a function or method. Ex: vam, dam, dm, yam, ym, cam, cm (normal, operator modes) +aM M Select a function or method. Ex: vaM, daM, dM, yaM, yM, caM, cM (normal, operator modes) -------------- ------------- -im Select inner function or method. Ex: vim, dim, yim, cim (normal, operator modes) +iM Select inner function or method. Ex: viM, diM, yiM, ciM (normal, operator modes) ============== ============= .. note:: See also ``:help ropevim.txt`` diff --git a/after/ftplugin/python.vim b/after/ftplugin/python.vim index c5ac59f4..301c7184 100644 --- a/after/ftplugin/python.vim +++ b/after/ftplugin/python.vim @@ -9,30 +9,34 @@ endif nnoremap ]] :call pymode#motion#move2('^\(class\\|def\)\s', '') nnoremap [[ :call pymode#motion#move2('^\(class\\|def\)\s', 'b') - nnoremap ]m :call pymode#motion#move2('^\s*def\s', '') - nnoremap [m :call pymode#motion#move2('^\s*def\s', 'b') + nnoremap ]C :call pymode#motion#move2('^\(class\\|def\)\s', '') + nnoremap [C :call pymode#motion#move2('^\(class\\|def\)\s', 'b') + nnoremap ]M :call pymode#motion#move2('^\s*def\s', '') + nnoremap [M :call pymode#motion#move2('^\s*def\s', 'b') onoremap ]] :call pymode#motion#move2('^\(class\\|def\)\s', '') onoremap [[ :call pymode#motion#move2('^\(class\\|def\)\s', 'b') - onoremap ]m :call pymode#motion#move2('^\s*def\s', '') - onoremap [m :call pymode#motion#move2('^\s*def\s', 'b') + onoremap ]C :call pymode#motion#move2('^\(class\\|def\)\s', '') + onoremap [C :call pymode#motion#move2('^\(class\\|def\)\s', 'b') + onoremap ]M :call pymode#motion#move2('^\s*def\s', '') + onoremap [M :call pymode#motion#move2('^\s*def\s', 'b') vnoremap ]] :call pymode#motion#vmove('^\(class\\|def\)\s', '') vnoremap [[ :call pymode#motion#vmove('^\(class\\|def\)\s', 'b') - vnoremap ]m :call pymode#motion#vmove('^\s*def\s', '') - vnoremap [m :call pymode#motion#vmove('^\s*def\s', 'b') - - onoremap c :call pymode#motion#select('^\s*class\s', 0) - onoremap ac :call pymode#motion#select('^\s*class\s', 0) - onoremap ic :call pymode#motion#select('^\s*class\s', 1) - vnoremap ac :call pymode#motion#select('^\s*class\s', 0) - vnoremap ic :call pymode#motion#select('^\s*class\s', 1) - - onoremap m :call pymode#motion#select('^\s*def\s', 0) - onoremap am :call pymode#motion#select('^\s*def\s', 0) - onoremap im :call pymode#motion#select('^\s*def\s', 1) - vnoremap am :call pymode#motion#select('^\s*def\s', 0) - vnoremap im :call pymode#motion#select('^\s*def\s', 1) + vnoremap ]M :call pymode#motion#vmove('^\s*def\s', '') + vnoremap [M :call pymode#motion#vmove('^\s*def\s', 'b') + + onoremap C :call pymode#motion#select('^\s*class\s', 0) + onoremap aC :call pymode#motion#select('^\s*class\s', 0) + onoremap iC :call pymode#motion#select('^\s*class\s', 1) + vnoremap aC :call pymode#motion#select('^\s*class\s', 0) + vnoremap iC :call pymode#motion#select('^\s*class\s', 1) + + onoremap M :call pymode#motion#select('^\s*def\s', 0) + onoremap aM :call pymode#motion#select('^\s*def\s', 0) + onoremap iM :call pymode#motion#select('^\s*def\s', 1) + vnoremap aM :call pymode#motion#select('^\s*def\s', 0) + vnoremap iM :call pymode#motion#select('^\s*def\s', 1) endif diff --git a/doc/pymode.txt b/doc/pymode.txt index cc66aab7..a893dfb3 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.5.3 + Version: 0.5.4 ============================================================================== CONTENTS *Python-mode-contents* @@ -307,12 +307,12 @@ Key Command ================ ============================ [[ Jump on previous class or function (normal, visual, operator modes) ]] Jump on next class or function (normal, visual, operator modes) -[m Jump on previous class or method (normal, visual, operator modes) -]m Jump on next class or method (normal, visual, operator modes) -ac Select a class. Ex: vac, dac, yac, cac (normal, operator modes) -ic Select inner class. Ex: vic, dic, yic, cic (normal, operator modes) -am Select a function or method. Ex: vam, dam, yam, cam (normal, operator modes) -im Select inner function or method. Ex: vim, dim, yim, cim (normal, operator modes) +[M Jump on previous class or method (normal, visual, operator modes) +]M Jump on next class or method (normal, visual, operator modes) +aC Select a class. Ex: vaC, daC, yaC, caC (normal, operator modes) +iC Select inner class. Ex: viC, diC, yiC, ciC (normal, operator modes) +aM Select a function or method. Ex: vaM, daM, yaM, caM (normal, operator modes) +iM Select inner function or method. Ex: viM, diM, yiM, ciM (normal, operator modes) ================ ============================ @@ -331,16 +331,16 @@ C-Space Rope code assist b Set breakpoints [[ Jump on previous class or function (normal, visual, operator modes) ]] Jump on next class or function (normal, visual, operator modes) -[m Jump on previous class or method (normal, visual, operator modes) -]m Jump on next class or method (normal, visual, operator modes) -ac c Operation with a class. - Ex: vac, dac, dc, yac, yc, cac, cc (normal, operator modes) -ic Operation with inner class. - Ex: vic, dic, yic, cic (normal, operator modes) -am m Operation with function or method. - Ex: vam, dam, dm, yam, ym, cam, cm (normal, operator modes) -im Operation with inner function or method. - Ex: vim, dim, yim, cim (normal, operator modes) +[M Jump on previous class or method (normal, visual, operator modes) +]M Jump on next class or method (normal, visual, operator modes) +aC C Operation with a class. + Ex: vaC, daC, dC, yaC, yC, caC, cC (normal, operator modes) +iC Operation with inner class. + Ex: viC, diC, yiC, ciC (normal, operator modes) +aM M Operation with function or method. + Ex: vaM, daM, dM, yaM, yM, caM, cM (normal, operator modes) +iM Operation with inner function or method. + Ex: viM, diM, yiM, ciM (normal, operator modes) ================ ============================ Note: diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 26f71b6e..893b37f1 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.5.3" +let g:pymode_version = "0.5.4" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 84074b1a4859fd7fb35a216feaf900bb73afb5ff Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 17 Jan 2012 23:52:40 +0400 Subject: [PATCH 025/513] Version 0.5.5 --- Changelog.rst | 3 ++- README.rst | 5 ++++- doc/pymode.txt | 13 +++++++++++-- ftplugin/python/pymode.vim | 4 ++++ plugin/pymode.vim | 5 ++++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 8daa2f47..7697ee8d 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,11 +1,12 @@ Changelog ========= -## 2012-01-17 0.5.4 +## 2012-01-17 0.5.5 ------------------- * Add a sign for info messages from pylint. (c) Fredrik Henrysson * Change motion keys: vic - viC, dam - daM and etc +* Add 'g:pymode_lint_onfly' option ## 2012-01-09 0.5.3 ------------------- diff --git a/README.rst b/README.rst index da323aa5..f1b3ee58 100644 --- a/README.rst +++ b/README.rst @@ -136,6 +136,9 @@ Default values: :: " values (pylint, pyflakes) let g:pymode_lint_checker = "pylint" + " Run linter on the fly + let g:pymode_lint_onfly = 0 + " Pylint configuration file " If file not found use 'pylintrc' from python-mode plugin directory let g:pymode_lint_config = "$HOME/.pylintrc" @@ -411,7 +414,7 @@ License Licensed under a `GNU lesser general public license`_. If you like this plugin, you can send me postcard :) -My address is here: Russia, 143400, Krasnogorsk, Shkolnaya 1-19 +My address is here: Russia, 143401, Krasnogorsk, Shkolnaya 1 kv. 19 Thanks for support! diff --git a/doc/pymode.txt b/doc/pymode.txt index a893dfb3..a45efe28 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.5.4 + Version: 0.5.5 ============================================================================== CONTENTS *Python-mode-contents* @@ -57,6 +57,8 @@ PythonMode. These options should be set in your vimrc. |'pymode_lint_checker'| Switch code checkers (pylint, pyflakes) +|'pymode_lint_onfly'| Run linter on the fly + |'pymode_lint_config'| Filepath to pylinc configuration |'pymode_lint_write'| Check code every save @@ -153,6 +155,13 @@ Default: "pylint". This option set code checker. +------------------------------------------------------------------------------ + *'pymode_lint_onfly'* +Values: 0 or 1 +Default: 0 + +This option enabled "on the fly" code checking + ------------------------------------------------------------------------------ *'pymode_lint_config'* Values: 'Path to pylint configuration file' @@ -427,7 +436,7 @@ The Python-mode is released under the GNU lesser general public license. See: http://www.gnu.org/copyleft/lesser.html If you like this plugin, you can send me postcard :) -My address is here: Russia, 143400, Krasnogorsk, Shkolnaya 1-19 +My address is here: Russia, 143401, Krasnogorsk, Shkolnaya 1 kv. 19 Thanks for support! diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 6dcd2248..f2ff027c 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -89,6 +89,10 @@ if g:pymode_lint au BufWritePost PyLint endif + if g:pymode_lint_onfly + au InsertLeave PyLint + endif + endif " }}} diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 893b37f1..e6186a52 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.5.4" +let g:pymode_version = "0.5.5" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version @@ -38,6 +38,9 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " OPTION: g:pymode_lint_write -- bool. Check code every save. call pymode#Default("g:pymode_lint_write", 1) + " OPTION: g:pymode_lint_onfly -- bool. Check code every save. + call pymode#Default("g:pymode_lint_onfly", 0) + " OPTION: g:pymode_lint_checker -- str. Use pylint of pyflakes for check. call pymode#Default("g:pymode_lint_checker", "pylint") From a1b5702e8d9e6616439d3a9e5a3b0553ee58c941 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 17 Jan 2012 23:54:02 +0400 Subject: [PATCH 026/513] Fix readme --- README.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index f1b3ee58..c96522d4 100644 --- a/README.rst +++ b/README.rst @@ -28,10 +28,12 @@ See (old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my f Changelog ========= -## 2012-01-09 0.5.3 +## 2012-01-17 0.5.5 ------------------- -* Prevent the configuration from breaking python-mode - (c) Dirk Wallenstein +* Add a sign for info messages from pylint. + (c) Fredrik Henrysson +* Change motion keys: vic - viC, dam - daM and etc +* Add 'g:pymode_lint_onfly' option Requirements From 95ff68c3a7f2d7e99d59adc2971f32a869309db3 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 6 Feb 2012 14:06:24 +0400 Subject: [PATCH 027/513] Version 0.5.6 --- Changelog.rst | 8 ++++++++ README.rst | 3 +++ autoload/pymode.vim | 9 +++++++++ autoload/pymode/lint.vim | 25 +++++++++++++++++++++++++ doc/pymode.txt | 9 +++++++++ ftplugin/python/pymode.vim | 8 ++++++++ plugin/pymode.vim | 18 ++++++++++++++---- syntax/python.vim | 3 +++ 8 files changed, 79 insertions(+), 4 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 7697ee8d..7c9ab9a9 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,14 @@ Changelog ========= +## 2012-02-06 0.5.6 +------------------- +* Fix 'g:pymode_syntax' option +* Show error message in bottom part of screen + see 'g:pymode_lint_message' +* Fix pylint for windows users +* Fix breakpoint command (Use pdb when idpb not installed) + ## 2012-01-17 0.5.5 ------------------- * Add a sign for info messages from pylint. diff --git a/README.rst b/README.rst index c96522d4..9aea0433 100644 --- a/README.rst +++ b/README.rst @@ -151,6 +151,9 @@ Default values: :: " Auto open cwindow if errors be finded let g:pymode_lint_cwindow = 1 + " Show error message if cursor placed at the error line + let g:pymode_lint_message = 1 + " Auto jump on first error let g:pymode_lint_jump = 0 diff --git a/autoload/pymode.vim b/autoload/pymode.vim index f9979820..7cd234ef 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -73,3 +73,12 @@ fun! pymode#ShowCommand(cmd) "{{{ normal gg wincmd p endfunction "}}} + +" DESC: Show wide message +fun! pymode#WideMessage(msg) "{{{ + let x=&ruler | let y=&showcmd + set noruler noshowcmd + redraw + echo strpart(a:msg, 0, &columns-1) + let &ruler=x | let &showcmd=y +endfunction "}}} diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 99758a22..6a93b81d 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -10,12 +10,23 @@ function! pymode#lint#Check() endif exe "py ".g:pymode_lint_checker."()" call setqflist(b:qf_list, 'r') + if g:pymode_lint_cwindow call pymode#QuickfixOpen(0, 0, g:pymode_lint_maxheight, g:pymode_lint_minheight, g:pymode_lint_jump) endif + if g:pymode_lint_signs call pymode#PlaceSigns() endif + + if g:pymode_lint_message + let b:errors = {} + for v in b:qf_list + let b:errors[v['lnum']] = v['text'] + endfor + call pymode#lint#show_errormessage() + endif + endfunction fun! pymode#lint#Toggle() "{{{ @@ -45,3 +56,17 @@ fun! pymode#lint#toggle_win(toggle, msg) "{{{ cclose endif endfunction "}}} + +fun! pymode#lint#show_errormessage() "{{{ + let cursor = getpos(".") + if !pymode#Default('b:errors', {}) || !len(b:errors) + return + endif + if has_key(b:errors, l:cursor[1]) + call pymode#WideMessage(b:errors[l:cursor[1]]) + let b:show_message = 1 + else + let b:show_message = 0 + echo + endif +endfunction " }}} diff --git a/doc/pymode.txt b/doc/pymode.txt index a45efe28..2860b0f5 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -65,6 +65,8 @@ PythonMode. These options should be set in your vimrc. |'pymode_lint_cwindow'| Show cwindow +|'pymode_lint_message'| Show current line errors in bottom + |'pymode_lint_signs'| Place signs |'pymode_lint_jump'| Auto jump on first error @@ -184,6 +186,13 @@ Default: 1. If this option is set to 0 then pylint not show cwindow. +------------------------------------------------------------------------------ + *'pymode_lint_message'* +Values: 0 or 1. +Default: 1. + +If this option is set to 0 then pylint not show errors in bottom + ------------------------------------------------------------------------------ *'pymode_lint_signs'* Values: 0 or 1. diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index f2ff027c..f7481b2a 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -78,6 +78,9 @@ endif if g:pymode_lint + " DESC: Show message flag + let b:show_message = 0 + " DESC: Set commands command! -buffer -nargs=0 PyLintToggle :call pymode#lint#Toggle() command! -buffer -nargs=0 PyLintWindowToggle :call pymode#lint#ToggleWindow() @@ -93,6 +96,11 @@ if g:pymode_lint au InsertLeave PyLint endif + if g:pymode_lint_message + au CursorHold call pymode#lint#show_errormessage() + au CursorMoved call pymode#lint#show_errormessage() + endif + endif " }}} diff --git a/plugin/pymode.vim b/plugin/pymode.vim index e6186a52..3d82c32d 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.5.5" +let g:pymode_version = "0.5.6" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version @@ -41,6 +41,9 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " OPTION: g:pymode_lint_onfly -- bool. Check code every save. call pymode#Default("g:pymode_lint_onfly", 0) + " OPTION: g:pymode_lint_message -- bool. Show current line error message + call pymode#Default("g:pymode_lint_message", 1) + " OPTION: g:pymode_lint_checker -- str. Use pylint of pyflakes for check. call pymode#Default("g:pymode_lint_checker", "pylint") @@ -89,7 +92,7 @@ from pyflakes import checker # Pylint setup linter = lint.PyLinter() -pylint_re = re.compile('^[^:]+:(\d+): \[([EWRCI]+)[^\]]*\] (.*)$') +pylint_re = re.compile('^(?:.:)?[^:]+:(\d+): \[([EWRCI]+)[^\]]*\] (.*)$') checkers.initialize(linter) linter.load_file_configuration(vim.eval("g:pymode_lint_config")) @@ -161,11 +164,18 @@ endif if !pymode#Default("g:pymode_breakpoint", 1) || g:pymode_breakpoint + if !pymode#Default("g:pymode_breakpoint_cmd", "import ipdb; ipdb.set_trace() ### XXX BREAKPOINT") && has("python") +python << EOF +try: + import ipdb +except ImportError: + vim.command('let g:pymode_breakpoint_cmd = "import pdb; pdb.set_trace() ### XXX BREAKPOINT"') +EOF + endif + " OPTION: g:pymode_breakpoint_key -- string. Key for set/unset breakpoint. call pymode#Default("g:pymode_breakpoint_key", "b") - call pymode#Default("g:pymode_breakpoint_cmd", "import ipdb; ipdb.set_trace() ### XXX BREAKPOINT") - endif " }}} diff --git a/syntax/python.vim b/syntax/python.vim index a470061f..d3b0fdd7 100644 --- a/syntax/python.vim +++ b/syntax/python.vim @@ -1,5 +1,8 @@ " vim: ft=vim:fdm=marker +" OPTION: g:pymode_syntax -- bool. +call pymode#Default('g:pymode_syntax', 1) + " DESC: Disable script loading if pymode#Default('b:current_syntax', 'python') || !g:pymode_syntax finish From 50aa001b48241e102a85a79c0eb18d5290ba82f8 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 6 Feb 2012 14:07:36 +0400 Subject: [PATCH 028/513] Changelog information to README --- README.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 9aea0433..cdc210d0 100644 --- a/README.rst +++ b/README.rst @@ -28,12 +28,13 @@ See (old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my f Changelog ========= -## 2012-01-17 0.5.5 +## 2012-02-06 0.5.6 ------------------- -* Add a sign for info messages from pylint. - (c) Fredrik Henrysson -* Change motion keys: vic - viC, dam - daM and etc -* Add 'g:pymode_lint_onfly' option +* Fix 'g:pymode_syntax' option +* Show error message in bottom part of screen + see 'g:pymode_lint_message' +* Fix pylint for windows users +* Fix breakpoint command (Use pdb when idpb not installed) Requirements From fadc02a7d82f86e0c284a077bc0992a727a04703 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 6 Feb 2012 22:06:58 +0400 Subject: [PATCH 029/513] Fix post address --- README.rst | 4 ++-- doc/pymode.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index cdc210d0..0f3c944a 100644 --- a/README.rst +++ b/README.rst @@ -420,8 +420,8 @@ License Licensed under a `GNU lesser general public license`_. If you like this plugin, you can send me postcard :) -My address is here: Russia, 143401, Krasnogorsk, Shkolnaya 1 kv. 19 -Thanks for support! +My address is here: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Klenov". +**Thanks for support!** .. _GNU lesser general public license: http://www.gnu.org/copyleft/lesser.html diff --git a/doc/pymode.txt b/doc/pymode.txt index 2860b0f5..144add39 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -445,7 +445,7 @@ The Python-mode is released under the GNU lesser general public license. See: http://www.gnu.org/copyleft/lesser.html If you like this plugin, you can send me postcard :) -My address is here: Russia, 143401, Krasnogorsk, Shkolnaya 1 kv. 19 +My address is here: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Klenov". Thanks for support! From 73f92fe76e604bb416ab408cee2b380f8d135b9a Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sat, 11 Feb 2012 00:18:10 +0400 Subject: [PATCH 030/513] Fix lint message. --- autoload/pymode/lint.vim | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 6a93b81d..8effa769 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -9,6 +9,15 @@ function! pymode#lint#Check() endtry endif exe "py ".g:pymode_lint_checker."()" + + if g:pymode_lint_message + let b:errors = {} + for v in b:qf_list + let b:errors[v['lnum']] = v['text'] + endfor + call pymode#lint#show_errormessage() + endif + call setqflist(b:qf_list, 'r') if g:pymode_lint_cwindow @@ -19,14 +28,6 @@ function! pymode#lint#Check() call pymode#PlaceSigns() endif - if g:pymode_lint_message - let b:errors = {} - for v in b:qf_list - let b:errors[v['lnum']] = v['text'] - endfor - call pymode#lint#show_errormessage() - endif - endfunction fun! pymode#lint#Toggle() "{{{ From f7ddb737963f1332444751312cac650270fb2154 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sat, 11 Feb 2012 00:19:17 +0400 Subject: [PATCH 031/513] Fix virtualenv --- autoload/pymode/virtualenv.vim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/autoload/pymode/virtualenv.vim b/autoload/pymode/virtualenv.vim index 536e64ca..fd0007da 100644 --- a/autoload/pymode/virtualenv.vim +++ b/autoload/pymode/virtualenv.vim @@ -1,5 +1,9 @@ fun! pymode#virtualenv#Activate() "{{{ + if !exists("$VIRTUAL_ENV") + return + endif + for env in g:pymode_virtualenv_enabled if env == $VIRTUAL_ENV return 0 @@ -7,7 +11,6 @@ fun! pymode#virtualenv#Activate() "{{{ endfor call add(g:pymode_virtualenv_enabled, $VIRTUAL_ENV) - echomsg "Enabled virtualenv: " . $VIRTUAL_ENV python << EOF ve_dir = os.environ['VIRTUAL_ENV'] From c24fa9aa9ad07ea819520efa0e266a990d0ea133 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sat, 11 Feb 2012 00:19:46 +0400 Subject: [PATCH 032/513] Version 0.5.7 --- Changelog.rst | 6 ++++ README.rst | 10 +++--- ftplugin/python/pymode.vim | 16 ---------- plugin/pymode.vim | 64 ++++++++++++++++++++------------------ 4 files changed, 44 insertions(+), 52 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 7c9ab9a9..f76aacb5 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,12 @@ Changelog ========= +## 2012-02-11 0.5.7 +------------------- +* Fix 'g:pymode_lint_message' mode error +* Fix breakpoints +* Fix python paths and virtualenv detection + ## 2012-02-06 0.5.6 ------------------- * Fix 'g:pymode_syntax' option diff --git a/README.rst b/README.rst index 0f3c944a..d3e655d2 100644 --- a/README.rst +++ b/README.rst @@ -28,13 +28,11 @@ See (old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my f Changelog ========= -## 2012-02-06 0.5.6 +## 2012-02-11 0.5.7 ------------------- -* Fix 'g:pymode_syntax' option -* Show error message in bottom part of screen - see 'g:pymode_lint_message' -* Fix pylint for windows users -* Fix breakpoint command (Use pdb when idpb not installed) +* Fix 'g:pymode_lint_message' mode error +* Fix breakpoints +* Fix python paths and virtualenv detection Requirements diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index f7481b2a..a4e537d7 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -43,22 +43,6 @@ endif " }}} -" Paths {{{ - -" Fix path for project -if g:pymode - py curpath = vim.eval('getcwd()') - py curpath in sys.path or sys.path.append(curpath) -endif - -" Add virtualenv paths -if g:pymode_virtualenv && exists("$VIRTUAL_ENV") - call pymode#virtualenv#Activate() -endif - -" }}} - - " Documentation {{{ if g:pymode_doc diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 3d82c32d..3ca8cceb 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.5.6" +let g:pymode_version = "0.5.7" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version @@ -21,16 +21,31 @@ endif " DESC: Fix python path if !pymode#Default('g:pymode_path', 1) || g:pymode_path python << EOF -import sys, vim -from os import path as op +import sys, vim, os -sys.path = [ - op.join(op.dirname(op.dirname(vim.eval("expand(':p')"))), - 'pylibs'), vim.eval("getcwd()") ] + sys.path +curpath = vim.eval("getcwd()") +libpath = os.path.join(os.path.dirname(os.path.dirname( + vim.eval("expand(':p')"))), 'pylibs') + +sys.path = [libpath, curpath] + sys.path EOF endif +" Virtualenv {{{ + +if !pymode#Default("g:pymode_virtualenv", 1) || g:pymode_virtualenv + + call pymode#Default("g:pymode_virtualenv_enabled", []) + + " Add virtualenv paths + call pymode#virtualenv#Activate() + +endif + +" }}} + + " Lint {{{ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint @@ -160,27 +175,6 @@ endif " }}} -" Breakpoints {{{ - -if !pymode#Default("g:pymode_breakpoint", 1) || g:pymode_breakpoint - - if !pymode#Default("g:pymode_breakpoint_cmd", "import ipdb; ipdb.set_trace() ### XXX BREAKPOINT") && has("python") -python << EOF -try: - import ipdb -except ImportError: - vim.command('let g:pymode_breakpoint_cmd = "import pdb; pdb.set_trace() ### XXX BREAKPOINT"') -EOF - endif - - " OPTION: g:pymode_breakpoint_key -- string. Key for set/unset breakpoint. - call pymode#Default("g:pymode_breakpoint_key", "b") - -endif - -" }}} - - " Documentation {{{ if !pymode#Default("g:pymode_doc", 1) || g:pymode_doc @@ -197,11 +191,21 @@ endif " }}} -" Virtualenv {{{ +" Breakpoints {{{ -if !pymode#Default("g:pymode_virtualenv", 1) || g:pymode_virtualenv +if !pymode#Default("g:pymode_breakpoint", 1) || g:pymode_breakpoint - call pymode#Default("g:pymode_virtualenv_enabled", []) + if !pymode#Default("g:pymode_breakpoint_cmd", "import ipdb; ipdb.set_trace() ### XXX BREAKPOINT") && has("python") +python << EOF +try: + import ipdb +except ImportError: + vim.command('let g:pymode_breakpoint_cmd = "import pdb; pdb.set_trace() ### XXX BREAKPOINT"') +EOF + endif + + " OPTION: g:pymode_breakpoint_key -- string. Key for set/unset breakpoint. + call pymode#Default("g:pymode_breakpoint_key", "b") endif From c79cf865182debd463a0c3cb5d0a37cefe635069 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sat, 11 Feb 2012 22:12:32 +0400 Subject: [PATCH 033/513] Fix pylint for windows users. --- pylibs/pylint/lint.py | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/pylibs/pylint/lint.py b/pylibs/pylint/lint.py index d11d7167..d3bb00ea 100644 --- a/pylibs/pylint/lint.py +++ b/pylibs/pylint/lint.py @@ -494,6 +494,10 @@ def check(self, files_or_modules): # if it's actually a c extension) self.current_file = astng.file self.check_astng_module(astng, walker, rawcheckers) + + # Close file for windows users + astng.file_stream.close() + # notify global end self.set_current_module('') self.stats['statement'] = walker.nbstatements @@ -632,7 +636,7 @@ def report_messages_stats(sect, stats, _): if not stats['by_msg']: # don't print this report when we didn't detected any errors raise EmptyReport() - in_order = sorted([(value, msg_id) + in_order = sorted([(value, msg_id) for msg_id, value in stats['by_msg'].items() if not msg_id.startswith('I')]) in_order.reverse() @@ -809,28 +813,28 @@ def __init__(self, args, reporter=None, exit=True): # add some help section linter.add_help_section('Environment variables', config.ENV_HELP, level=1) linter.add_help_section('Output', ''' -Using the default text output, the message format is : - - MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE - -There are 5 kind of message types : - * (C) convention, for programming standard violation - * (R) refactor, for bad code smell - * (W) warning, for python specific problems - * (E) error, for probable bugs in the code +Using the default text output, the message format is : + + MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE + +There are 5 kind of message types : + * (C) convention, for programming standard violation + * (R) refactor, for bad code smell + * (W) warning, for python specific problems + * (E) error, for probable bugs in the code * (F) fatal, if an error occurred which prevented pylint from doing further processing. ''', level=1) linter.add_help_section('Output status code', ''' -Pylint should leave with following status code: - * 0 if everything went fine - * 1 if a fatal message was issued - * 2 if an error message was issued - * 4 if a warning message was issued - * 8 if a refactor message was issued - * 16 if a convention message was issued - * 32 on usage error - +Pylint should leave with following status code: + * 0 if everything went fine + * 1 if a fatal message was issued + * 2 if an error message was issued + * 4 if a warning message was issued + * 8 if a refactor message was issued + * 16 if a convention message was issued + * 32 on usage error + status 1 to 16 will be bit-ORed so you can know which different categories has been issued by analysing pylint output status code ''', level=1) From 3c37b38fe71a391125abbf2cd5b10933c4a2f081 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sun, 12 Feb 2012 01:24:34 +0400 Subject: [PATCH 034/513] Refactor python documentation search. --- autoload/pymode/doc.vim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/autoload/pymode/doc.vim b/autoload/pymode/doc.vim index f1e4270b..ce64ecfd 100644 --- a/autoload/pymode/doc.vim +++ b/autoload/pymode/doc.vim @@ -2,6 +2,11 @@ fun! pymode#doc#Show(word) "{{{ if a:word == '' echoerr "No name/symbol under cursor!" else - call pymode#ShowCommand(g:pydoc . " " . escape(a:word, " ")) + call pymode#TempBuffer() + redi @"> + sil!py help(vim.eval('a:word')) + redi END + normal Pdd + wincmd p endif endfunction "}}} From 04e408e733db8189bec69d92d5cb07575be4bbe4 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sun, 12 Feb 2012 01:24:48 +0400 Subject: [PATCH 035/513] Refactor python code run --- autoload/pymode/run.vim | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index f06e8e7f..a8c9be68 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -1,5 +1,11 @@ " DESC: Save file if it modified and run python code -fun! pymode#run#Run() "{{{ +fun! pymode#run#Run(line1, line2) "{{{ if &modifiable && &modified | write | endif - call pymode#ShowCommand(g:python . " " . expand("%:p")) + let f = expand("%:p") + call pymode#TempBuffer() + redi @"> + exe "sil!py execfile('" . l:f . "')" + redi END + normal Pdd + wincmd p endfunction "}}} From dd05bfce5966eb569a91f7a78b3bcc6ca11ba92c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sun, 12 Feb 2012 01:32:19 +0400 Subject: [PATCH 036/513] Fix run and doc --- ftplugin/python/pymode.vim | 4 +++- plugin/pymode.vim | 21 +++++++-------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index a4e537d7..706c0c37 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -52,6 +52,7 @@ if g:pymode_doc " DESC: Set keys exe "nnoremap " g:pymode_doc_key ":call pymode#doc#Show(expand(''))" + exe "vnoremap " g:pymode_doc_key ":call pymode#doc#Show('')" endif @@ -115,10 +116,11 @@ endif if g:pymode_run " DESC: Set commands - command! -buffer -nargs=0 Pyrun call pymode#run#Run() + command! -buffer -nargs=0 -range=% Pyrun call pymode#run#Run(, ) " DESC: Set keys exe "nnoremap " g:pymode_run_key ":Pyrun" + exe "vnoremap " g:pymode_run_key ":Pyrun" endif diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 3ca8cceb..6e4832cd 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.5.7" +let g:pymode_version = "0.5.8" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version @@ -11,10 +11,11 @@ endif " DESC: Check python support if !has('python') echoerr expand(":t") . " required vim compiled with +python." - echoerr "Pymode rope, pylint and virtualenv plugins will be disabled." - let g:pymode_lint = 0 - let g:pymode_rope = 0 - let g:pymode_path = 0 + let g:pymode_lint = 0 + let g:pymode_rope = 0 + let g:pymode_path = 0 + let g:pymode_doc = 0 + let g:pymode_run = 0 let g:pymode_virtualenv = 0 endif @@ -179,10 +180,6 @@ endif if !pymode#Default("g:pymode_doc", 1) || g:pymode_doc - if !pymode#CheckProgram("pydoc", "or disable pymode_doc.") - let g:pymode_doc = 0 - endif - " OPTION: g:pymode_doc_key -- string. Key for show python documantation. call pymode#Default("g:pymode_doc_key", "K") @@ -216,11 +213,7 @@ endif if !pymode#Default("g:pymode_run", 1) || g:pymode_run - if !pymode#CheckProgram("python", "or disable pymode_run.") - let g:pymode_run = 0 - endif - - " OPTION: g:pymode_doc_key -- string. Key for show python documantation. + " OPTION: g:pymode_doc_key -- string. Key for show python documentation. call pymode#Default("g:pymode_run_key", "r") endif From dc47efffb8d6da8e2c7c5f755f4d49d6d539dfa7 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sun, 12 Feb 2012 12:35:34 +0400 Subject: [PATCH 037/513] Fix code run and documentation search for windows users --- autoload/pymode/doc.vim | 7 +++++-- autoload/pymode/run.vim | 5 ++--- plugin/pymode.vim | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/autoload/pymode/doc.vim b/autoload/pymode/doc.vim index ce64ecfd..f325a480 100644 --- a/autoload/pymode/doc.vim +++ b/autoload/pymode/doc.vim @@ -2,10 +2,13 @@ fun! pymode#doc#Show(word) "{{{ if a:word == '' echoerr "No name/symbol under cursor!" else - call pymode#TempBuffer() + py sys.stdout, _ = StringIO.StringIO(), sys.stdout + py help(vim.eval('a:word')) + py sys.stdout, out = _, sys.stdout.getvalue() redi @"> - sil!py help(vim.eval('a:word')) + sil!py print out redi END + call pymode#TempBuffer() normal Pdd wincmd p endif diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index a8c9be68..23fc6a9e 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -1,11 +1,10 @@ " DESC: Save file if it modified and run python code fun! pymode#run#Run(line1, line2) "{{{ if &modifiable && &modified | write | endif - let f = expand("%:p") - call pymode#TempBuffer() redi @"> - exe "sil!py execfile('" . l:f . "')" + sil!py execfile(vim.eval('expand("%s:p")')) redi END + call pymode#TempBuffer() normal Pdd wincmd p endfunction "}}} diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 6e4832cd..611c72f4 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -22,7 +22,7 @@ endif " DESC: Fix python path if !pymode#Default('g:pymode_path', 1) || g:pymode_path python << EOF -import sys, vim, os +import sys, vim, os, StringIO curpath = vim.eval("getcwd()") libpath = os.path.join(os.path.dirname(os.path.dirname( From e9529fe35a34a40c4e2213d517af8f51b26613ae Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sun, 12 Feb 2012 12:38:13 +0400 Subject: [PATCH 038/513] Version 0.5.8 --- Changelog.rst | 6 ++++++ README.rst | 11 ++++------- doc/pymode.txt | 8 -------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index f76aacb5..b812ce1c 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,12 @@ Changelog ========= +## 2012-02-12 0.5.8 +------------------- +* Fix pylint for Windows users +* Python documentation search running from Vim (delete g:pydoc option) +* Python code execution running from Vim (delete g:python option) + ## 2012-02-11 0.5.7 ------------------- * Fix 'g:pymode_lint_message' mode error diff --git a/README.rst b/README.rst index d3e655d2..bc17634c 100644 --- a/README.rst +++ b/README.rst @@ -28,11 +28,11 @@ See (old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my f Changelog ========= -## 2012-02-11 0.5.7 +## 2012-02-12 0.5.8 ------------------- -* Fix 'g:pymode_lint_message' mode error -* Fix breakpoints -* Fix python paths and virtualenv detection +* Fix pylint for Windows users +* Python documentation search running from Vim (delete g:pydoc option) +* Python code execution running from Vim (delete g:python option) Requirements @@ -109,9 +109,6 @@ Default values: :: " Key for show python documentation let g:pymode_doc_key = 'K' - " Executable command for documentation search - let g:pydoc = 'pydoc' - Run python code --------------- diff --git a/doc/pymode.txt b/doc/pymode.txt index 144add39..4fabcb38 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -47,8 +47,6 @@ PythonMode. These options should be set in your vimrc. |'pymode_doc_key'| Key for show documentation -|'pydoc'| Command for run pydoc - |'pymode_run'| Turns off the run code script |'pymode_run_key'| Key for run python code @@ -124,12 +122,6 @@ Default: 'K'. Set key for show python documentation. ------------------------------------------------------------------------------- - *'pydoc'* -Default: 'pydoc'. - -Set command for documentation search. - ------------------------------------------------------------------------------ *'pymode_run'* Values: 0 or 1. From ec24cfef96f1275a0cd6c5b15f6d670afa061e37 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 13 Feb 2012 20:40:34 +0400 Subject: [PATCH 039/513] Add pymode_lint_hold option. Prepare to release 0.6.0 --- Changelog.rst | 4 ++++ README.rst | 4 ++++ autoload/pymode/lint.vim | 2 +- doc/pymode.txt | 10 ++++++++++ plugin/pymode.vim | 6 +++++- 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index b812ce1c..b1949573 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,10 @@ Changelog ========= +## 2012-02-?? 0.6.0 +------------------- +* Add 'pymode_lint_hold' option + ## 2012-02-12 0.5.8 ------------------- * Fix pylint for Windows users diff --git a/README.rst b/README.rst index bc17634c..4e108782 100644 --- a/README.rst +++ b/README.rst @@ -153,6 +153,10 @@ Default values: :: " Auto jump on first error let g:pymode_lint_jump = 0 + " Hold cursor in current window + " when quickfix is open + let g:pymode_lint_hold = 0 + " Place error signs let g:pymode_lint_signs = 1 diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 8effa769..fa854602 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -21,7 +21,7 @@ function! pymode#lint#Check() call setqflist(b:qf_list, 'r') if g:pymode_lint_cwindow - call pymode#QuickfixOpen(0, 0, g:pymode_lint_maxheight, g:pymode_lint_minheight, g:pymode_lint_jump) + call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, g:pymode_lint_jump) endif if g:pymode_lint_signs diff --git a/doc/pymode.txt b/doc/pymode.txt index 4fabcb38..7b0f3e30 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -69,6 +69,8 @@ PythonMode. These options should be set in your vimrc. |'pymode_lint_jump'| Auto jump on first error +|'pymode_lint_hold'| Hold cursor in current window + |'pymode_lint_minheight'| Minimal height of pylint error window |'pymode_lint_maxheight'| Maximal height of pylint error window @@ -199,6 +201,14 @@ Default: 0. If this option is set to 0 then pylint not jump on first error. +------------------------------------------------------------------------------ + *'pymode_lint_hold'* +Values: 0 or 1. +Default: 0. + +If this option is set to 0 then pylint switch on quickfix window when it open +Not working when |'pymode_lint_jump'| enabled. + ------------------------------------------------------------------------------ *'pymode_lint_minheight'* Values: int diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 611c72f4..9ca1ebc0 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.5.8" +let g:pymode_version = "0.6.0" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version @@ -72,6 +72,10 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " OPTION: g:pymode_lint_jump -- int. Jump on first error. call pymode#Default("g:pymode_lint_jump", 0) + " OPTION: g:pymode_lint_hold -- int. Hold cursor on current window when + " quickfix open + call pymode#Default("g:pymode_lint_hold", 0) + " OPTION: g:pymode_lint_minheight -- int. Minimal height of pymode lint window call pymode#Default("g:pymode_lint_minheight", 3) From 168b990360bf94c5976497080575c614f357a9b2 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 29 Feb 2012 19:45:51 +0400 Subject: [PATCH 040/513] Fill qlist only if errors found. --- autoload/pymode.vim | 2 +- autoload/pymode/lint.vim | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index 7cd234ef..46a62e54 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -31,7 +31,7 @@ endfunction "}}} fun! pymode#PlaceSigns() "{{{ sign unplace * for item in filter(getqflist(), 'v:val.bufnr != ""') - execute printf('sign place 1 line=%d name=%s buffer=%d', item.lnum, item.type, item.bufnr) + execute printf('silent! sign place 1 line=%d name=%s buffer=%d', item.lnum, item.type, item.bufnr) endfor endfunction "}}} diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index fa854602..fb86ef14 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -10,6 +10,16 @@ function! pymode#lint#Check() endif exe "py ".g:pymode_lint_checker."()" + if len(b:qf_list) || (exists('b:errors') && len(b:errors)) + + call setqflist(b:qf_list, 'r') + + if g:pymode_lint_cwindow + call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, g:pymode_lint_jump) + endif + + endif + if g:pymode_lint_message let b:errors = {} for v in b:qf_list @@ -18,12 +28,6 @@ function! pymode#lint#Check() call pymode#lint#show_errormessage() endif - call setqflist(b:qf_list, 'r') - - if g:pymode_lint_cwindow - call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, g:pymode_lint_jump) - endif - if g:pymode_lint_signs call pymode#PlaceSigns() endif From d46538db9d8b02e986e794214591da2454909342 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 29 Feb 2012 19:46:06 +0400 Subject: [PATCH 041/513] Fix ipdb loading. --- plugin/pymode.vim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 9ca1ebc0..cc60dae3 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -198,8 +198,9 @@ if !pymode#Default("g:pymode_breakpoint", 1) || g:pymode_breakpoint if !pymode#Default("g:pymode_breakpoint_cmd", "import ipdb; ipdb.set_trace() ### XXX BREAKPOINT") && has("python") python << EOF +from imp import find_module try: - import ipdb + find_module('ipdb') except ImportError: vim.command('let g:pymode_breakpoint_cmd = "import pdb; pdb.set_trace() ### XXX BREAKPOINT"') EOF From 107a26190229e16f2b09824def378d5c6ed9b96c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 02:56:31 +0400 Subject: [PATCH 042/513] Improve load speed. --- autoload/pymode/lint.vim | 3 +- plugin/pymode.vim | 84 ++++------------------------------------ 2 files changed, 10 insertions(+), 77 deletions(-) diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index fb86ef14..3212b647 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -8,7 +8,8 @@ function! pymode#lint#Check() return 0 endtry endif - exe "py ".g:pymode_lint_checker."()" + + py check_file() if len(b:qf_list) || (exists('b:errors') && len(b:errors)) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index cc60dae3..f7ae66e6 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -82,6 +82,12 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " OPTION: g:pymode_lint_maxheight -- int. Maximal height of pymode lint window call pymode#Default("g:pymode_lint_maxheight", 6) + " OPTION: g:pymode_lint_ignore -- string. Skip errors and warnings (e.g. E4,W) + call pymode#Default("g:pymode_lint_ignore", "E501") + + " OPTION: g:pymode_lint_select -- string. Select errors and warnings (e.g. E4,W) + call pymode#Default("g:pymode_lint_select", "") + " OPTION: g:pymode_lint_signs -- bool. Place error signs if !pymode#Default("g:pymode_lint_signs", 1) || g:pymode_lint_signs @@ -99,82 +105,8 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint let g:pymode_lint_config = expand(":p:h:h") . "/pylint.ini" endif -python << EOF -import os -import StringIO -import _ast -import re - -from logilab.astng.builder import MANAGER -from pylint import lint, checkers -from pyflakes import checker - - -# Pylint setup -linter = lint.PyLinter() -pylint_re = re.compile('^(?:.:)?[^:]+:(\d+): \[([EWRCI]+)[^\]]*\] (.*)$') - -checkers.initialize(linter) -linter.load_file_configuration(vim.eval("g:pymode_lint_config")) -linter.set_option("output-format", "parseable") -linter.set_option("reports", 0) - -# Pyflakes setup - -# Pylint check -def pylint(): - filename = vim.current.buffer.name - MANAGER.astng_cache.clear() - linter.reporter.out = StringIO.StringIO() - linter.check(filename) - qf = [] - for w in linter.reporter.out.getvalue().split('\n'): - test = pylint_re.match(w) - test and qf.append(dict( - filename = filename, - bufnr = vim.current.buffer.number, - lnum = test.group(1), - type = test.group(2), - text = test.group(3).replace("'", "\""), - )) - vim.command('let b:qf_list = %s' % repr(qf)) - -# Pyflakes check -def pyflakes(): - filename = vim.current.buffer.name - codeString = file(filename, 'U').read() + '\n' - qf = [] - try: - tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST) - - except SyntaxError, value: - msg = value.args[0] - if codeString is None: - vim.command('echoerr "%s: problem decoding source"' % filename) - else: - lineno, _, text = value.lineno, value.offset, value.text - qf.append(dict( - filename = filename, - bufnr = vim.current.buffer.number, - lnum = str(lineno), - text = msg, - type = 'E' - )) - - else: - w = checker.Checker(tree, filename) - w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) - for w in w.messages: - qf.append(dict( - filename = filename, - bufnr = vim.current.buffer.number, - lnum = str(w.lineno), - text = w.message % w.message_args, - type = 'E' - )) - - vim.command('let b:qf_list = %s' % repr(qf)) -EOF + py from pymode import check_file + endif " }}} From 3a8d6dc03d121e27f3bddf38ea2f57c5e3fa227d Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 02:56:43 +0400 Subject: [PATCH 043/513] Add code messages. --- pylibs/pyflakes/messages.py | 88 +++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/pylibs/pyflakes/messages.py b/pylibs/pyflakes/messages.py index 73bf4cc3..56beb5cd 100644 --- a/pylibs/pyflakes/messages.py +++ b/pylibs/pyflakes/messages.py @@ -13,74 +13,83 @@ def __str__(self): class UnusedImport(Message): - message = '%r imported but unused' - def __init__(self, filename, loc, name): - Message.__init__(self, filename, loc, use_column=False) + message = 'W402 %r imported but unused' + + def __init__(self, filename, lineno, name): + Message.__init__(self, filename, lineno) self.message_args = (name,) class RedefinedWhileUnused(Message): - message = 'redefinition of unused %r from line %r' - def __init__(self, filename, loc, name, orig_loc): - Message.__init__(self, filename, loc) - self.message_args = (name, orig_loc.lineno) + message = 'W801 redefinition of unused %r from line %r' + + def __init__(self, filename, lineno, name, orig_lineno): + Message.__init__(self, filename, lineno) + self.message_args = (name, orig_lineno) class ImportShadowedByLoopVar(Message): - message = 'import %r from line %r shadowed by loop variable' - def __init__(self, filename, loc, name, orig_loc): - Message.__init__(self, filename, loc) - self.message_args = (name, orig_loc.lineno) + message = 'W403 import %r from line %r shadowed by loop variable' + + def __init__(self, filename, lineno, name, orig_lineno): + Message.__init__(self, filename, lineno) + self.message_args = (name, orig_lineno) class ImportStarUsed(Message): - message = "'from %s import *' used; unable to detect undefined names" - def __init__(self, filename, loc, modname): - Message.__init__(self, filename, loc) + message = "W404 'from %s import *' used; unable to detect undefined names" + + def __init__(self, filename, lineno, modname): + Message.__init__(self, filename, lineno) self.message_args = (modname,) class UndefinedName(Message): - message = 'undefined name %r' - def __init__(self, filename, loc, name): - Message.__init__(self, filename, loc) - self.message_args = (name,) + message = 'W802 undefined name %r' + def __init__(self, filename, lineno, name): + Message.__init__(self, filename, lineno) + self.message_args = (name,) class UndefinedExport(Message): - message = 'undefined name %r in __all__' - def __init__(self, filename, loc, name): - Message.__init__(self, filename, loc) - self.message_args = (name,) + message = 'W803 undefined name %r in __all__' + def __init__(self, filename, lineno, name): + Message.__init__(self, filename, lineno) + self.message_args = (name,) class UndefinedLocal(Message): - message = "local variable %r (defined in enclosing scope on line %r) referenced before assignment" - def __init__(self, filename, loc, name, orig_loc): - Message.__init__(self, filename, loc) - self.message_args = (name, orig_loc.lineno) + message = "W804 local variable %r (defined in enclosing scope on line " \ + "%r) referenced before assignment" + + def __init__(self, filename, lineno, name, orig_lineno): + Message.__init__(self, filename, lineno) + self.message_args = (name, orig_lineno) class DuplicateArgument(Message): - message = 'duplicate argument %r in function definition' - def __init__(self, filename, loc, name): - Message.__init__(self, filename, loc) + message = 'W805 duplicate argument %r in function definition' + + def __init__(self, filename, lineno, name): + Message.__init__(self, filename, lineno) self.message_args = (name,) class RedefinedFunction(Message): - message = 'redefinition of function %r from line %r' - def __init__(self, filename, loc, name, orig_loc): - Message.__init__(self, filename, loc) - self.message_args = (name, orig_loc.lineno) + message = 'W806 redefinition of function %r from line %r' + + def __init__(self, filename, lineno, name, orig_lineno): + Message.__init__(self, filename, lineno) + self.message_args = (name, orig_lineno) class LateFutureImport(Message): - message = 'future import(s) %r after other statements' - def __init__(self, filename, loc, names): - Message.__init__(self, filename, loc) + message = 'W405 future import(s) %r after other statements' + + def __init__(self, filename, lineno, names): + Message.__init__(self, filename, lineno) self.message_args = (names,) @@ -90,7 +99,8 @@ class UnusedVariable(Message): used. """ - message = 'local variable %r is assigned to but never used' - def __init__(self, filename, loc, names): - Message.__init__(self, filename, loc) + message = 'W806 local variable %r is assigned to but never used' + + def __init__(self, filename, lineno, names): + Message.__init__(self, filename, lineno) self.message_args = (names,) From a42ddc611932e21f43630b9442fd9723e4a3b918 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 02:59:17 +0400 Subject: [PATCH 044/513] Add information about new options and checkers. --- Changelog.rst | 5 +++++ README.rst | 17 ++++++++++++++--- doc/pymode.txt | 34 +++++++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index b1949573..7d7c843f 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -4,6 +4,11 @@ Changelog ## 2012-02-?? 0.6.0 ------------------- * Add 'pymode_lint_hold' option +* Improve pymode loading speed +* Add pep8, mccabe lint checkers +* Now g:pymode_lint_checker can have many values + Ex. "pep8,pyflakes,mccabe" +* Add 'pymode_lint_ignore' and 'pymode_lint_select' options ## 2012-02-12 0.5.8 ------------------- diff --git a/README.rst b/README.rst index 4e108782..afb66047 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ Python-mode, Python in VIM ########################## -Python-mode is a vim plugin that allows you to use the pylint_, rope_, pydoc_, pyflakes_ libraries in vim to provide +Python-mode is a vim plugin that allows you to use the pylint_, rope_, pydoc_, pyflakes_, pep8_, mccabe_ libraries in vim to provide features like python code looking for bugs, refactoring and some other useful things. This plugin allow you create python code in vim very easily. @@ -17,6 +17,7 @@ There is no need to install the pylint_, rope_ or any used python library on you - Go to definition - Powerful customization - Virtualenv support +- Many linters (pylint_, pyflakes_, ...) that can be run simultaneously - And more... See (old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my first screencast) @@ -130,10 +131,18 @@ Default values: :: " Load pylint code plugin let g:pymode_lint = 1 - " Switch pylint or pyflakes code checker - " values (pylint, pyflakes) + " Switch pylint, pyflakes, pep8, mccabe code-checkers + " Can have multiply values "pep8,pyflakes,mcccabe" let g:pymode_lint_checker = "pylint" + " Skip errors and warnings + " E.g. "E501,W002", "E2,W" (Skip all Warnings and Errors startswith E2) and etc + let g:pymode_lint_ignore = "E501" + + " Select errors and warnings + " E.g. "E4,W" + let g:pymode_lint_select = "" + " Run linter on the fly let g:pymode_lint_onfly = 0 @@ -430,3 +439,5 @@ My address is here: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Kle .. _rope: http://rope.sourceforge.net/ .. _pydoc: http://docs.python.org/library/pydoc.html .. _pathogen: https://github.com/tpope/vim-pathogen +.. _pep8: http://pypi.python.org/pypi/pep8 +.. _mccabe: http://en.wikipedia.org/wiki/Cyclomatic_complexity diff --git a/doc/pymode.txt b/doc/pymode.txt index 7b0f3e30..08826433 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -53,7 +53,11 @@ PythonMode. These options should be set in your vimrc. |'pymode_lint'| Turns off pylint script -|'pymode_lint_checker'| Switch code checkers (pylint, pyflakes) +|'pymode_lint_checker'| Switch code checkers (pylint, pyflakes, pep8, mccabe) + +|'pymode_lint_ignore'| Skip errors and warnings + +|'pymode_lint_select'| Select errors and warnings |'pymode_lint_onfly'| Run linter on the fly @@ -146,10 +150,32 @@ If this option is set to 0 then pylint script is disabled. ------------------------------------------------------------------------------ *'pymode_lint_checker'* -Values: "pylint" or "pyflakes" +Values: "pylint", "pyflakes", "pep8", "mccabe" + You can set many checkers. E.g. "pyflakes,pep8,mccabe" ~ + Default: "pylint". -This option set code checker. +This option set code checkers. + +------------------------------------------------------------------------------ + *'pymode_lint_ignore'* +Values: IDs of errors, separated by commas or empty string + E.g. "E501,W002", "E2,W" (Skip all Warnings and Errors startswith E2) and etc ~ + +Default: "E501". + +Skip errors and warnings. +See also: |'pymode_lint_select'|, |'pymode_lint_config'| + +------------------------------------------------------------------------------ + *'pymode_lint_select'* +Values: IDs of errors, separated by commas or empty string + E.g. "W002,C" Force W002 and all C-ids ~ + +Default: "". + +Select errors and warnings. +See also: |'pymode_lint_ignore'|, |'pymode_lint_config'| ------------------------------------------------------------------------------ *'pymode_lint_onfly'* @@ -166,6 +192,8 @@ Default: "$HOME/.pylintrc" If this option is set path to pylint configuration. If configuration not found uses file 'pylintrc' from python-mode sources. +See also: |'pymode_lint_ignore'|, |'pymode_lint_select'| + ------------------------------------------------------------------------------ *'pymode_lint_write'* Values: 0 or 1. From 553369763112802c63d94ec9f6c8b7e5c194587d Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 02:59:48 +0400 Subject: [PATCH 045/513] Add new checkers and now python-mode allowed use many checkers in same time. --- pylibs/mccabe.py | 293 ++++++++++ pylibs/pep8.py | 1369 ++++++++++++++++++++++++++++++++++++++++++++++ pylibs/pymode.py | 165 ++++++ 3 files changed, 1827 insertions(+) create mode 100644 pylibs/mccabe.py create mode 100644 pylibs/pep8.py create mode 100644 pylibs/pymode.py diff --git a/pylibs/mccabe.py b/pylibs/mccabe.py new file mode 100644 index 00000000..96fb6e72 --- /dev/null +++ b/pylibs/mccabe.py @@ -0,0 +1,293 @@ +""" Meager code path measurement tool. + Ned Batchelder + http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html + MIT License. +""" +try: + from compiler import parse # NOQA + iter_child_nodes = None # NOQA +except ImportError: + from ast import parse, iter_child_nodes # NOQA + +import optparse +import sys +from collections import defaultdict + +WARNING_CODE = "W901" + + +class ASTVisitor: + + VERBOSE = 0 + + def __init__(self): + self.node = None + self._cache = {} + + def default(self, node, *args): + if hasattr(node, 'getChildNodes'): + children = node.getChildNodes() + else: + children = iter_child_nodes(node) + + for child in children: + self.dispatch(child, *args) + + def dispatch(self, node, *args): + self.node = node + klass = node.__class__ + meth = self._cache.get(klass) + if meth is None: + className = klass.__name__ + meth = getattr(self.visitor, 'visit' + className, self.default) + self._cache[klass] = meth + + return meth(node, *args) + + def preorder(self, tree, visitor, *args): + """Do preorder walk of tree using visitor""" + self.visitor = visitor + visitor.visit = self.dispatch + self.dispatch(tree, *args) # XXX *args make sense? + + +class PathNode: + def __init__(self, name, look="circle"): + self.name = name + self.look = look + + def to_dot(self): + print('node [shape=%s,label="%s"] %d;' % \ + (self.look, self.name, self.dot_id())) + + def dot_id(self): + return id(self) + + +class PathGraph: + def __init__(self, name, entity, lineno): + self.name = name + self.entity = entity + self.lineno = lineno + self.nodes = defaultdict(list) + + def connect(self, n1, n2): + self.nodes[n1].append(n2) + + def to_dot(self): + print('subgraph {') + for node in self.nodes: + node.to_dot() + for node, nexts in self.nodes.items(): + for next in nexts: + print('%s -- %s;' % (node.dot_id(), next.dot_id())) + print('}') + + def complexity(self): + """ Return the McCabe complexity for the graph. + V-E+2 + """ + num_edges = sum([len(n) for n in self.nodes.values()]) + num_nodes = len(self.nodes) + return num_edges - num_nodes + 2 + + +class PathGraphingAstVisitor(ASTVisitor): + """ A visitor for a parsed Abstract Syntax Tree which finds executable + statements. + """ + + def __init__(self): + ASTVisitor.__init__(self) + self.classname = "" + self.graphs = {} + self.reset() + + def reset(self): + self.graph = None + self.tail = None + + def visitFunction(self, node): + + if self.classname: + entity = '%s%s' % (self.classname, node.name) + else: + entity = node.name + + name = '%d:1: %r' % (node.lineno, entity) + + if self.graph is not None: + # closure + pathnode = self.appendPathNode(name) + self.tail = pathnode + self.default(node) + bottom = PathNode("", look='point') + self.graph.connect(self.tail, bottom) + self.graph.connect(pathnode, bottom) + self.tail = bottom + else: + self.graph = PathGraph(name, entity, node.lineno) + pathnode = PathNode(name) + self.tail = pathnode + self.default(node) + self.graphs["%s%s" % (self.classname, node.name)] = self.graph + self.reset() + + visitFunctionDef = visitFunction + + def visitClass(self, node): + old_classname = self.classname + self.classname += node.name + "." + self.default(node) + self.classname = old_classname + + def appendPathNode(self, name): + if not self.tail: + return + pathnode = PathNode(name) + self.graph.connect(self.tail, pathnode) + self.tail = pathnode + return pathnode + + def visitSimpleStatement(self, node): + if node.lineno is None: + lineno = 0 + else: + lineno = node.lineno + name = "Stmt %d" % lineno + self.appendPathNode(name) + + visitAssert = visitAssign = visitAssTuple = visitPrint = \ + visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ + visitPass = visitDiscard = visitGlobal = visitReturn = \ + visitSimpleStatement + + def visitLoop(self, node): + name = "Loop %d" % node.lineno + + if self.graph is None: + # global loop + self.graph = PathGraph(name, name, node.lineno) + pathnode = PathNode(name) + self.tail = pathnode + self.default(node) + self.graphs["%s%s" % (self.classname, name)] = self.graph + self.reset() + else: + pathnode = self.appendPathNode(name) + self.tail = pathnode + self.default(node.body) + bottom = PathNode("", look='point') + self.graph.connect(self.tail, bottom) + self.graph.connect(pathnode, bottom) + self.tail = bottom + + # TODO: else clause in node.else_ + + visitFor = visitWhile = visitLoop + + def visitIf(self, node): + name = "If %d" % node.lineno + pathnode = self.appendPathNode(name) + if not pathnode: + return # TODO: figure out what to do with if's outside def's. + loose_ends = [] + for t, n in node.tests: + self.tail = pathnode + self.default(n) + loose_ends.append(self.tail) + if node.else_: + self.tail = pathnode + self.default(node.else_) + loose_ends.append(self.tail) + else: + loose_ends.append(pathnode) + bottom = PathNode("", look='point') + for le in loose_ends: + self.graph.connect(le, bottom) + self.tail = bottom + + # TODO: visitTryExcept + # TODO: visitTryFinally + # TODO: visitWith + + # XXX todo: determine which ones can add to the complexity + # py2 + # TODO: visitStmt + # TODO: visitAssName + # TODO: visitCallFunc + # TODO: visitConst + + # py3 + # TODO: visitStore + # TODO: visitCall + # TODO: visitLoad + # TODO: visitNum + # TODO: visitarguments + # TODO: visitExpr + + +def get_code_complexity(code, min=7, filename='stdin'): + complex = [] + try: + ast = parse(code) + except AttributeError: + e = sys.exc_info()[1] + sys.stderr.write("Unable to parse %s: %s\n" % (filename, e)) + return 0 + + visitor = PathGraphingAstVisitor() + visitor.preorder(ast, visitor) + for graph in visitor.graphs.values(): + if graph is None: + # ? + continue + if graph.complexity() >= min: + complex.append(dict( + type = 'W', + lnum = graph.lineno, + text = '%s %r is too complex (%d)' % ( + WARNING_CODE, + graph.entity, + graph.complexity(), + ) + )) + + return complex + + +def get_module_complexity(module_path, min=7): + """Returns the complexity of a module""" + code = open(module_path, "rU").read() + '\n\n' + return get_code_complexity(code, min, filename=module_path) + + +def main(argv): + opar = optparse.OptionParser() + opar.add_option("-d", "--dot", dest="dot", + help="output a graphviz dot file", action="store_true") + opar.add_option("-m", "--min", dest="min", + help="minimum complexity for output", type="int", + default=2) + + options, args = opar.parse_args(argv) + + text = open(args[0], "rU").read() + '\n\n' + ast = parse(text) + visitor = PathGraphingAstVisitor() + visitor.preorder(ast, visitor) + + if options.dot: + print('graph {') + for graph in visitor.graphs.values(): + if graph.complexity() >= options.min: + graph.to_dot() + print('}') + else: + for graph in visitor.graphs.values(): + if graph.complexity() >= options.min: + print(graph.name, graph.complexity()) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/pylibs/pep8.py b/pylibs/pep8.py new file mode 100644 index 00000000..fb9964fb --- /dev/null +++ b/pylibs/pep8.py @@ -0,0 +1,1369 @@ +#!/usr/bin/python +# flake8: noqa +# pep8.py - Check Python source code formatting, according to PEP 8 +# Copyright (C) 2006 Johann C. Rocholl +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +Check Python source code formatting, according to PEP 8: +http://www.python.org/dev/peps/pep-0008/ + +For usage and a list of options, try this: +$ python pep8.py -h + +This program and its regression test suite live here: +http://github.com/jcrocholl/pep8 + +Groups of errors and warnings: +E errors +W warnings +100 indentation +200 whitespace +300 blank lines +400 imports +500 line length +600 deprecation +700 statements + +You can add checks to this program by writing plugins. Each plugin is +a simple function that is called for each line of source code, either +physical or logical. + +Physical line: +- Raw line of text from the input file. + +Logical line: +- Multi-line statements converted to a single line. +- Stripped left and right. +- Contents of strings replaced with 'xxx' of same length. +- Comments removed. + +The check function requests physical or logical lines by the name of +the first argument: + +def maximum_line_length(physical_line) +def extraneous_whitespace(logical_line) +def blank_lines(logical_line, blank_lines, indent_level, line_number) + +The last example above demonstrates how check plugins can request +additional information with extra arguments. All attributes of the +Checker object are available. Some examples: + +lines: a list of the raw lines from the input file +tokens: the tokens that contribute to this logical line +line_number: line number in the input file +blank_lines: blank lines before this one +indent_char: first indentation character in this file (' ' or '\t') +indent_level: indentation (with tabs expanded to multiples of 8) +previous_indent_level: indentation on previous line +previous_logical: previous logical line + +The docstring of each check function shall be the relevant part of +text from PEP 8. It is printed if the user enables --show-pep8. +Several docstrings contain examples directly from the PEP 8 document. + +Okay: spam(ham[1], {eggs: 2}) +E201: spam( ham[1], {eggs: 2}) + +These examples are verified automatically when pep8.py is run with the +--doctest option. You can add examples for your own check functions. +The format is simple: "Okay" or error/warning code followed by colon +and space, the rest of the line is example source code. If you put 'r' +before the docstring, you can use \n for newline, \t for tab and \s +for space. + +""" +from flake8 import __version__ as flake8_version +from flake8.pyflakes import __version__ as pep8_version + +__version__ = '0.6.1' + +import os +import sys +import re +import time +import inspect +import keyword +import tokenize +from optparse import OptionParser +from fnmatch import fnmatch +try: + frozenset +except NameError: + from sets import ImmutableSet as frozenset + +from flake8.util import skip_line + +DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git' +DEFAULT_IGNORE = 'E24' +MAX_LINE_LENGTH = 79 + +INDENT_REGEX = re.compile(r'([ \t]*)') +RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*(,)') +QUOTED_REGEX = re.compile(r"""([""'])(?:(?=(\\?))\2.)*?\1""") +SELFTEST_REGEX = re.compile(r'(Okay|[EW]\d{3}):\s(.*)') +ERRORCODE_REGEX = re.compile(r'[EW]\d{3}') +DOCSTRING_REGEX = re.compile(r'u?r?["\']') +WHITESPACE_AROUND_OPERATOR_REGEX = \ + re.compile('([^\w\s]*)\s*(\t| )\s*([^\w\s]*)') +EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]') +WHITESPACE_AROUND_NAMED_PARAMETER_REGEX = \ + re.compile(r'[()]|\s=[^=]|[^=!<>]=\s') + + +WHITESPACE = ' \t' + +BINARY_OPERATORS = frozenset(['**=', '*=', '+=', '-=', '!=', '<>', + '%=', '^=', '&=', '|=', '==', '/=', '//=', '<=', '>=', '<<=', '>>=', + '%', '^', '&', '|', '=', '/', '//', '<', '>', '<<']) +UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) +OPERATORS = BINARY_OPERATORS | UNARY_OPERATORS +SKIP_TOKENS = frozenset([tokenize.COMMENT, tokenize.NL, tokenize.INDENT, + tokenize.DEDENT, tokenize.NEWLINE]) +E225NOT_KEYWORDS = (frozenset(keyword.kwlist + ['print']) - + frozenset(['False', 'None', 'True'])) +BENCHMARK_KEYS = ('directories', 'files', 'logical lines', 'physical lines') + +options = None +args = None + + +############################################################################## +# Plugins (check functions) for physical lines +############################################################################## + + +def tabs_or_spaces(physical_line, indent_char): + r""" + Never mix tabs and spaces. + + The most popular way of indenting Python is with spaces only. The + second-most popular way is with tabs only. Code indented with a mixture + of tabs and spaces should be converted to using spaces exclusively. When + invoking the Python command line interpreter with the -t option, it issues + warnings about code that illegally mixes tabs and spaces. When using -tt + these warnings become errors. These options are highly recommended! + + Okay: if a == 0:\n a = 1\n b = 1 + E101: if a == 0:\n a = 1\n\tb = 1 + """ + indent = INDENT_REGEX.match(physical_line).group(1) + for offset, char in enumerate(indent): + if char != indent_char: + return offset, "E101 indentation contains mixed spaces and tabs" + + +def tabs_obsolete(physical_line): + r""" + For new projects, spaces-only are strongly recommended over tabs. Most + editors have features that make this easy to do. + + Okay: if True:\n return + W191: if True:\n\treturn + """ + indent = INDENT_REGEX.match(physical_line).group(1) + if indent.count('\t'): + return indent.index('\t'), "W191 indentation contains tabs" + + +def trailing_whitespace(physical_line): + r""" + JCR: Trailing whitespace is superfluous. + FBM: Except when it occurs as part of a blank line (i.e. the line is + nothing but whitespace). According to Python docs[1] a line with only + whitespace is considered a blank line, and is to be ignored. However, + matching a blank line to its indentation level avoids mistakenly + terminating a multi-line statement (e.g. class declaration) when + pasting code into the standard Python interpreter. + + [1] http://docs.python.org/reference/lexical_analysis.html#blank-lines + + The warning returned varies on whether the line itself is blank, for easier + filtering for those who want to indent their blank lines. + + Okay: spam(1) + W291: spam(1)\s + W293: class Foo(object):\n \n bang = 12 + """ + physical_line = physical_line.rstrip('\n') # chr(10), newline + physical_line = physical_line.rstrip('\r') # chr(13), carriage return + physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L + stripped = physical_line.rstrip() + if physical_line != stripped: + if stripped: + return len(stripped), "W291 trailing whitespace" + else: + return 0, "W293 blank line contains whitespace" + + +def trailing_blank_lines(physical_line, lines, line_number): + r""" + JCR: Trailing blank lines are superfluous. + + Okay: spam(1) + W391: spam(1)\n + """ + if physical_line.strip() == '' and line_number == len(lines): + return 0, "W391 blank line at end of file" + + +def missing_newline(physical_line): + """ + JCR: The last line should have a newline. + """ + if physical_line.rstrip() == physical_line: + return len(physical_line), "W292 no newline at end of file" + + +def maximum_line_length(physical_line): + """ + Limit all lines to a maximum of 79 characters. + + There are still many devices around that are limited to 80 character + lines; plus, limiting windows to 80 characters makes it possible to have + several windows side-by-side. The default wrapping on such devices looks + ugly. Therefore, please limit all lines to a maximum of 79 characters. + For flowing long blocks of text (docstrings or comments), limiting the + length to 72 characters is recommended. + """ + line = physical_line.rstrip() + length = len(line) + if length > MAX_LINE_LENGTH: + try: + # The line could contain multi-byte characters + if not hasattr(line, 'decode'): # Python 3 + line = line.encode('latin-1') + length = len(line.decode('utf-8')) + except UnicodeDecodeError: + pass + if length > MAX_LINE_LENGTH: + return MAX_LINE_LENGTH, "E501 line too long (%d characters)" % length + + +############################################################################## +# Plugins (check functions) for logical lines +############################################################################## + + +def blank_lines(logical_line, blank_lines, indent_level, line_number, + previous_logical, previous_indent_level, + blank_lines_before_comment): + r""" + Separate top-level function and class definitions with two blank lines. + + Method definitions inside a class are separated by a single blank line. + + Extra blank lines may be used (sparingly) to separate groups of related + functions. Blank lines may be omitted between a bunch of related + one-liners (e.g. a set of dummy implementations). + + Use blank lines in functions, sparingly, to indicate logical sections. + + Okay: def a():\n pass\n\n\ndef b():\n pass + Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass + + E301: class Foo:\n b = 0\n def bar():\n pass + E302: def a():\n pass\n\ndef b(n):\n pass + E303: def a():\n pass\n\n\n\ndef b(n):\n pass + E303: def a():\n\n\n\n pass + E304: @decorator\n\ndef a():\n pass + """ + if line_number == 1: + return # Don't expect blank lines before the first line + max_blank_lines = max(blank_lines, blank_lines_before_comment) + if previous_logical.startswith('@'): + if max_blank_lines: + return 0, "E304 blank lines found after function decorator" + elif max_blank_lines > 2 or (indent_level and max_blank_lines == 2): + return 0, "E303 too many blank lines (%d)" % max_blank_lines + elif (logical_line.startswith('def ') or + logical_line.startswith('class ') or + logical_line.startswith('@')): + if indent_level: + if not (max_blank_lines or previous_indent_level < indent_level or + DOCSTRING_REGEX.match(previous_logical)): + return 0, "E301 expected 1 blank line, found 0" + elif max_blank_lines != 2: + return 0, "E302 expected 2 blank lines, found %d" % max_blank_lines + + +def extraneous_whitespace(logical_line): + """ + Avoid extraneous whitespace in the following situations: + + - Immediately inside parentheses, brackets or braces. + + - Immediately before a comma, semicolon, or colon. + + Okay: spam(ham[1], {eggs: 2}) + E201: spam( ham[1], {eggs: 2}) + E201: spam(ham[ 1], {eggs: 2}) + E201: spam(ham[1], { eggs: 2}) + E202: spam(ham[1], {eggs: 2} ) + E202: spam(ham[1 ], {eggs: 2}) + E202: spam(ham[1], {eggs: 2 }) + + E203: if x == 4: print x, y; x, y = y , x + E203: if x == 4: print x, y ; x, y = y, x + E203: if x == 4 : print x, y; x, y = y, x + """ + line = logical_line + for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line): + text = match.group() + char = text.strip() + found = match.start() + if text == char + ' ' and char in '([{': + return found + 1, "E201 whitespace after '%s'" % char + if text == ' ' + char and line[found - 1] != ',': + if char in '}])': + return found, "E202 whitespace before '%s'" % char + if char in ',;:': + return found, "E203 whitespace before '%s'" % char + + +def missing_whitespace(logical_line): + """ + JCR: Each comma, semicolon or colon should be followed by whitespace. + + Okay: [a, b] + Okay: (3,) + Okay: a[1:4] + Okay: a[:4] + Okay: a[1:] + Okay: a[1:4:2] + E231: ['a','b'] + E231: foo(bar,baz) + """ + line = logical_line + for index in range(len(line) - 1): + char = line[index] + if char in ',;:' and line[index + 1] not in WHITESPACE: + before = line[:index] + if char == ':' and before.count('[') > before.count(']'): + continue # Slice syntax, no space required + if char == ',' and line[index + 1] == ')': + continue # Allow tuple with only one element: (3,) + return index, "E231 missing whitespace after '%s'" % char + + +def indentation(logical_line, previous_logical, indent_char, + indent_level, previous_indent_level): + r""" + Use 4 spaces per indentation level. + + For really old code that you don't want to mess up, you can continue to + use 8-space tabs. + + Okay: a = 1 + Okay: if a == 0:\n a = 1 + E111: a = 1 + + Okay: for item in items:\n pass + E112: for item in items:\npass + + Okay: a = 1\nb = 2 + E113: a = 1\n b = 2 + """ + if indent_char == ' ' and indent_level % 4: + return 0, "E111 indentation is not a multiple of four" + indent_expect = previous_logical.endswith(':') + if indent_expect and indent_level <= previous_indent_level: + return 0, "E112 expected an indented block" + if indent_level > previous_indent_level and not indent_expect: + return 0, "E113 unexpected indentation" + + +def whitespace_before_parameters(logical_line, tokens): + """ + Avoid extraneous whitespace in the following situations: + + - Immediately before the open parenthesis that starts the argument + list of a function call. + + - Immediately before the open parenthesis that starts an indexing or + slicing. + + Okay: spam(1) + E211: spam (1) + + Okay: dict['key'] = list[index] + E211: dict ['key'] = list[index] + E211: dict['key'] = list [index] + """ + prev_type = tokens[0][0] + prev_text = tokens[0][1] + prev_end = tokens[0][3] + for index in range(1, len(tokens)): + token_type, text, start, end, line = tokens[index] + if (token_type == tokenize.OP and + text in '([' and + start != prev_end and + (prev_type == tokenize.NAME or prev_text in '}])') and + # Syntax "class A (B):" is allowed, but avoid it + (index < 2 or tokens[index - 2][1] != 'class') and + # Allow "return (a.foo for a in range(5))" + (not keyword.iskeyword(prev_text))): + return prev_end, "E211 whitespace before '%s'" % text + prev_type = token_type + prev_text = text + prev_end = end + + +def whitespace_around_operator(logical_line): + """ + Avoid extraneous whitespace in the following situations: + + - More than one space around an assignment (or other) operator to + align it with another. + + Okay: a = 12 + 3 + E221: a = 4 + 5 + E222: a = 4 + 5 + E223: a = 4\t+ 5 + E224: a = 4 +\t5 + """ + for match in WHITESPACE_AROUND_OPERATOR_REGEX.finditer(logical_line): + before, whitespace, after = match.groups() + tab = whitespace == '\t' + offset = match.start(2) + if before in OPERATORS: + return offset, (tab and "E224 tab after operator" or + "E222 multiple spaces after operator") + elif after in OPERATORS: + return offset, (tab and "E223 tab before operator" or + "E221 multiple spaces before operator") + + +def missing_whitespace_around_operator(logical_line, tokens): + r""" + - Always surround these binary operators with a single space on + either side: assignment (=), augmented assignment (+=, -= etc.), + comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not), + Booleans (and, or, not). + + - Use spaces around arithmetic operators. + + Okay: i = i + 1 + Okay: submitted += 1 + Okay: x = x * 2 - 1 + Okay: hypot2 = x * x + y * y + Okay: c = (a + b) * (a - b) + Okay: foo(bar, key='word', *args, **kwargs) + Okay: baz(**kwargs) + Okay: negative = -1 + Okay: spam(-1) + Okay: alpha[:-i] + Okay: if not -5 < x < +5:\n pass + Okay: lambda *args, **kw: (args, kw) + + E225: i=i+1 + E225: submitted +=1 + E225: x = x*2 - 1 + E225: hypot2 = x*x + y*y + E225: c = (a+b) * (a-b) + E225: c = alpha -4 + E225: z = x **y + """ + parens = 0 + need_space = False + prev_type = tokenize.OP + prev_text = prev_end = None + for token_type, text, start, end, line in tokens: + if token_type in (tokenize.NL, tokenize.NEWLINE, tokenize.ERRORTOKEN): + # ERRORTOKEN is triggered by backticks in Python 3000 + continue + if text in ('(', 'lambda'): + parens += 1 + elif text == ')': + parens -= 1 + if need_space: + if start != prev_end: + need_space = False + elif text == '>' and prev_text == '<': + # Tolerate the "<>" operator, even if running Python 3 + pass + else: + return prev_end, "E225 missing whitespace around operator" + elif token_type == tokenize.OP and prev_end is not None: + if text == '=' and parens: + # Allow keyword args or defaults: foo(bar=None). + pass + elif text in BINARY_OPERATORS: + need_space = True + elif text in UNARY_OPERATORS: + # Allow unary operators: -123, -x, +1. + # Allow argument unpacking: foo(*args, **kwargs). + if prev_type == tokenize.OP: + if prev_text in '}])': + need_space = True + elif prev_type == tokenize.NAME: + if prev_text not in E225NOT_KEYWORDS: + need_space = True + else: + need_space = True + if need_space and start == prev_end: + return prev_end, "E225 missing whitespace around operator" + prev_type = token_type + prev_text = text + prev_end = end + + +def whitespace_around_comma(logical_line): + """ + Avoid extraneous whitespace in the following situations: + + - More than one space around an assignment (or other) operator to + align it with another. + + JCR: This should also be applied around comma etc. + Note: these checks are disabled by default + + Okay: a = (1, 2) + E241: a = (1, 2) + E242: a = (1,\t2) + """ + line = logical_line + for separator in ',;:': + found = line.find(separator + ' ') + if found > -1: + return found + 1, "E241 multiple spaces after '%s'" % separator + found = line.find(separator + '\t') + if found > -1: + return found + 1, "E242 tab after '%s'" % separator + + +def whitespace_around_named_parameter_equals(logical_line): + """ + Don't use spaces around the '=' sign when used to indicate a + keyword argument or a default parameter value. + + Okay: def complex(real, imag=0.0): + Okay: return magic(r=real, i=imag) + Okay: boolean(a == b) + Okay: boolean(a != b) + Okay: boolean(a <= b) + Okay: boolean(a >= b) + + E251: def complex(real, imag = 0.0): + E251: return magic(r = real, i = imag) + """ + parens = 0 + for match in WHITESPACE_AROUND_NAMED_PARAMETER_REGEX.finditer( + logical_line): + text = match.group() + if parens and len(text) == 3: + issue = "E251 no spaces around keyword / parameter equals" + return match.start(), issue + if text == '(': + parens += 1 + elif text == ')': + parens -= 1 + + +def whitespace_before_inline_comment(logical_line, tokens): + """ + Separate inline comments by at least two spaces. + + An inline comment is a comment on the same line as a statement. Inline + comments should be separated by at least two spaces from the statement. + They should start with a # and a single space. + + Okay: x = x + 1 # Increment x + Okay: x = x + 1 # Increment x + E261: x = x + 1 # Increment x + E262: x = x + 1 #Increment x + E262: x = x + 1 # Increment x + """ + prev_end = (0, 0) + for token_type, text, start, end, line in tokens: + if token_type == tokenize.NL: + continue + if token_type == tokenize.COMMENT: + if not line[:start[1]].strip(): + continue + if prev_end[0] == start[0] and start[1] < prev_end[1] + 2: + return (prev_end, + "E261 at least two spaces before inline comment") + if (len(text) > 1 and text.startswith('# ') + or not text.startswith('# ')): + return start, "E262 inline comment should start with '# '" + else: + prev_end = end + + +def imports_on_separate_lines(logical_line): + r""" + Imports should usually be on separate lines. + + Okay: import os\nimport sys + E401: import sys, os + + Okay: from subprocess import Popen, PIPE + Okay: from myclas import MyClass + Okay: from foo.bar.yourclass import YourClass + Okay: import myclass + Okay: import foo.bar.yourclass + """ + line = logical_line + if line.startswith('import '): + found = line.find(',') + if found > -1: + return found, "E401 multiple imports on one line" + + +def compound_statements(logical_line): + r""" + Compound statements (multiple statements on the same line) are + generally discouraged. + + While sometimes it's okay to put an if/for/while with a small body + on the same line, never do this for multi-clause statements. Also + avoid folding such long lines! + + Okay: if foo == 'blah':\n do_blah_thing() + Okay: do_one() + Okay: do_two() + Okay: do_three() + + E701: if foo == 'blah': do_blah_thing() + E701: for x in lst: total += x + E701: while t < 10: t = delay() + E701: if foo == 'blah': do_blah_thing() + E701: else: do_non_blah_thing() + E701: try: something() + E701: finally: cleanup() + E701: if foo == 'blah': one(); two(); three() + + E702: do_one(); do_two(); do_three() + """ + line = logical_line + found = line.find(':') + if -1 < found < len(line) - 1: + before = line[:found] + if (before.count('{') <= before.count('}') and # {'a': 1} (dict) + before.count('[') <= before.count(']') and # [1:2] (slice) + not re.search(r'\blambda\b', before)): # lambda x: x + return found, "E701 multiple statements on one line (colon)" + found = line.find(';') + if -1 < found: + return found, "E702 multiple statements on one line (semicolon)" + + +def python_3000_has_key(logical_line): + """ + The {}.has_key() method will be removed in the future version of + Python. Use the 'in' operation instead, like: + d = {"a": 1, "b": 2} + if "b" in d: + print d["b"] + """ + pos = logical_line.find('.has_key(') + if pos > -1: + return pos, "W601 .has_key() is deprecated, use 'in'" + + +def python_3000_raise_comma(logical_line): + """ + When raising an exception, use "raise ValueError('message')" + instead of the older form "raise ValueError, 'message'". + + The paren-using form is preferred because when the exception arguments + are long or include string formatting, you don't need to use line + continuation characters thanks to the containing parentheses. The older + form will be removed in Python 3000. + """ + match = RAISE_COMMA_REGEX.match(logical_line) + if match: + rest = QUOTED_REGEX.sub("", logical_line) + # but allow three argument form of raise + if rest.count(",") == 1: + return match.start(1), "W602 deprecated form of raising exception" + + +def python_3000_not_equal(logical_line): + """ + != can also be written <>, but this is an obsolete usage kept for + backwards compatibility only. New code should always use !=. + The older syntax is removed in Python 3000. + """ + pos = logical_line.find('<>') + if pos > -1: + return pos, "W603 '<>' is deprecated, use '!='" + + +def python_3000_backticks(logical_line): + """ + Backticks are removed in Python 3000. + Use repr() instead. + """ + pos = logical_line.find('`') + if pos > -1: + return pos, "W604 backticks are deprecated, use 'repr()'" + + +############################################################################## +# Helper functions +############################################################################## + + +if '' == ''.encode(): + # Python 2: implicit encoding. + + def readlines(filename): + return open(filename).readlines() +else: + # Python 3: decode to latin-1. + # This function is lazy, it does not read the encoding declaration. + # XXX: use tokenize.detect_encoding() + + def readlines(filename): # NOQA + return open(filename, encoding='latin-1').readlines() + + +def expand_indent(line): + """ + Return the amount of indentation. + Tabs are expanded to the next multiple of 8. + + >>> expand_indent(' ') + 4 + >>> expand_indent('\\t') + 8 + >>> expand_indent(' \\t') + 8 + >>> expand_indent(' \\t') + 8 + >>> expand_indent(' \\t') + 16 + """ + result = 0 + for char in line: + if char == '\t': + result = result // 8 * 8 + 8 + elif char == ' ': + result += 1 + else: + break + return result + + +def mute_string(text): + """ + Replace contents with 'xxx' to prevent syntax matching. + + >>> mute_string('"abc"') + '"xxx"' + >>> mute_string("'''abc'''") + "'''xxx'''" + >>> mute_string("r'abc'") + "r'xxx'" + """ + start = 1 + end = len(text) - 1 + # String modifiers (e.g. u or r) + if text.endswith('"'): + start += text.index('"') + elif text.endswith("'"): + start += text.index("'") + # Triple quotes + if text.endswith('"""') or text.endswith("'''"): + start += 2 + end -= 2 + return text[:start] + 'x' * (end - start) + text[end:] + + +def message(text): + """Print a message.""" + # print >> sys.stderr, options.prog + ': ' + text + # print >> sys.stderr, text + print(text) + + +############################################################################## +# Framework to run all checks +############################################################################## + + +def find_checks(argument_name): + """ + Find all globally visible functions where the first argument name + starts with argument_name. + """ + checks = [] + for name, function in globals().items(): + if not inspect.isfunction(function): + continue + args = inspect.getargspec(function)[0] + if args and args[0].startswith(argument_name): + codes = ERRORCODE_REGEX.findall(inspect.getdoc(function) or '') + for code in codes or ['']: + if not code or not ignore_code(code): + checks.append((name, function, args)) + break + checks.sort() + return checks + + +class Checker(object): + """ + Load a Python source file, tokenize it, check coding style. + """ + + def __init__(self, filename, lines=None): + self.filename = filename + if filename is None: + self.filename = 'stdin' + self.lines = lines or [] + elif lines is None: + self.lines = readlines(filename) + else: + self.lines = lines + options.counters['physical lines'] += len(self.lines) + self.errors = [] + + def readline(self): + """ + Get the next line from the input buffer. + """ + self.line_number += 1 + if self.line_number > len(self.lines): + return '' + return self.lines[self.line_number - 1] + + def readline_check_physical(self): + """ + Check and return the next physical line. This method can be + used to feed tokenize.generate_tokens. + """ + line = self.readline() + if line: + self.check_physical(line) + return line + + def run_check(self, check, argument_names): + """ + Run a check plugin. + """ + arguments = [] + for name in argument_names: + arguments.append(getattr(self, name)) + return check(*arguments) + + def check_physical(self, line): + """ + Run all physical checks on a raw input line. + """ + self.physical_line = line + if self.indent_char is None and len(line) and line[0] in ' \t': + self.indent_char = line[0] + for name, check, argument_names in options.physical_checks: + result = self.run_check(check, argument_names) + if result is not None: + offset, text = result + self.report_error(self.line_number, offset, text, check) + + def build_tokens_line(self): + """ + Build a logical line from tokens. + """ + self.mapping = [] + logical = [] + length = 0 + previous = None + for token in self.tokens: + token_type, text = token[0:2] + if token_type in SKIP_TOKENS: + continue + if token_type == tokenize.STRING: + text = mute_string(text) + if previous: + end_line, end = previous[3] + start_line, start = token[2] + if end_line != start_line: # different row + prev_text = self.lines[end_line - 1][end - 1] + if prev_text == ',' or (prev_text not in '{[(' + and text not in '}])'): + logical.append(' ') + length += 1 + elif end != start: # different column + fill = self.lines[end_line - 1][end:start] + logical.append(fill) + length += len(fill) + self.mapping.append((length, token)) + logical.append(text) + length += len(text) + previous = token + self.logical_line = ''.join(logical) + assert self.logical_line.lstrip() == self.logical_line + assert self.logical_line.rstrip() == self.logical_line + + def check_logical(self): + """ + Build a line from tokens and run all logical checks on it. + """ + options.counters['logical lines'] += 1 + self.build_tokens_line() + first_line = self.lines[self.mapping[0][1][2][0] - 1] + indent = first_line[:self.mapping[0][1][2][1]] + self.previous_indent_level = self.indent_level + self.indent_level = expand_indent(indent) + if options.verbose >= 2: + print(self.logical_line[:80].rstrip()) + for name, check, argument_names in options.logical_checks: + if options.verbose >= 4: + print(' ' + name) + result = self.run_check(check, argument_names) + if result is not None: + offset, text = result + if isinstance(offset, tuple): + original_number, original_offset = offset + else: + for token_offset, token in self.mapping: + if offset >= token_offset: + original_number = token[2][0] + original_offset = (token[2][1] + + offset - token_offset) + self.report_error(original_number, original_offset, + text, check) + self.previous_logical = self.logical_line + + def check_all(self, expected=None, line_offset=0): + """ + Run all checks on the input file. + """ + self.expected = expected or () + self.line_offset = line_offset + self.line_number = 0 + self.file_errors = 0 + self.indent_char = None + self.indent_level = 0 + self.previous_logical = '' + self.blank_lines = 0 + self.blank_lines_before_comment = 0 + self.tokens = [] + parens = 0 + for token in tokenize.generate_tokens(self.readline_check_physical): + if options.verbose >= 3: + if token[2][0] == token[3][0]: + pos = '[%s:%s]' % (token[2][1] or '', token[3][1]) + else: + pos = 'l.%s' % token[3][0] + print('l.%s\t%s\t%s\t%r' % + (token[2][0], pos, tokenize.tok_name[token[0]], token[1])) + self.tokens.append(token) + token_type, text = token[0:2] + if token_type == tokenize.OP and text in '([{': + parens += 1 + if token_type == tokenize.OP and text in '}])': + parens -= 1 + if token_type == tokenize.NEWLINE and not parens: + self.check_logical() + self.blank_lines = 0 + self.blank_lines_before_comment = 0 + self.tokens = [] + if token_type == tokenize.NL and not parens: + if len(self.tokens) <= 1: + # The physical line contains only this token. + self.blank_lines += 1 + self.tokens = [] + if token_type == tokenize.COMMENT: + source_line = token[4] + token_start = token[2][1] + if source_line[:token_start].strip() == '': + self.blank_lines_before_comment = max(self.blank_lines, + self.blank_lines_before_comment) + self.blank_lines = 0 + if text.endswith('\n') and not parens: + # The comment also ends a physical line. This works around + # Python < 2.6 behaviour, which does not generate NL after + # a comment which is on a line by itself. + self.tokens = [] + return self.file_errors + + def report_error(self, line_number, offset, text, check): + """ + Report an error, according to options. + """ + if skip_line(self.physical_line): + return + code = text[:4] + if ignore_code(code): + return + self.errors.append(dict( + text = text, + type = code[0], + col = offset + 1, + lnum = self.line_offset + line_number, + )) + + +def input_file(filename): + """ + Run all checks on a Python source file. + """ + if options.verbose: + message('checking ' + filename) + errors = Checker(filename).check_all() + return errors + + +def input_dir(dirname, runner=None): + """ + Check all Python source files in this directory and all subdirectories. + """ + dirname = dirname.rstrip('/') + if excluded(dirname): + return + if runner is None: + runner = input_file + for root, dirs, files in os.walk(dirname): + if options.verbose: + message('directory ' + root) + options.counters['directories'] += 1 + dirs.sort() + for subdir in dirs: + if excluded(subdir): + dirs.remove(subdir) + files.sort() + for filename in files: + if filename_match(filename) and not excluded(filename): + options.counters['files'] += 1 + runner(os.path.join(root, filename)) + + +def excluded(filename): + """ + Check if options.exclude contains a pattern that matches filename. + """ + basename = os.path.basename(filename) + for pattern in options.exclude: + if fnmatch(basename, pattern): + # print basename, 'excluded because it matches', pattern + return True + + +def filename_match(filename): + """ + Check if options.filename contains a pattern that matches filename. + If options.filename is unspecified, this always returns True. + """ + if not options.filename: + return True + for pattern in options.filename: + if fnmatch(filename, pattern): + return True + + +def ignore_code(code): + """ + Check if options.ignore contains a prefix of the error code. + If options.select contains a prefix of the error code, do not ignore it. + """ + for select in options.select: + if code.startswith(select): + return False + for ignore in options.ignore: + if code.startswith(ignore): + return True + + +def reset_counters(): + for key in list(options.counters.keys()): + if key not in BENCHMARK_KEYS: + del options.counters[key] + options.messages = {} + + +def get_error_statistics(): + """Get error statistics.""" + return get_statistics("E") + + +def get_warning_statistics(): + """Get warning statistics.""" + return get_statistics("W") + + +def get_statistics(prefix=''): + """ + Get statistics for message codes that start with the prefix. + + prefix='' matches all errors and warnings + prefix='E' matches all errors + prefix='W' matches all warnings + prefix='E4' matches all errors that have to do with imports + """ + stats = [] + keys = list(options.messages.keys()) + keys.sort() + for key in keys: + if key.startswith(prefix): + stats.append('%-7s %s %s' % + (options.counters[key], key, options.messages[key])) + return stats + + +def get_count(prefix=''): + """Return the total count of errors and warnings.""" + keys = list(options.messages.keys()) + count = 0 + for key in keys: + if key.startswith(prefix): + count += options.counters[key] + return count + + +def print_statistics(prefix=''): + """Print overall statistics (number of errors and warnings).""" + for line in get_statistics(prefix): + print(line) + + +def print_benchmark(elapsed): + """ + Print benchmark numbers. + """ + print('%-7.2f %s' % (elapsed, 'seconds elapsed')) + for key in BENCHMARK_KEYS: + print('%-7d %s per second (%d total)' % ( + options.counters[key] / elapsed, key, + options.counters[key])) + + +def run_tests(filename): + """ + Run all the tests from a file. + + A test file can provide many tests. Each test starts with a declaration. + This declaration is a single line starting with '#:'. + It declares codes of expected failures, separated by spaces or 'Okay' + if no failure is expected. + If the file does not contain such declaration, it should pass all tests. + If the declaration is empty, following lines are not checked, until next + declaration. + + Examples: + + * Only E224 and W701 are expected: #: E224 W701 + * Following example is conform: #: Okay + * Don't check these lines: #: + """ + lines = readlines(filename) + ['#:\n'] + line_offset = 0 + codes = ['Okay'] + testcase = [] + for index, line in enumerate(lines): + if not line.startswith('#:'): + if codes: + # Collect the lines of the test case + testcase.append(line) + continue + if codes and index > 0: + label = '%s:%s:1' % (filename, line_offset + 1) + codes = [c for c in codes if c != 'Okay'] + # Run the checker + errors = Checker(filename, testcase).check_all(codes, line_offset) + # Check if the expected errors were found + for code in codes: + if not options.counters.get(code): + errors += 1 + message('%s: error %s not found' % (label, code)) + if options.verbose and not errors: + message('%s: passed (%s)' % (label, ' '.join(codes))) + # Keep showing errors for multiple tests + reset_counters() + # output the real line numbers + line_offset = index + # configure the expected errors + codes = line.split()[1:] + # empty the test case buffer + del testcase[:] + + +def selftest(): + """ + Test all check functions with test cases in docstrings. + """ + count_passed = 0 + count_failed = 0 + checks = options.physical_checks + options.logical_checks + for name, check, argument_names in checks: + for line in check.__doc__.splitlines(): + line = line.lstrip() + match = SELFTEST_REGEX.match(line) + if match is None: + continue + code, source = match.groups() + checker = Checker(None) + for part in source.split(r'\n'): + part = part.replace(r'\t', '\t') + part = part.replace(r'\s', ' ') + checker.lines.append(part + '\n') + options.quiet = 2 + checker.check_all() + error = None + if code == 'Okay': + if len(options.counters) > len(BENCHMARK_KEYS): + codes = [key for key in options.counters.keys() + if key not in BENCHMARK_KEYS] + error = "incorrectly found %s" % ', '.join(codes) + elif not options.counters.get(code): + error = "failed to find %s" % code + # Reset the counters + reset_counters() + if not error: + count_passed += 1 + else: + count_failed += 1 + if len(checker.lines) == 1: + print("pep8.py: %s: %s" % + (error, checker.lines[0].rstrip())) + else: + print("pep8.py: %s:" % error) + for line in checker.lines: + print(line.rstrip()) + if options.verbose: + print("%d passed and %d failed." % (count_passed, count_failed)) + if count_failed: + print("Test failed.") + else: + print("Test passed.") + + +def process_options(arglist=None): + """ + Process options passed either via arglist or via command line args. + """ + global options, args + version = '%s (pyflakes: %s, pep8: %s)' % (flake8_version, pep8_version, + __version__) + parser = OptionParser(version=version, + usage="%prog [options] input ...") + parser.add_option('--builtins', default=[], action="append", + help="append builtin function (pyflakes " + "_MAGIC_GLOBALS)") + parser.add_option('--max-complexity', default=-1, action='store', + type='int', help="McCabe complexity treshold") + parser.add_option('-v', '--verbose', default=0, action='count', + help="print status messages, or debug with -vv") + parser.add_option('-q', '--quiet', default=0, action='count', + help="report only file names, or nothing with -qq") + parser.add_option('-r', '--no-repeat', action='store_true', + help="don't show all occurrences of the same error") + parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE, + help="exclude files or directories which match these " + "comma separated patterns (default: %s)" % + DEFAULT_EXCLUDE) + parser.add_option('--exit-zero', action='store_true', + help="use exit code 0 (success), even if there are " + "warnings") + parser.add_option('--filename', metavar='patterns', default='*.py', + help="when parsing directories, only check filenames " + "matching these comma separated patterns (default: " + "*.py)") + parser.add_option('--select', metavar='errors', default='', + help="select errors and warnings (e.g. E,W6)") + parser.add_option('--ignore', metavar='errors', default='', + help="skip errors and warnings (e.g. E4,W)") + parser.add_option('--show-source', action='store_true', + help="show source code for each error") + parser.add_option('--show-pep8', action='store_true', + help="show text of PEP 8 for each error") + parser.add_option('--statistics', action='store_true', + help="count errors and warnings") + parser.add_option('--count', action='store_true', + help="print total number of errors and warnings " + "to standard error and set exit code to 1 if " + "total is not null") + parser.add_option('--benchmark', action='store_true', + help="measure processing speed") + parser.add_option('--testsuite', metavar='dir', + help="run regression tests from dir") + parser.add_option('--doctest', action='store_true', + help="run doctest on myself") + options, args = parser.parse_args(arglist) + if options.testsuite: + args.append(options.testsuite) + if not args and not options.doctest: + parser.error('input not specified') + options.prog = os.path.basename(sys.argv[0]) + options.exclude = options.exclude.split(',') + for index in range(len(options.exclude)): + options.exclude[index] = options.exclude[index].rstrip('/') + if options.filename: + options.filename = options.filename.split(',') + if options.select: + options.select = options.select.split(',') + else: + options.select = [] + if options.ignore: + options.ignore = options.ignore.split(',') + elif options.select: + # Ignore all checks which are not explicitly selected + options.ignore = [''] + elif options.testsuite or options.doctest: + # For doctest and testsuite, all checks are required + options.ignore = [] + else: + # The default choice: ignore controversial checks + options.ignore = DEFAULT_IGNORE.split(',') + options.physical_checks = find_checks('physical_line') + options.logical_checks = find_checks('logical_line') + options.counters = dict.fromkeys(BENCHMARK_KEYS, 0) + options.messages = {} + return options, args + + +def _main(): + """ + Parse options and run checks on Python source. + """ + options, args = process_options() + if options.doctest: + import doctest + doctest.testmod(verbose=options.verbose) + selftest() + if options.testsuite: + runner = run_tests + else: + runner = input_file + start_time = time.time() + for path in args: + if os.path.isdir(path): + input_dir(path, runner=runner) + elif not excluded(path): + options.counters['files'] += 1 + runner(path) + elapsed = time.time() - start_time + if options.statistics: + print_statistics() + if options.benchmark: + print_benchmark(elapsed) + count = get_count() + if count: + if options.count: + sys.stderr.write(str(count) + '\n') + sys.exit(1) + + +if __name__ == '__main__': + _main() diff --git a/pylibs/pymode.py b/pylibs/pymode.py new file mode 100644 index 00000000..7ab41054 --- /dev/null +++ b/pylibs/pymode.py @@ -0,0 +1,165 @@ +import vim + + +def check_file(): + filename = vim.current.buffer.name + checkers = vim.eval('g:pymode_lint_checker').split(',') + ignore = vim.eval("g:pymode_lint_ignore") + ignore = ignore and ignore.split(',') or [] + select = vim.eval("g:pymode_lint_select") + select = select and select.split(',') or [] + errors = [] + + for c in checkers: + checker = globals().get(c) + if checker: + errors += checker(filename) + + for e in errors: + e.update( + filename = filename, + bufnr = vim.current.buffer.number, + ) + + def ignore_error(e): + for s in select: + if e['text'].startswith(s): + return True + for i in ignore: + if e['text'].startswith(i): + return False + return True + + errors = filter(ignore_error, errors) + errors = sorted(errors, key=lambda x: x['lnum']) + + vim.command('let b:qf_list = %s' % repr(errors)) + + +def mccabe(filename): + import mccabe as mc + return mc.get_module_complexity(filename) + + +def pep8(filename): + _ = PEP8 or _init_pep8() + checker = PEP8['module'].Checker(filename) + checker.check_all() + return checker.errors + + +def pylint(filename): + + import StringIO + from logilab.astng.builder import MANAGER + _ = PYLINT or _init_pylint() + linter = PYLINT['lint'] + + MANAGER.astng_cache.clear() + linter.reporter.out = StringIO.StringIO() + linter.check(filename) + errors, linter.reporter.errors = linter.reporter.errors, [] + return errors + + +def pyflakes(filename): + from pyflakes import checker + import _ast + + codeString = file(filename, 'U').read() + '\n' + errors = [] + try: + tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST) + + except SyntaxError, value: + msg = value.args[0] + if codeString is None: + vim.command('echoerr "%s: problem decoding source"' % filename) + else: + lnum, col, _ = value.lineno, value.offset, value.text + errors.append(dict( + lnum = lnum, + col = col, + text = msg, + type = 'E' + )) + + else: + w = checker.Checker(tree, filename) + w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) + for w in w.messages: + errors.append(dict( + lnum = w.lineno, + col = w.offset, + text = w.message % w.message_args, + type = 'E' + )) + return errors + + +PYLINT = dict() +def _init_pylint(): + + from pylint import lint, checkers + import re + + class VimReporter(object): + def __init__(self): + self.errors = [] + + def add_message(self, msg_id, location, msg): + _, _, line, col = location[1:] + self.errors.append(dict( + lnum = line, + col = col, + text = "%s %s" % (msg_id, msg), + type = msg_id[0] + )) + + + PYLINT['lint'] = lint.PyLinter() + PYLINT['re'] = re.compile('^(?:.:)?[^:]+:(\d+): \[([EWRCI]+)[^\]]*\] (.*)$') + + checkers.initialize(PYLINT['lint']) + PYLINT['lint'].load_file_configuration(vim.eval("g:pymode_lint_config")) + PYLINT['lint'].set_option("output-format", "parseable") + PYLINT['lint'].set_option("include-ids", 1) + PYLINT['lint'].set_option("reports", 0) + PYLINT['lint'].reporter = VimReporter() + + +PEP8 = dict() +def _init_pep8(): + + import pep8 as p8 + + class _PEP8Options(object): + # Default options taken from pep8.process_options() + max_complexity = -1 + verbose = False + quiet = False + no_repeat = False + exclude = [exc.rstrip('/') for exc in p8.DEFAULT_EXCLUDE.split(',')] + filename = ['*.py'] + select = [] + ignore = p8.DEFAULT_IGNORE.split(',') # or []? + show_source = False + show_pep8 = False + statistics = False + count = False + benchmark = False + testsuite = '' + doctest = False + logical_checks = physical_checks = None + messages = counters = None + + # default p8 setup + p8.options = _PEP8Options() + p8.options.physical_checks = p8.find_checks('physical_line') + p8.options.logical_checks = p8.find_checks('logical_line') + p8.options.counters = dict.fromkeys(p8.BENCHMARK_KEYS, 0) + p8.options.messages = {} + p8.args = [] + + PEP8['init'] = True + PEP8['module'] = p8 From 288d2d52278cf8afd92c390eed8702bff0904c4d Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 14:22:28 +0400 Subject: [PATCH 046/513] Some fixes. --- pylibs/logilab/__init__.py | 8 ++++---- pylibs/pymode.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pylibs/logilab/__init__.py b/pylibs/logilab/__init__.py index 6f2b6e67..c9634900 100644 --- a/pylibs/logilab/__init__.py +++ b/pylibs/logilab/__init__.py @@ -1,5 +1,5 @@ """generated file, don't modify or your data will be lost""" -try: - __import__('pkg_resources').declare_namespace(__name__) -except ImportError: - pass +# try: + # __import__('pkg_resources').declare_namespace(__name__) +# except ImportError: + # pass diff --git a/pylibs/pymode.py b/pylibs/pymode.py index 7ab41054..ccab3a15 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -90,7 +90,7 @@ def pyflakes(filename): for w in w.messages: errors.append(dict( lnum = w.lineno, - col = w.offset, + col = w.col, text = w.message % w.message_args, type = 'E' )) From b91b850ce410e6133078349be446a62df2de34cd Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 14:26:01 +0400 Subject: [PATCH 047/513] Fix mccabe --- pylibs/pymode.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pylibs/pymode.py b/pylibs/pymode.py index ccab3a15..683d8219 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -38,7 +38,16 @@ def ignore_error(e): def mccabe(filename): import mccabe as mc - return mc.get_module_complexity(filename) + try: + return mc.get_module_complexity(filename) + except SyntaxError, e: + lnum, col, msg = e.lineno, e.offset, e.text + return [dict( + lnum = lnum, + col = col, + text = msg, + type = 'E' + )] def pep8(filename): From bb62fdc919e6cf4039bab7f9023c46661d96b0ea Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 15:45:07 +0400 Subject: [PATCH 048/513] Fix pep8 --- pylibs/pep8.py | 102 +------------------------------------------------ 1 file changed, 1 insertion(+), 101 deletions(-) diff --git a/pylibs/pep8.py b/pylibs/pep8.py index fb9964fb..8459866b 100644 --- a/pylibs/pep8.py +++ b/pylibs/pep8.py @@ -1,99 +1,4 @@ #!/usr/bin/python -# flake8: noqa -# pep8.py - Check Python source code formatting, according to PEP 8 -# Copyright (C) 2006 Johann C. Rocholl -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -""" -Check Python source code formatting, according to PEP 8: -http://www.python.org/dev/peps/pep-0008/ - -For usage and a list of options, try this: -$ python pep8.py -h - -This program and its regression test suite live here: -http://github.com/jcrocholl/pep8 - -Groups of errors and warnings: -E errors -W warnings -100 indentation -200 whitespace -300 blank lines -400 imports -500 line length -600 deprecation -700 statements - -You can add checks to this program by writing plugins. Each plugin is -a simple function that is called for each line of source code, either -physical or logical. - -Physical line: -- Raw line of text from the input file. - -Logical line: -- Multi-line statements converted to a single line. -- Stripped left and right. -- Contents of strings replaced with 'xxx' of same length. -- Comments removed. - -The check function requests physical or logical lines by the name of -the first argument: - -def maximum_line_length(physical_line) -def extraneous_whitespace(logical_line) -def blank_lines(logical_line, blank_lines, indent_level, line_number) - -The last example above demonstrates how check plugins can request -additional information with extra arguments. All attributes of the -Checker object are available. Some examples: - -lines: a list of the raw lines from the input file -tokens: the tokens that contribute to this logical line -line_number: line number in the input file -blank_lines: blank lines before this one -indent_char: first indentation character in this file (' ' or '\t') -indent_level: indentation (with tabs expanded to multiples of 8) -previous_indent_level: indentation on previous line -previous_logical: previous logical line - -The docstring of each check function shall be the relevant part of -text from PEP 8. It is printed if the user enables --show-pep8. -Several docstrings contain examples directly from the PEP 8 document. - -Okay: spam(ham[1], {eggs: 2}) -E201: spam( ham[1], {eggs: 2}) - -These examples are verified automatically when pep8.py is run with the ---doctest option. You can add examples for your own check functions. -The format is simple: "Okay" or error/warning code followed by colon -and space, the rest of the line is example source code. If you put 'r' -before the docstring, you can use \n for newline, \t for tab and \s -for space. - -""" -from flake8 import __version__ as flake8_version -from flake8.pyflakes import __version__ as pep8_version __version__ = '0.6.1' @@ -111,8 +16,6 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number) except NameError: from sets import ImmutableSet as frozenset -from flake8.util import skip_line - DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git' DEFAULT_IGNORE = 'E24' MAX_LINE_LENGTH = 79 @@ -1003,8 +906,6 @@ def report_error(self, line_number, offset, text, check): """ Report an error, according to options. """ - if skip_line(self.physical_line): - return code = text[:4] if ignore_code(code): return @@ -1254,8 +1155,7 @@ def process_options(arglist=None): Process options passed either via arglist or via command line args. """ global options, args - version = '%s (pyflakes: %s, pep8: %s)' % (flake8_version, pep8_version, - __version__) + version = __version__ parser = OptionParser(version=version, usage="%prog [options] input ...") parser.add_option('--builtins', default=[], action="append", From e54d6e651d2979f9f18f6312c41a66dba3bff009 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 17:19:38 +0400 Subject: [PATCH 049/513] Fix qf_list --- autoload/pymode/lint.vim | 16 ++++++++++------ ftplugin/python/pymode.vim | 10 ++++++++-- plugin/pymode.vim | 2 ++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 3212b647..6ed84329 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -4,17 +4,19 @@ function! pymode#lint#Check() try write catch /E212/ - echohl Error | echo "File modified and I can't save it. PyLint cancel." | echohl None + echohl Error | echo "File modified and I can't save it. Cancel code checking." | echohl None return 0 endtry endif py check_file() - if len(b:qf_list) || (exists('b:errors') && len(b:errors)) + if g:qf_list != b:qf_list call setqflist(b:qf_list, 'r') + let g:qf_list = b:qf_list + if g:pymode_lint_cwindow call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, g:pymode_lint_jump) endif @@ -22,7 +24,6 @@ function! pymode#lint#Check() endif if g:pymode_lint_message - let b:errors = {} for v in b:qf_list let b:errors[v['lnum']] = v['text'] endfor @@ -35,21 +36,25 @@ function! pymode#lint#Check() endfunction + fun! pymode#lint#Toggle() "{{{ let g:pymode_lint = g:pymode_lint ? 0 : 1 call pymode#lint#toggle_win(g:pymode_lint, "Pymode lint") endfunction "}}} + fun! pymode#lint#ToggleWindow() "{{{ let g:pymode_lint_cwindow = g:pymode_lint_cwindow ? 0 : 1 call pymode#lint#toggle_win(g:pymode_lint_cwindow, "Pymode lint cwindow") endfunction "}}} + fun! pymode#lint#ToggleChecker() "{{{ let g:pymode_lint_checker = g:pymode_lint_checker == "pylint" ? "pyflakes" : "pylint" echomsg "Pymode lint checker: " . g:pymode_lint_checker endfunction "}}} + fun! pymode#lint#toggle_win(toggle, msg) "{{{ if a:toggle echomsg a:msg." enabled" @@ -63,11 +68,10 @@ fun! pymode#lint#toggle_win(toggle, msg) "{{{ endif endfunction "}}} + fun! pymode#lint#show_errormessage() "{{{ + if !len(b:errors) | return | endif let cursor = getpos(".") - if !pymode#Default('b:errors', {}) || !len(b:errors) - return - endif if has_key(b:errors, l:cursor[1]) call pymode#WideMessage(b:errors[l:cursor[1]]) let b:show_message = 1 diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 706c0c37..d6519e86 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -63,8 +63,7 @@ endif if g:pymode_lint - " DESC: Show message flag - let b:show_message = 0 + let b:qf_list = [] " DESC: Set commands command! -buffer -nargs=0 PyLintToggle :call pymode#lint#Toggle() @@ -82,6 +81,13 @@ if g:pymode_lint endif if g:pymode_lint_message + + " DESC: Show message flag + let b:show_message = 0 + + " DESC: Errors dict + let b:errors = {} + au CursorHold call pymode#lint#show_errormessage() au CursorMoved call pymode#lint#show_errormessage() endif diff --git a/plugin/pymode.vim b/plugin/pymode.vim index f7ae66e6..259882db 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -51,6 +51,8 @@ endif if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint + let g:qf_list = [] + " OPTION: g:pymode_lint_write -- bool. Check code every save. call pymode#Default("g:pymode_lint_write", 1) From f2f7c06f6f35e6a07b72c98e498e06d909cfb4c3 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 17:33:23 +0400 Subject: [PATCH 050/513] Fix messages --- autoload/pymode/lint.vim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 6ed84329..d19b39bd 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -1,4 +1,6 @@ function! pymode#lint#Check() + let b:errors = {} + if g:pymode_lint == 0 | return | endif if &modifiable && &modified try From 14be21fb0799480f31c073042eb83ad79eada5d5 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 18:21:52 +0400 Subject: [PATCH 051/513] Fix linters --- pylibs/pymode.py | 57 +++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 37 deletions(-) diff --git a/pylibs/pymode.py b/pylibs/pymode.py index 683d8219..1e4d6a79 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -13,7 +13,15 @@ def check_file(): for c in checkers: checker = globals().get(c) if checker: - errors += checker(filename) + try: + errors += checker(filename) + except SyntaxError, e: + errors.append(dict( + lnum = e.lineno, + col = e.offset, + text = e.args[0] + )) + break for e in errors: e.update( @@ -38,16 +46,7 @@ def ignore_error(e): def mccabe(filename): import mccabe as mc - try: - return mc.get_module_complexity(filename) - except SyntaxError, e: - lnum, col, msg = e.lineno, e.offset, e.text - return [dict( - lnum = lnum, - col = col, - text = msg, - type = 'E' - )] + return mc.get_module_complexity(filename) def pep8(filename): @@ -77,32 +76,16 @@ def pyflakes(filename): codeString = file(filename, 'U').read() + '\n' errors = [] - try: - tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST) - - except SyntaxError, value: - msg = value.args[0] - if codeString is None: - vim.command('echoerr "%s: problem decoding source"' % filename) - else: - lnum, col, _ = value.lineno, value.offset, value.text - errors.append(dict( - lnum = lnum, - col = col, - text = msg, - type = 'E' - )) - - else: - w = checker.Checker(tree, filename) - w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) - for w in w.messages: - errors.append(dict( - lnum = w.lineno, - col = w.col, - text = w.message % w.message_args, - type = 'E' - )) + tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST) + w = checker.Checker(tree, filename) + w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) + for w in w.messages: + errors.append(dict( + lnum = w.lineno, + col = w.col, + text = w.message % w.message_args, + type = 'E' + )) return errors From a23712e9bb0e0d1ec116ecc42e144fae2c9c3561 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 1 Mar 2012 19:01:27 +0400 Subject: [PATCH 052/513] Fix linters --- autoload/pymode.vim | 3 +++ autoload/pymode/lint.vim | 18 ++++++++++-------- ftplugin/python/pymode.vim | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index 46a62e54..db3e1f0e 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -28,6 +28,7 @@ fun! pymode#QuickfixOpen(onlyRecognized, holdCursor, maxHeight, minHeight, jumpE endif endfunction "}}} + fun! pymode#PlaceSigns() "{{{ sign unplace * for item in filter(getqflist(), 'v:val.bufnr != ""') @@ -35,6 +36,7 @@ fun! pymode#PlaceSigns() "{{{ endfor endfunction "}}} + fun! pymode#CheckProgram(name, append) "{{{ let name = 'g:' . a:name if pymode#Default(name, a:name) @@ -74,6 +76,7 @@ fun! pymode#ShowCommand(cmd) "{{{ wincmd p endfunction "}}} + " DESC: Show wide message fun! pymode#WideMessage(msg) "{{{ let x=&ruler | let y=&showcmd diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index d19b39bd..e41e4c06 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -1,7 +1,9 @@ function! pymode#lint#Check() + + if !g:pymode_lint | return | endif + let b:errors = {} - if g:pymode_lint == 0 | return | endif if &modifiable && &modified try write @@ -19,19 +21,19 @@ function! pymode#lint#Check() let g:qf_list = b:qf_list + if g:pymode_lint_message + for v in b:qf_list + let b:errors[v['lnum']] = v['text'] + endfor + call pymode#lint#show_errormessage() + endif + if g:pymode_lint_cwindow call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, g:pymode_lint_jump) endif endif - if g:pymode_lint_message - for v in b:qf_list - let b:errors[v['lnum']] = v['text'] - endfor - call pymode#lint#show_errormessage() - endif - if g:pymode_lint_signs call pymode#PlaceSigns() endif diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index d6519e86..713724b4 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -90,6 +90,7 @@ if g:pymode_lint au CursorHold call pymode#lint#show_errormessage() au CursorMoved call pymode#lint#show_errormessage() + endif endif From df7ce10587f10051f4f61040fe7c3baeb73148c9 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 2 Mar 2012 17:32:34 +0400 Subject: [PATCH 053/513] Another fix linters --- doc/pymode.txt | 2 +- pylibs/pymode.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/pymode.txt b/doc/pymode.txt index 08826433..6edbbbe8 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.5.5 + Version: 0.6.0 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/pylibs/pymode.py b/pylibs/pymode.py index 1e4d6a79..fdb8640a 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -22,9 +22,12 @@ def check_file(): text = e.args[0] )) break + except Exception, e: + print e for e in errors: e.update( + text = e.get('text', '').replace("'", "\"").split('\n')[0], filename = filename, bufnr = vim.current.buffer.number, ) @@ -41,7 +44,7 @@ def ignore_error(e): errors = filter(ignore_error, errors) errors = sorted(errors, key=lambda x: x['lnum']) - vim.command('let b:qf_list = %s' % repr(errors)) + vim.command(('let b:qf_list = %s' % repr(errors)).replace('\': u', '\': ')) def mccabe(filename): From 5eccc893a1b339ffd15179932cad3b33b7997df2 Mon Sep 17 00:00:00 2001 From: Steve Losh Date: Fri, 2 Mar 2012 11:00:47 -0500 Subject: [PATCH 054/513] Allow print to be marked as a keyword instead of a function. --- syntax/python.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syntax/python.vim b/syntax/python.vim index d3b0fdd7..0a1f66ab 100644 --- a/syntax/python.vim +++ b/syntax/python.vim @@ -33,7 +33,7 @@ call pymode#Default('g:pymode_syntax_all', 1) syn keyword pythonException try except finally syn keyword pythonOperator and in is not or - if !pymode#Default("g:pymode_syntax_print_as_function", 0) || g:pymode_syntax_print_as_function + if !pymode#Default("g:pymode_syntax_print_as_function", 0) || !g:pymode_syntax_print_as_function syn keyword pythonStatement print endif From 95bd4cef589d71778ba4a299edccb14afe9d0bab Mon Sep 17 00:00:00 2001 From: Lowe Thiderman Date: Sun, 4 Mar 2012 16:41:59 +0100 Subject: [PATCH 055/513] Fix hardcoded mappings Fix mappings to rope that did not honor g:pymode_rope_local_prefix --- ftplugin/python/pymode.vim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 713724b4..b3b80edd 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -103,10 +103,10 @@ endif if g:pymode_rope " DESC: Set keys - noremap g :RopeGotoDefinition - noremap d :RopeShowDoc - noremap f :RopeFindOccurrences - noremap m :emenu Rope. + exec "noremap " . g:pymode_rope_local_prefix . "g :RopeGotoDefinition" + exec "noremap " . g:pymode_rope_local_prefix . "d :RopeShowDoc" + exec "noremap " . g:pymode_rope_local_prefix . "f :RopeFindOccurrences" + exec "noremap " . g:pymode_rope_local_prefix . "m :emenu Rope . " inoremap =RopeLuckyAssistInsertMode() let s:prascm = g:pymode_rope_always_show_complete_menu ? "" : "" From 9590fa6d6372f88cb53c278d750703752b88f4dc Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sun, 11 Mar 2012 18:11:01 +0400 Subject: [PATCH 056/513] Fix bugs --- Changelog.rst | 1 + ftplugin/python/pymode.vim | 8 ++++---- pylibs/pymode.py | 1 + pylibs/ropevim.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 7d7c843f..bc78636b 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -9,6 +9,7 @@ Changelog * Now g:pymode_lint_checker can have many values Ex. "pep8,pyflakes,mccabe" * Add 'pymode_lint_ignore' and 'pymode_lint_select' options +* Fix rope keys ## 2012-02-12 0.5.8 ------------------- diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index b3b80edd..37294e34 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -103,10 +103,10 @@ endif if g:pymode_rope " DESC: Set keys - exec "noremap " . g:pymode_rope_local_prefix . "g :RopeGotoDefinition" - exec "noremap " . g:pymode_rope_local_prefix . "d :RopeShowDoc" - exec "noremap " . g:pymode_rope_local_prefix . "f :RopeFindOccurrences" - exec "noremap " . g:pymode_rope_local_prefix . "m :emenu Rope . " + exe "noremap " . g:pymode_rope_local_prefix . "g :RopeGotoDefinition" + exe "noremap " . g:pymode_rope_local_prefix . "d :RopeShowDoc" + exe "noremap " . g:pymode_rope_local_prefix . "f :RopeFindOccurrences" + exe "noremap " . g:pymode_rope_local_prefix . "m :emenu Rope . " inoremap =RopeLuckyAssistInsertMode() let s:prascm = g:pymode_rope_always_show_complete_menu ? "" : "" diff --git a/pylibs/pymode.py b/pylibs/pymode.py index fdb8640a..9b8dba5c 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -27,6 +27,7 @@ def check_file(): for e in errors: e.update( + col = e.get('col') or '', text = e.get('text', '').replace("'", "\"").split('\n')[0], filename = filename, bufnr = vim.current.buffer.number, diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index fc4f8d78..1e9e98b8 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -288,7 +288,7 @@ def _add_command(self, name, callback, key, prefix, prekey): (_vim_name(name), _vim_name(name))) if key is not None: key = prekey + key.replace(' ', '') - vim.command('map %s :call %s()' % (key, _vim_name(name))) + vim.command('noremap %s :call %s()' % (key, _vim_name(name))) @staticmethod def _add_function(name, callback, prefix=False): From 357ec1c6054134b1bf16f63ac62d70b607ace6a6 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 13 Mar 2012 14:28:42 +0400 Subject: [PATCH 057/513] Fix motion. --- after/ftplugin/python.vim | 36 +++++++++---------- autoload/pymode.vim | 71 ++++++++++++++++++++++++++++++++++--- autoload/pymode/motion.vim | 72 ++++++++++++++++++-------------------- 3 files changed, 118 insertions(+), 61 deletions(-) diff --git a/after/ftplugin/python.vim b/after/ftplugin/python.vim index 301c7184..ccadf116 100644 --- a/after/ftplugin/python.vim +++ b/after/ftplugin/python.vim @@ -7,24 +7,24 @@ endif if !pymode#Default('g:pymode_motion', 1) || g:pymode_motion - nnoremap ]] :call pymode#motion#move2('^\(class\\|def\)\s', '') - nnoremap [[ :call pymode#motion#move2('^\(class\\|def\)\s', 'b') - nnoremap ]C :call pymode#motion#move2('^\(class\\|def\)\s', '') - nnoremap [C :call pymode#motion#move2('^\(class\\|def\)\s', 'b') - nnoremap ]M :call pymode#motion#move2('^\s*def\s', '') - nnoremap [M :call pymode#motion#move2('^\s*def\s', 'b') - - onoremap ]] :call pymode#motion#move2('^\(class\\|def\)\s', '') - onoremap [[ :call pymode#motion#move2('^\(class\\|def\)\s', 'b') - onoremap ]C :call pymode#motion#move2('^\(class\\|def\)\s', '') - onoremap [C :call pymode#motion#move2('^\(class\\|def\)\s', 'b') - onoremap ]M :call pymode#motion#move2('^\s*def\s', '') - onoremap [M :call pymode#motion#move2('^\s*def\s', 'b') - - vnoremap ]] :call pymode#motion#vmove('^\(class\\|def\)\s', '') - vnoremap [[ :call pymode#motion#vmove('^\(class\\|def\)\s', 'b') - vnoremap ]M :call pymode#motion#vmove('^\s*def\s', '') - vnoremap [M :call pymode#motion#vmove('^\s*def\s', 'b') + nnoremap ]] :call pymode#motion#move('^\(class\\|def\)\s', '') + nnoremap [[ :call pymode#motion#move('^\(class\\|def\)\s', 'b') + nnoremap ]C :call pymode#motion#move('^\(class\\|def\)\s', '') + nnoremap [C :call pymode#motion#move('^\(class\\|def\)\s', 'b') + nnoremap ]M :call pymode#motion#move('^\s*def\s', '') + nnoremap [M :call pymode#motion#move('^\s*def\s', 'b') + + onoremap ]] :call pymode#motion#move('^\(class\\|def\)\s', '') + onoremap [[ :call pymode#motion#move('^\(class\\|def\)\s', 'b') + onoremap ]C :call pymode#motion#move('^\(class\\|def\)\s', '') + onoremap [C :call pymode#motion#move('^\(class\\|def\)\s', 'b') + onoremap ]M :call pymode#motion#move('^\s*def\s', '') + onoremap [M :call pymode#motion#move('^\s*def\s', 'b') + + vnoremap ]] :call pymode#motion#vmove('^\(class\\|def\)\s', '') + vnoremap [[ :call pymode#motion#vmove('^\(class\\|def\)\s', 'b') + vnoremap ]M :call pymode#motion#vmove('^\s*def\s', '') + vnoremap [M :call pymode#motion#vmove('^\s*def\s', 'b') onoremap C :call pymode#motion#select('^\s*class\s', 0) onoremap aC :call pymode#motion#select('^\s*class\s', 0) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index db3e1f0e..bbea42bc 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -1,4 +1,9 @@ +" Python-mode base functions + + fun! pymode#Default(name, default) "{{{ + " DESC: Set default value if it not exists + " if !exists(a:name) let {a:name} = a:default return 0 @@ -6,7 +11,10 @@ fun! pymode#Default(name, default) "{{{ return 1 endfunction "}}} + fun! pymode#QuickfixOpen(onlyRecognized, holdCursor, maxHeight, minHeight, jumpError) "{{{ + " DESC: Open quickfix window + " let numErrors = len(filter(getqflist(), 'v:val.valid')) let numOthers = len(getqflist()) - numErrors if numErrors > 0 || (!a:onlyRecognized && numOthers > 0) @@ -30,6 +38,8 @@ endfunction "}}} fun! pymode#PlaceSigns() "{{{ + " DESC: Place error signs + " sign unplace * for item in filter(getqflist(), 'v:val.bufnr != ""') execute printf('silent! sign place 1 line=%d name=%s buffer=%d', item.lnum, item.type, item.bufnr) @@ -38,6 +48,8 @@ endfunction "}}} fun! pymode#CheckProgram(name, append) "{{{ + " DESC: Check program is executable or redifined by user. + " let name = 'g:' . a:name if pymode#Default(name, a:name) return 1 @@ -49,21 +61,29 @@ fun! pymode#CheckProgram(name, append) "{{{ return 1 endfunction "}}} + fun! pymode#TempBuffer() "{{{ + " DESC: Open temp buffer. + " pclose | botright 8new setlocal buftype=nofile bufhidden=delete noswapfile nowrap previewwindow redraw endfunction "}}} + fun! pymode#ShowStr(str) "{{{ + " DESC: Open temp buffer with `str`. + " call pymode#TempBuffer() put! =a:str redraw - normal gg - wincmd p + normal gg | wincmd p endfunction "}}} + fun! pymode#ShowCommand(cmd) "{{{ + " DESC: Run command and open temp buffer with result + " call pymode#TempBuffer() try silent exec 'r!' . a:cmd @@ -72,16 +92,57 @@ fun! pymode#ShowCommand(cmd) "{{{ echoerr 'Command fail: '.a:cmd endtry redraw - normal gg - wincmd p + normal gg | wincmd p endfunction "}}} -" DESC: Show wide message fun! pymode#WideMessage(msg) "{{{ + " DESC: Show wide message + let x=&ruler | let y=&showcmd set noruler noshowcmd redraw echo strpart(a:msg, 0, &columns-1) let &ruler=x | let &showcmd=y endfunction "}}} + + +fun! pymode#BlockStart(lnum, ...) "{{{ + let pattern = a:0 ? a:1 : '^\s*\(@\|class\s.*:\|def\s\)' + let lnum = a:lnum + 1 + let indent = 100 + while lnum + let lnum = prevnonblank(lnum - 1) + let test = indent(lnum) + let line = getline(lnum) + if line =~ '^\s*#' " Skip comments + continue + elseif !test " Zero-level regular line + return lnum + elseif test >= indent " Skip deeper or equal lines + continue + " Indent is strictly less at this point: check for def/class + elseif line =~ pattern && line !~ '^\s*@' + return lnum + endif + let indent = indent(lnum) + endwhile + return 0 +endfunction "}}} + + +fun! pymode#BlockEnd(lnum, ...) "{{{ + let indent = a:0 ? a:1 : indent(a:lnum) + let lnum = a:lnum + while lnum + let lnum = nextnonblank(lnum + 1) + if getline(lnum) =~ '^\s*#' | continue + elseif lnum && indent(lnum) <= indent + return lnum - 1 + endif + endwhile + return line('$') +endfunction "}}} + + +" vim: fdm=marker:fdl=0 diff --git a/autoload/pymode/motion.vim b/autoload/pymode/motion.vim index 1273f29e..c8c58ca9 100644 --- a/autoload/pymode/motion.vim +++ b/autoload/pymode/motion.vim @@ -1,40 +1,25 @@ -" Check indentation level on motion -" dC dM - -fun! pymode#motion#block(lnum) "{{{ - let start = indent(a:lnum) - let num = a:lnum - while num - let num = nextnonblank(num + 1) - if num && indent(num) <= start - return num - 1 - endif - endwhile - return line('$') -endfunction "}}} +" Python-mode motion functions -fun! pymode#motion#move2(pattern, flags, ...) "{{{ - normal! m' +fun! pymode#motion#move(pattern, flags, ...) "{{{ let cnt = v:count1 - 1 - let [line, column] = searchpos(a:pattern, a:flags . 'W') + let [line, column] = searchpos(a:pattern, a:flags . 'sW') let indent = indent(line) - - while l:cnt && l:line + while cnt && line let [line, column] = searchpos(a:pattern, a:flags . 'W') - if indent(line) == l:indent - let cnt = l:cnt - 1 + if indent(line) == indent + let cnt = cnt - 1 endif endwhile - return [line, column] - endfunction "}}} -fun! pymode#motion#vmove(pattern, flags) "{{{ - let end = pymode#motion#move2(a:pattern, a:flags) - normal! gv +fun! pymode#motion#vmove(pattern, flags) range "{{{ + call cursor(a:lastline, 0) + let end = pymode#motion#move(a:pattern, a:flags) + call cursor(a:firstline, 0) + normal! v call cursor(end) endfunction "}}} @@ -45,21 +30,32 @@ endfunction "}}} fun! pymode#motion#select(pattern, inner) "{{{ + let cnt = v:count1 - 1 let orig = getpos('.')[1:2] - let start = pymode#motion#move2(a:pattern, 'cb') - let eline = pymode#motion#block(start[0]) - let end = [eline, len(getline(eline))] - call cursor(orig) - - if pymode#motion#pos_le(start, orig) && pymode#motion#pos_le(orig, end) + let snum = pymode#BlockStart(orig[0], a:pattern) + if getline(snum) !~ a:pattern + return 0 + endif + let enum = pymode#BlockEnd(snum, indent(snum)) + while cnt + let lnum = search(a:pattern, 'nW') + if lnum + let enum = pymode#BlockEnd(lnum, indent(lnum)) + call cursor(enum, 1) + endif + let cnt = cnt - 1 + endwhile + if pymode#motion#pos_le([snum, 0], orig) && pymode#motion#pos_le(orig, [enum, 1]) if a:inner - let start = [start[0] + 1, start[1]] - let eline = prevnonblank(end[0]) - let end = [eline, len(getline(eline))] + let snum = snum + 1 + let enum = prevnonblank(enum) endif + + call cursor(snum, 1) normal! v - call cursor(start) - normal! o - call cursor(end) + call cursor(enum, len(getline(enum))) endif endfunction "}}} + + +" vim: fdm=marker:fdl=0 From 20c8a531282e4ad7f495ca22896a619e67cc3dfe Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 13 Mar 2012 14:29:27 +0400 Subject: [PATCH 058/513] Add folding. --- autoload/pymode/folding.vim | 86 +++++++++++++++++++++++++++++++++++++ ftplugin/python/pymode.vim | 21 ++++++--- plugin/pymode.vim | 3 ++ 3 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 autoload/pymode/folding.vim diff --git a/autoload/pymode/folding.vim b/autoload/pymode/folding.vim new file mode 100644 index 00000000..d97d22f2 --- /dev/null +++ b/autoload/pymode/folding.vim @@ -0,0 +1,86 @@ +" Python-mode folding functions + + +let s:defpat = '^\s*\(@\|class\s.*:\|def\s\)' + + +fun! pymode#folding#text() " {{{ + let fs = v:foldstart + while getline(fs) =~ '^\s*@' + let fs = nextnonblank(fs + 1) + endwhile + let line = getline(fs) + + let nucolwidth = &fdc + &number * &numberwidth + let windowwidth = winwidth(0) - nucolwidth - 3 + let foldedlinecount = v:foldend - v:foldstart + + " expand tabs into spaces + let onetab = strpart(' ', 0, &tabstop) + let line = substitute(line, '\t', onetab, 'g') + + let line = strpart(line, 0, windowwidth - 2 -len(foldedlinecount)) + let fillcharcount = windowwidth - len(line) - len(foldedlinecount) + return line . '…' . repeat(" ",fillcharcount) . foldedlinecount . '…' . ' ' +endfunction "}}} + + +fun! pymode#folding#indent(lnum) "{{{ + let indent = indent(pymode#BlockStart(a:lnum)) + return indent ? indent + &shiftwidth : 0 +endfunction "}}} + + +fun! pymode#folding#expr(lnum) "{{{ + let line = getline(a:lnum) + let indent = indent(a:lnum) + + if line == '' | return getline(a:lnum+1) == ''?'=':'-1' | endif + + if line =~ s:defpat && getline(prevnonblank(a:lnum-1)) !~ '^\s*@' + let n = a:lnum + while getline(n) =~ '^\s*@' | let n = nextnonblank(n + 1) | endwhile + if getline(n) =~ s:defpat + return ">".(indent/&shiftwidth+1) + endif + endif + + let p = prevnonblank(a:lnum-1) + while p>0 && getline(p) =~ '^\s*#' | let p = prevnonblank(p-1) + endwhile + let pind = indent(p) + if getline(p) =~ s:defpat && getline(prevnonblank(a:lnum - 1)) !~ '^\s*@' + let pind = pind + &shiftwidth + elseif p==0 | let pind = 0 + endif + + if indent>0 && indent==pind | return '=' + elseif indent>pind | return '=' + elseif indent==0 + if pind==0 && line =~ '^#' | return 0 + elseif line !~'^#' + if 01' + elseif 0==pind && getline(prevnonblank(a:lnum-1)) =~ '^\s*#' | return '>1' + else | return '=' + endif + endif + let n = nextnonblank(a:lnum+1) + while n>0 && getline(n) =~'^\s*#' | let n = nextnonblank(n+1) + endwhile + if indent(n)==0 | return 0 + else | return -1 + end + endif + let blockindent = indent(pymode#BlockStart(a:lnum)) + &shiftwidth + if blockindent==0 | return 1 | endif + let n = nextnonblank(a:lnum+1) + while n>0 && getline(n) =~'^\s*#' | let n = nextnonblank(n+1) | endwhile + let nind = indent(n) + if line =~ '^\s*#' && indent>=nind | return -1 + elseif line =~ '^\s*#' | return nind / &shiftwidth + else | return blockindent / &shiftwidth + endif +endfunction "}}} + + +" vim: fdm=marker:fdl=0 diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 37294e34..9cd359f5 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -24,13 +24,6 @@ if !pymode#Default('g:pymode_options_indent', 1) || g:pymode_options_indent setlocal autoindent endif -" Python fold options -if !pymode#Default('g:pymode_options_fold', 1) || g:pymode_options_fold - setlocal foldlevelstart=99 - setlocal foldlevel=99 - setlocal foldmethod=indent -endif - " Python other options if !pymode#Default('g:pymode_options_other', 1) || g:pymode_options_other setlocal complete+=t @@ -154,4 +147,18 @@ endif " }}} + +" Folding {{{ + +if g:pymode_folding + + setlocal foldmethod=expr + setlocal foldexpr=pymode#folding#expr(v:lnum) + setlocal foldtext=pymode#folding#text() + +endif + +" }}} + + " vim: fdm=marker:fdl=0 diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 259882db..cc28b394 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -258,6 +258,9 @@ endif " }}} +" OPTION: g:pymode_folding -- bool. Enable python-mode folding for pyfiles. +call pymode#Default("g:pymode_folding", 1) + " OPTION: g:pymode_utils_whitespaces -- bool. Remove unused whitespaces on save call pymode#Default("g:pymode_utils_whitespaces", 1) From 577a77c65d2b4c9d35940241bcf37b05077646fb Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 13 Mar 2012 14:39:15 +0400 Subject: [PATCH 059/513] Fix docs and readme. --- Changelog.rst | 4 +++- README.rst | 45 ++++++++++++++++++++++++++++------------ autoload/pymode/doc.vim | 6 ++++++ autoload/pymode/lint.vim | 2 +- doc/pymode.txt | 13 ++++-------- 5 files changed, 46 insertions(+), 24 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index bc78636b..a76eeecd 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -## 2012-02-?? 0.6.0 +## 2012-03-?? 0.6.0 ------------------- * Add 'pymode_lint_hold' option * Improve pymode loading speed @@ -10,6 +10,8 @@ Changelog Ex. "pep8,pyflakes,mccabe" * Add 'pymode_lint_ignore' and 'pymode_lint_select' options * Fix rope keys +* Fix python motion in visual mode +* Add folding 'pymode_folding' ## 2012-02-12 0.5.8 ------------------- diff --git a/README.rst b/README.rst index afb66047..6fae6663 100644 --- a/README.rst +++ b/README.rst @@ -8,19 +8,20 @@ This plugin allow you create python code in vim very easily. There is no need to install the pylint_, rope_ or any used python library on your system. - Python objects and motion (]], 3[[, ]]M, vaC, viM, daC, ciM, ...) +- Folding of python code +- Virtualenv support - Highlight syntax errors - Highlight and auto fix unused imports +- Many linters (pylint_, pyflakes_, ...) that can be run simultaneously - Strong code completion - Code refactoring - Python documentation - Run python code - Go to definition - Powerful customization -- Virtualenv support -- Many linters (pylint_, pyflakes_, ...) that can be run simultaneously -- And more... +- And more, more ... -See (old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my first screencast) +See (very old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my first screencast) .. contents:: @@ -223,14 +224,38 @@ Default values: :: let g:pymode_rope_always_show_complete_menu = 0 -Other stuff ------------ +Automatically folding of python code +-------------------------------------- Default values: :: - " Load python objects and motion + " Enable python folding + let g:pymode_folding = 1 + + +Vim python motions and operators +-------------------------------- + +Default values: :: + + " Enable python objects and motion let g:pymode_motion = 1 + +Virtualenv support +------------------ + +Default values: :: + + " Auto fix vim python paths if virtualenv enabled + let g:pymode_virtualenv = 1 + + +Other stuff +----------- + +Default values: :: + " Load breakpoints plugin let g:pymode_breakpoint = 1 @@ -240,15 +265,9 @@ Default values: :: " Autoremove unused whitespaces let g:pymode_utils_whitespaces = 1 - " Auto fix vim python paths if virtualenv enabled - let g:pymode_virtualenv = 1 - " Set default pymode python indent options let g:pymode_options_indent = 1 - " Set default pymode python fold options - let g:pymode_options_fold = 1 - " Set default pymode python other options let g:pymode_options_other = 1 diff --git a/autoload/pymode/doc.vim b/autoload/pymode/doc.vim index f325a480..ab119869 100644 --- a/autoload/pymode/doc.vim +++ b/autoload/pymode/doc.vim @@ -1,3 +1,6 @@ +" Python-mode search by documentation + + fun! pymode#doc#Show(word) "{{{ if a:word == '' echoerr "No name/symbol under cursor!" @@ -13,3 +16,6 @@ fun! pymode#doc#Show(word) "{{{ wincmd p endif endfunction "}}} + + +" vim: fdm=marker:fdl=0 diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index e41e4c06..18a91aa5 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -1,4 +1,4 @@ -function! pymode#lint#Check() +fun! pymode#lint#Check() if !g:pymode_lint | return | endif diff --git a/doc/pymode.txt b/doc/pymode.txt index 6edbbbe8..35e77ecb 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -81,6 +81,8 @@ PythonMode. These options should be set in your vimrc. |'pymode_rope'| Turns off rope script +|'pymode_folding'| Turns on/off python folding + |'pymode_breakpoint'| Turns off breakpoint script |'pymode_breakpoint_key'| Key for breakpoint @@ -96,9 +98,6 @@ PythonMode. These options should be set in your vimrc. |'pymode_options_indent'| Set default pymode options for python indentation -|'pymode_options_fold'| Set default pymode options for - python folding - |'pymode_options_other'| Set default pymode options for python codding @@ -319,16 +318,12 @@ If this option is set to 1, pymode enable next options for python buffers: > setlocal autoindent < ------------------------------------------------------------------------------ - *'pymode_options_fold'* + *'pymode_folding'* Values: 0 or 1. Default: 1. -If this option is set to 1, pymode enable next options for python buffers: > +If this option is set to 1, pymode enable python-folding. - setlocal foldlevelstart=99 - setlocal foldlevel=99 - setlocal foldmethod=indent -< ------------------------------------------------------------------------------ *'pymode_options_other'* Values: 0 or 1. From 2089fe9e769dfa9e6eee66453907cd36e247041b Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 13 Mar 2012 18:10:04 +0400 Subject: [PATCH 060/513] Fix rope keys --- Changelog.rst | 3 ++- README.rst | 10 ++++++++-- doc/pymode.txt | 2 +- ftplugin/python/pymode.vim | 8 ++++---- plugin/pymode.vim | 7 +++++-- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index a76eeecd..b170439b 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -## 2012-03-?? 0.6.0 +## 2012-03-13 0.6.0 ------------------- * Add 'pymode_lint_hold' option * Improve pymode loading speed @@ -12,6 +12,7 @@ Changelog * Fix rope keys * Fix python motion in visual mode * Add folding 'pymode_folding' +* Warning: 'pymode_lint_checker' now set to 'pyflakes,pep8,mccabe' by default ## 2012-02-12 0.5.8 ------------------- diff --git a/README.rst b/README.rst index 6fae6663..05698f2a 100644 --- a/README.rst +++ b/README.rst @@ -134,7 +134,7 @@ Default values: :: " Switch pylint, pyflakes, pep8, mccabe code-checkers " Can have multiply values "pep8,pyflakes,mcccabe" - let g:pymode_lint_checker = "pylint" + let g:pymode_lint_checker = "pyflakes,pep8,mccabe" " Skip errors and warnings " E.g. "E501,W002", "E2,W" (Skip all Warnings and Errors startswith E2) and etc @@ -329,6 +329,12 @@ Keys Command -------------- ------------- **** Rope autocomplete (g:pymode_rope enabled) -------------- ------------- +**g** Rope goto definition (g:pymode_rope enabled) +-------------- ------------- +**d** Rope show documentation (g:pymode_rope enabled) +-------------- ------------- +**f** Rope find occurrences (g:pymode_rope enabled) +-------------- ------------- **r** Run python (g:pymode_run enabled) -------------- ------------- **b** Set, unset breakpoint (g:pymode_breakpoint enabled) @@ -421,7 +427,7 @@ Development of pylint-mode happens at github: https://github.com/klen/python-mod Copyright ========= -Copyright (C) 2011 Kirill Klenov (klen_) +Copyright (C) 2012 Kirill Klenov (klen_) **Rope** Copyright (C) 2006-2010 Ali Gholami Rudi diff --git a/doc/pymode.txt b/doc/pymode.txt index 35e77ecb..5cdf8cd3 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -152,7 +152,7 @@ If this option is set to 0 then pylint script is disabled. Values: "pylint", "pyflakes", "pep8", "mccabe" You can set many checkers. E.g. "pyflakes,pep8,mccabe" ~ -Default: "pylint". +Default: "pyflakes,pep8,mccabe". This option set code checkers. diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 9cd359f5..e3b139f2 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -96,10 +96,10 @@ endif if g:pymode_rope " DESC: Set keys - exe "noremap " . g:pymode_rope_local_prefix . "g :RopeGotoDefinition" - exe "noremap " . g:pymode_rope_local_prefix . "d :RopeShowDoc" - exe "noremap " . g:pymode_rope_local_prefix . "f :RopeFindOccurrences" - exe "noremap " . g:pymode_rope_local_prefix . "m :emenu Rope . " + exe "noremap " . g:pymode_rope_short_prefix . "g :RopeGotoDefinition" + exe "noremap " . g:pymode_rope_short_prefix . "d :RopeShowDoc" + exe "noremap " . g:pymode_rope_short_prefix . "f :RopeFindOccurrences" + exe "noremap " . g:pymode_rope_short_prefix . "m :emenu Rope . " inoremap =RopeLuckyAssistInsertMode() let s:prascm = g:pymode_rope_always_show_complete_menu ? "" : "" diff --git a/plugin/pymode.vim b/plugin/pymode.vim index cc28b394..8f8bc1af 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -62,8 +62,8 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " OPTION: g:pymode_lint_message -- bool. Show current line error message call pymode#Default("g:pymode_lint_message", 1) - " OPTION: g:pymode_lint_checker -- str. Use pylint of pyflakes for check. - call pymode#Default("g:pymode_lint_checker", "pylint") + " OPTION: g:pymode_lint_checker -- str. Choices are: pylint, pyflakes, pep8, mccabe + call pymode#Default("g:pymode_lint_checker", "pyflakes,pep8,mccabe") " OPTION: g:pymode_lint_config -- str. Path to pylint config file call pymode#Default("g:pymode_lint_config", $HOME . "/.pylintrc") @@ -197,6 +197,9 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope " OPTION: g:pymode_rope_local_prefix -- string. call pymode#Default("g:pymode_rope_local_prefix", "r") + " OPTION: g:pymode_rope_short_prefix -- string. + call pymode#Default("g:pymode_rope_short_prefix", "") + " OPTION: g:pymode_rope_vim_completion -- bool. call pymode#Default("g:pymode_rope_vim_completion", 1) From 6cf0c6453c094eef1a7fb195988fdf60a907edc9 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 13 Mar 2012 20:21:07 +0400 Subject: [PATCH 061/513] Fix locale --- pylibs/pymode.py | 65 +++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/pylibs/pymode.py b/pylibs/pymode.py index 9b8dba5c..74d218b8 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -1,6 +1,11 @@ +import locale + import vim +locale.setlocale(locale.LC_CTYPE, "C") + + def check_file(): filename = vim.current.buffer.name checkers = vim.eval('g:pymode_lint_checker').split(',') @@ -17,9 +22,9 @@ def check_file(): errors += checker(filename) except SyntaxError, e: errors.append(dict( - lnum = e.lineno, - col = e.offset, - text = e.args[0] + lnum=e.lineno, + col=e.offset, + text=e.args[0] )) break except Exception, e: @@ -27,22 +32,13 @@ def check_file(): for e in errors: e.update( - col = e.get('col') or '', - text = e.get('text', '').replace("'", "\"").split('\n')[0], - filename = filename, - bufnr = vim.current.buffer.number, + col=e.get('col') or '', + text=e.get('text', '').replace("'", "\"").split('\n')[0], + filename=filename, + bufnr=vim.current.buffer.number, ) - def ignore_error(e): - for s in select: - if e['text'].startswith(s): - return True - for i in ignore: - if e['text'].startswith(i): - return False - return True - - errors = filter(ignore_error, errors) + errors = filter(lambda e: _ignore_error(e, select, ignore), errors) errors = sorted(errors, key=lambda x: x['lnum']) vim.command(('let b:qf_list = %s' % repr(errors)).replace('\': u', '\': ')) @@ -54,7 +50,7 @@ def mccabe(filename): def pep8(filename): - _ = PEP8 or _init_pep8() + PEP8 or _init_pep8() checker = PEP8['module'].Checker(filename) checker.check_all() return checker.errors @@ -64,7 +60,7 @@ def pylint(filename): import StringIO from logilab.astng.builder import MANAGER - _ = PYLINT or _init_pylint() + PYLINT or _init_pylint() linter = PYLINT['lint'] MANAGER.astng_cache.clear() @@ -85,15 +81,17 @@ def pyflakes(filename): w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) for w in w.messages: errors.append(dict( - lnum = w.lineno, - col = w.col, - text = w.message % w.message_args, - type = 'E' + lnum=w.lineno, + col=w.col, + text=w.message % w.message_args, + type='E' )) return errors PYLINT = dict() + + def _init_pylint(): from pylint import lint, checkers @@ -106,13 +104,12 @@ def __init__(self): def add_message(self, msg_id, location, msg): _, _, line, col = location[1:] self.errors.append(dict( - lnum = line, - col = col, - text = "%s %s" % (msg_id, msg), - type = msg_id[0] + lnum=line, + col=col, + text="%s %s" % (msg_id, msg), + type=msg_id[0] )) - PYLINT['lint'] = lint.PyLinter() PYLINT['re'] = re.compile('^(?:.:)?[^:]+:(\d+): \[([EWRCI]+)[^\]]*\] (.*)$') @@ -125,6 +122,8 @@ def add_message(self, msg_id, location, msg): PEP8 = dict() + + def _init_pep8(): import pep8 as p8 @@ -159,3 +158,13 @@ class _PEP8Options(object): PEP8['init'] = True PEP8['module'] = p8 + + +def _ignore_error(e, select, ignore): + for s in select: + if e['text'].startswith(s): + return True + for i in ignore: + if e['text'].startswith(i): + return False + return True From 1d60548ce9543c462a1fc36830c5e7ec33a57b78 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 21 Mar 2012 17:44:55 +0400 Subject: [PATCH 062/513] Fix pymode_run. --- Changelog.rst | 4 ++++ autoload/pymode.vim | 7 +++++-- autoload/pymode/run.vim | 2 +- doc/pymode.txt | 2 +- plugin/pymode.vim | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index b170439b..7fb59ab1 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,10 @@ Changelog ========= +## 2012-03-?? 0.6.1 +------------------- +* Fix pymode_run for "unnamed" clipboard + ## 2012-03-13 0.6.0 ------------------- * Add 'pymode_lint_hold' option diff --git a/autoload/pymode.vim b/autoload/pymode.vim index bbea42bc..cb2d1dd6 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -74,10 +74,12 @@ endfunction "}}} fun! pymode#ShowStr(str) "{{{ " DESC: Open temp buffer with `str`. " + let g:pymode_curbuf = bufnr("%") call pymode#TempBuffer() put! =a:str redraw - normal gg | wincmd p + normal gg + wincmd p endfunction "}}} @@ -92,7 +94,8 @@ fun! pymode#ShowCommand(cmd) "{{{ echoerr 'Command fail: '.a:cmd endtry redraw - normal gg | wincmd p + normal gg + wincmd p endfunction "}}} diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index 23fc6a9e..ed685bb5 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -5,6 +5,6 @@ fun! pymode#run#Run(line1, line2) "{{{ sil!py execfile(vim.eval('expand("%s:p")')) redi END call pymode#TempBuffer() - normal Pdd + normal ""Pdd wincmd p endfunction "}}} diff --git a/doc/pymode.txt b/doc/pymode.txt index 5cdf8cd3..c7f588bf 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.0 + Version: 0.6.1 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 8f8bc1af..cadb15ea 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.6.0" +let g:pymode_version = "0.6.1" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 716b10f6c6ec6e09ec50008f92f41f816a081a00 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 10 Apr 2012 12:59:37 +0400 Subject: [PATCH 063/513] Add 'pymode_lint_mccabe_complexity' option. --- README.rst | 3 +++ doc/pymode.txt | 9 +++++++++ plugin/pymode.vim | 4 +++- pylibs/pymode.py | 2 ++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 05698f2a..1fe2c656 100644 --- a/README.rst +++ b/README.rst @@ -170,6 +170,9 @@ Default values: :: " Place error signs let g:pymode_lint_signs = 1 + " Maximum allowed mccabe complexity + let g:pymode_lint_mccabe_complexity = 8 + " Minimal height of pylint error window let g:pymode_lint_minheight = 3 diff --git a/doc/pymode.txt b/doc/pymode.txt index c7f588bf..2e88afd7 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -77,6 +77,8 @@ PythonMode. These options should be set in your vimrc. |'pymode_lint_minheight'| Minimal height of pylint error window +|'pymode_lint_mccabe_complexity'| Maximum allowed mccabe complexity + |'pymode_lint_maxheight'| Maximal height of pylint error window |'pymode_rope'| Turns off rope script @@ -243,6 +245,13 @@ Default: 3. Set minimal height for pylint cwindow +------------------------------------------------------------------------------ + *'pymode_lint_mccabe_complexity'* +Values: int +Default: 8. + +Set minimal complexity for mccabe linter. + ------------------------------------------------------------------------------ *'pymode_lint_maxheight'* Values: int diff --git a/plugin/pymode.vim b/plugin/pymode.vim index cadb15ea..f8d28f31 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -90,6 +90,9 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " OPTION: g:pymode_lint_select -- string. Select errors and warnings (e.g. E4,W) call pymode#Default("g:pymode_lint_select", "") + " OPTION: g:pymode_lint_mccabe_complexity -- int. Maximum allowd complexity + call pymode#Default("g:pymode_lint_mccabe_complexity", 8) + " OPTION: g:pymode_lint_signs -- bool. Place error signs if !pymode#Default("g:pymode_lint_signs", 1) || g:pymode_lint_signs @@ -268,4 +271,3 @@ call pymode#Default("g:pymode_folding", 1) call pymode#Default("g:pymode_utils_whitespaces", 1) " vim: fdm=marker:fdl=0 - diff --git a/pylibs/pymode.py b/pylibs/pymode.py index 74d218b8..4dfd130a 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -47,6 +47,8 @@ def check_file(): def mccabe(filename): import mccabe as mc return mc.get_module_complexity(filename) + complexity = int(vim.eval("g:pymode_lint_mccabe_complexity")) + return mc.get_module_complexity(filename, complexity) def pep8(filename): From 65fc8d781feddeb80db060ad0f8c95fa13968eb6 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 10 Apr 2012 13:25:07 +0400 Subject: [PATCH 064/513] Update pep8 to version 1.0.1 --- doc/pymode.txt | 4 ++ plugin/pymode.vim | 2 +- pylibs/pep8.py | 169 ++++++++++++++++++++++++++++++++++------------ pylibs/pymode.py | 7 +- 4 files changed, 133 insertions(+), 49 deletions(-) diff --git a/doc/pymode.txt b/doc/pymode.txt index 2e88afd7..61bca576 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -466,6 +466,10 @@ You may set |exrc| and |secure| in your |vimrc| for auto set custom settings fro Copyright (c) 2005 Divmod, Inc. http://www.divmod.com/ + PEP8: + Copyright (C) 2006 Johann C. Rocholl + http://github.com/jcrocholl/pep8 + Python syntax for vim: Copyright (c) 2010 Dmitry Vasiliev http://www.hlabs.spb.ru/vim/python.vim diff --git a/plugin/pymode.vim b/plugin/pymode.vim index f8d28f31..687fa7fa 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -90,7 +90,7 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " OPTION: g:pymode_lint_select -- string. Select errors and warnings (e.g. E4,W) call pymode#Default("g:pymode_lint_select", "") - " OPTION: g:pymode_lint_mccabe_complexity -- int. Maximum allowd complexity + " OPTION: g:pymode_lint_mccabe_complexity -- int. Maximum allowed complexity call pymode#Default("g:pymode_lint_mccabe_complexity", 8) " OPTION: g:pymode_lint_signs -- bool. Place error signs diff --git a/pylibs/pep8.py b/pylibs/pep8.py index 8459866b..09409452 100644 --- a/pylibs/pep8.py +++ b/pylibs/pep8.py @@ -1,6 +1,98 @@ -#!/usr/bin/python - -__version__ = '0.6.1' +#!/usr/bin/env python +# pep8.py - Check Python source code formatting, according to PEP 8 +# Copyright (C) 2006 Johann C. Rocholl +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +Check Python source code formatting, according to PEP 8: +http://www.python.org/dev/peps/pep-0008/ + +For usage and a list of options, try this: +$ python pep8.py -h + +This program and its regression test suite live here: +http://github.com/jcrocholl/pep8 + +Groups of errors and warnings: +E errors +W warnings +100 indentation +200 whitespace +300 blank lines +400 imports +500 line length +600 deprecation +700 statements + +You can add checks to this program by writing plugins. Each plugin is +a simple function that is called for each line of source code, either +physical or logical. + +Physical line: +- Raw line of text from the input file. + +Logical line: +- Multi-line statements converted to a single line. +- Stripped left and right. +- Contents of strings replaced with 'xxx' of same length. +- Comments removed. + +The check function requests physical or logical lines by the name of +the first argument: + +def maximum_line_length(physical_line) +def extraneous_whitespace(logical_line) +def blank_lines(logical_line, blank_lines, indent_level, line_number) + +The last example above demonstrates how check plugins can request +additional information with extra arguments. All attributes of the +Checker object are available. Some examples: + +lines: a list of the raw lines from the input file +tokens: the tokens that contribute to this logical line +line_number: line number in the input file +blank_lines: blank lines before this one +indent_char: first indentation character in this file (' ' or '\t') +indent_level: indentation (with tabs expanded to multiples of 8) +previous_indent_level: indentation on previous line +previous_logical: previous logical line + +The docstring of each check function shall be the relevant part of +text from PEP 8. It is printed if the user enables --show-pep8. +Several docstrings contain examples directly from the PEP 8 document. + +Okay: spam(ham[1], {eggs: 2}) +E201: spam( ham[1], {eggs: 2}) + +These examples are verified automatically when pep8.py is run with the +--doctest option. You can add examples for your own check functions. +The format is simple: "Okay" or error/warning code followed by colon +and space, the rest of the line is example source code. If you put 'r' +before the docstring, you can use \n for newline, \t for tab and \s +for space. + +""" + +__version__ = '1.0.1' import os import sys @@ -16,13 +108,14 @@ except NameError: from sets import ImmutableSet as frozenset + DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git' DEFAULT_IGNORE = 'E24' MAX_LINE_LENGTH = 79 INDENT_REGEX = re.compile(r'([ \t]*)') RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*(,)') -QUOTED_REGEX = re.compile(r"""([""'])(?:(?=(\\?))\2.)*?\1""") +RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,\s*\w+\s*,\s*\w+') SELFTEST_REGEX = re.compile(r'(Okay|[EW]\d{3}):\s(.*)') ERRORCODE_REGEX = re.compile(r'[EW]\d{3}') DOCSTRING_REGEX = re.compile(r'u?r?["\']') @@ -31,6 +124,7 @@ EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]') WHITESPACE_AROUND_NAMED_PARAMETER_REGEX = \ re.compile(r'[()]|\s=[^=]|[^=!<>]=\s') +LAMBDA_REGEX = re.compile(r'\blambda\b') WHITESPACE = ' \t' @@ -150,16 +244,17 @@ def maximum_line_length(physical_line): """ line = physical_line.rstrip() length = len(line) - if length > MAX_LINE_LENGTH: + if length > options.max_line_length: try: # The line could contain multi-byte characters if not hasattr(line, 'decode'): # Python 3 line = line.encode('latin-1') length = len(line.decode('utf-8')) - except UnicodeDecodeError: + except UnicodeError: pass - if length > MAX_LINE_LENGTH: - return MAX_LINE_LENGTH, "E501 line too long (%d characters)" % length + if length > options.max_line_length: + return options.max_line_length, \ + "E501 line too long (%d characters)" % length ############################################################################## @@ -564,7 +659,7 @@ def compound_statements(logical_line): before = line[:found] if (before.count('{') <= before.count('}') and # {'a': 1} (dict) before.count('[') <= before.count(']') and # [1:2] (slice) - not re.search(r'\blambda\b', before)): # lambda x: x + not LAMBDA_REGEX.search(before)): # lambda x: x return found, "E701 multiple statements on one line (colon)" found = line.find(';') if -1 < found: @@ -595,11 +690,8 @@ def python_3000_raise_comma(logical_line): form will be removed in Python 3000. """ match = RAISE_COMMA_REGEX.match(logical_line) - if match: - rest = QUOTED_REGEX.sub("", logical_line) - # but allow three argument form of raise - if rest.count(",") == 1: - return match.start(1), "W602 deprecated form of raising exception" + if match and not RERAISE_COMMA_REGEX.match(logical_line): + return match.start(1), "W602 deprecated form of raising exception" def python_3000_not_equal(logical_line): @@ -630,15 +722,13 @@ def python_3000_backticks(logical_line): if '' == ''.encode(): # Python 2: implicit encoding. - def readlines(filename): return open(filename).readlines() else: # Python 3: decode to latin-1. # This function is lazy, it does not read the encoding declaration. # XXX: use tokenize.detect_encoding() - - def readlines(filename): # NOQA + def readlines(filename): return open(filename, encoding='latin-1').readlines() @@ -694,13 +784,6 @@ def mute_string(text): return text[:start] + 'x' * (end - start) + text[end:] -def message(text): - """Print a message.""" - # print >> sys.stderr, options.prog + ': ' + text - # print >> sys.stderr, text - print(text) - - ############################################################################## # Framework to run all checks ############################################################################## @@ -922,9 +1005,8 @@ def input_file(filename): Run all checks on a Python source file. """ if options.verbose: - message('checking ' + filename) + print('checking ' + filename) errors = Checker(filename).check_all() - return errors def input_dir(dirname, runner=None): @@ -938,10 +1020,10 @@ def input_dir(dirname, runner=None): runner = input_file for root, dirs, files in os.walk(dirname): if options.verbose: - message('directory ' + root) + print('directory ' + root) options.counters['directories'] += 1 dirs.sort() - for subdir in dirs: + for subdir in dirs[:]: if excluded(subdir): dirs.remove(subdir) files.sort() @@ -1046,8 +1128,8 @@ def print_benchmark(elapsed): print('%-7.2f %s' % (elapsed, 'seconds elapsed')) for key in BENCHMARK_KEYS: print('%-7d %s per second (%d total)' % ( - options.counters[key] / elapsed, key, - options.counters[key])) + options.counters[key] / elapsed, key, + options.counters[key])) def run_tests(filename): @@ -1087,9 +1169,9 @@ def run_tests(filename): for code in codes: if not options.counters.get(code): errors += 1 - message('%s: error %s not found' % (label, code)) + print('%s: error %s not found' % (label, code)) if options.verbose and not errors: - message('%s: passed (%s)' % (label, ' '.join(codes))) + print('%s: passed (%s)' % (label, ' '.join(codes))) # Keep showing errors for multiple tests reset_counters() # output the real line numbers @@ -1155,27 +1237,20 @@ def process_options(arglist=None): Process options passed either via arglist or via command line args. """ global options, args - version = __version__ - parser = OptionParser(version=version, + parser = OptionParser(version=__version__, usage="%prog [options] input ...") - parser.add_option('--builtins', default=[], action="append", - help="append builtin function (pyflakes " - "_MAGIC_GLOBALS)") - parser.add_option('--max-complexity', default=-1, action='store', - type='int', help="McCabe complexity treshold") parser.add_option('-v', '--verbose', default=0, action='count', help="print status messages, or debug with -vv") parser.add_option('-q', '--quiet', default=0, action='count', help="report only file names, or nothing with -qq") - parser.add_option('-r', '--no-repeat', action='store_true', - help="don't show all occurrences of the same error") + parser.add_option('-r', '--repeat', default=True, action='store_true', + help="(obsolete) show all occurrences of the same error") + parser.add_option('--first', action='store_false', dest='repeat', + help="show first occurrence of each error") parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE, help="exclude files or directories which match these " "comma separated patterns (default: %s)" % DEFAULT_EXCLUDE) - parser.add_option('--exit-zero', action='store_true', - help="use exit code 0 (success), even if there are " - "warnings") parser.add_option('--filename', metavar='patterns', default='*.py', help="when parsing directories, only check filenames " "matching these comma separated patterns (default: " @@ -1198,6 +1273,10 @@ def process_options(arglist=None): help="measure processing speed") parser.add_option('--testsuite', metavar='dir', help="run regression tests from dir") + parser.add_option('--max-line-length', type='int', metavar='n', + default=MAX_LINE_LENGTH, + help="set maximum allowed line length (default: %d)" % + MAX_LINE_LENGTH) parser.add_option('--doctest', action='store_true', help="run doctest on myself") options, args = parser.parse_args(arglist) @@ -1220,7 +1299,7 @@ def process_options(arglist=None): elif options.select: # Ignore all checks which are not explicitly selected options.ignore = [''] - elif options.testsuite or options.doctest: + elif options.testsuite or options.doctest or not DEFAULT_IGNORE: # For doctest and testsuite, all checks are required options.ignore = [] else: diff --git a/pylibs/pymode.py b/pylibs/pymode.py index 4dfd130a..0866f50c 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -132,12 +132,10 @@ def _init_pep8(): class _PEP8Options(object): # Default options taken from pep8.process_options() - max_complexity = -1 verbose = False quiet = False - no_repeat = False + repeat = True exclude = [exc.rstrip('/') for exc in p8.DEFAULT_EXCLUDE.split(',')] - filename = ['*.py'] select = [] ignore = p8.DEFAULT_IGNORE.split(',') # or []? show_source = False @@ -146,7 +144,10 @@ class _PEP8Options(object): count = False benchmark = False testsuite = '' + max_line_length = p8.MAX_LINE_LENGTH + filename = ['*.py'] doctest = False + logical_checks = physical_checks = None messages = counters = None From 043617dd1aab898d4e39608eee5f5ff297f5c1fb Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 10 Apr 2012 13:39:58 +0400 Subject: [PATCH 065/513] Version 0.6.1 --- Changelog.rst | 8 +++++++- README.rst | 6 +++++- doc/ropevim.txt | 3 +++ ftplugin/python/pymode.vim | 2 +- plugin/pymode.vim | 4 ++-- pylibs/ropevim.py | 2 +- 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 7fb59ab1..28dcd5d5 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,9 +1,15 @@ Changelog ========= -## 2012-03-?? 0.6.1 +## 2012-04-10 0.6.1 ------------------- * Fix pymode_run for "unnamed" clipboard +* Add 'pymode_lint_mccabe_complexity' option +* Update Pep8 to version 1.0.1 +* Warning! Change 'pymode_rope_goto_def_newwin' option + for open "goto definition" in new window, set it to 'new' or 'vnew' + for horizontally or vertically split + If you use default behaviour (in the same buffer), not changes needed. ## 2012-03-13 0.6.0 ------------------- diff --git a/README.rst b/README.rst index 1fe2c656..5ef4e70e 100644 --- a/README.rst +++ b/README.rst @@ -222,7 +222,7 @@ Default values: :: let g:pymode_rope_guess_project = 1 - let g:pymode_rope_goto_def_newwin = 0 + let g:pymode_rope_goto_def_newwin = "" let g:pymode_rope_always_show_complete_menu = 0 @@ -445,6 +445,10 @@ Copyright (C) 2012 Kirill Klenov (klen_) Copyright (c) 2005 Divmod, Inc. http://www.divmod.com/ + **PEP8** + Copyright (C) 2006 Johann C. Rocholl + http://github.com/jcrocholl/pep8 + **Python syntax for vim** Copyright (c) 2010 Dmitry Vasiliev http://www.hlabs.spb.ru/vim/python.vim diff --git a/doc/ropevim.txt b/doc/ropevim.txt index be42682b..c1651a7d 100644 --- a/doc/ropevim.txt +++ b/doc/ropevim.txt @@ -252,6 +252,9 @@ restructuring can be: > for "go to definition" result if the definition found is located in another file. By default the file is open in the same buffer. + Values: '' -- same buffer, 'new' -- + horizontally split, 'vnew' -- + vertically split *'pymode_rope_always_show_complete_menu'* If set, rope autocompletion menu always show. diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index e3b139f2..79701b29 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -30,7 +30,7 @@ if !pymode#Default('g:pymode_options_other', 1) || g:pymode_options_other setlocal formatoptions-=t setlocal number setlocal nowrap - setlocal textwidth=80 + setlocal textwidth=79 endif " }}} diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 687fa7fa..343a5213 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -209,8 +209,8 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope " OPTION: g:pymode_rope_guess_project -- bool. call pymode#Default("g:pymode_rope_guess_project", 1) - " OPTION: g:pymode_rope_goto_def_newwin -- bool. - call pymode#Default("g:pymode_rope_goto_def_newwin", 0) + " OPTION: g:pymode_rope_goto_def_newwin -- str ('new', 'vnew', ''). + call pymode#Default("g:pymode_rope_goto_def_newwin", "") " OPTION: g:pymode_rope_always_show_complete_menu -- bool. call pymode#Default("g:pymode_rope_always_show_complete_menu", 0) diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 1e9e98b8..920f2ab2 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -210,7 +210,7 @@ def reload_files(self, filenames, moves=None): def find_file(self, filename, readonly=False, other=False, force=False): if filename != self.filename() or force: if other: - vim.command('new') + vim.command(other) filename = '\\ '.join(s.rstrip() for s in filename.split()) vim.command('e %s' % filename) if readonly: From 10b618bd6bbad6520533f34773037192de145d72 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 11 Apr 2012 00:47:52 +0400 Subject: [PATCH 066/513] Fix mccabe. --- pylibs/pymode.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pylibs/pymode.py b/pylibs/pymode.py index 0866f50c..294f9fe8 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -1,3 +1,4 @@ +import StringIO import locale import vim @@ -46,9 +47,9 @@ def check_file(): def mccabe(filename): import mccabe as mc - return mc.get_module_complexity(filename) + complexity = int(vim.eval("g:pymode_lint_mccabe_complexity")) - return mc.get_module_complexity(filename, complexity) + return mc.get_module_complexity(filename, min=complexity) def pep8(filename): @@ -59,9 +60,8 @@ def pep8(filename): def pylint(filename): - - import StringIO from logilab.astng.builder import MANAGER + PYLINT or _init_pylint() linter = PYLINT['lint'] From 2d906ce40c52008f512d029305d52d91106de287 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 11 Apr 2012 00:48:02 +0400 Subject: [PATCH 067/513] Fix paths. --- autoload/pymode/virtualenv.vim | 2 ++ plugin/pymode.vim | 27 ++++++++++++++------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/autoload/pymode/virtualenv.vim b/autoload/pymode/virtualenv.vim index fd0007da..eea6cd1f 100644 --- a/autoload/pymode/virtualenv.vim +++ b/autoload/pymode/virtualenv.vim @@ -13,6 +13,8 @@ fun! pymode#virtualenv#Activate() "{{{ call add(g:pymode_virtualenv_enabled, $VIRTUAL_ENV) python << EOF +import sys, vim, os + ve_dir = os.environ['VIRTUAL_ENV'] ve_dir in sys.path or sys.path.insert(0, ve_dir) activate_this = os.path.join(os.path.join(ve_dir, 'bin'), 'activate_this.py') diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 343a5213..52111a5b 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -19,19 +19,6 @@ if !has('python') let g:pymode_virtualenv = 0 endif -" DESC: Fix python path -if !pymode#Default('g:pymode_path', 1) || g:pymode_path -python << EOF -import sys, vim, os, StringIO - -curpath = vim.eval("getcwd()") -libpath = os.path.join(os.path.dirname(os.path.dirname( - vim.eval("expand(':p')"))), 'pylibs') - -sys.path = [libpath, curpath] + sys.path -EOF -endif - " Virtualenv {{{ @@ -47,6 +34,20 @@ endif " }}} +" DESC: Fix python path +if !pymode#Default('g:pymode_path', 1) || g:pymode_path +python << EOF +import sys, vim, os + +curpath = vim.eval("getcwd()") +libpath = os.path.join(os.path.dirname(os.path.dirname( + vim.eval("expand(':p')"))), 'pylibs') + +sys.path = [libpath, curpath] + sys.path +EOF +endif + + " Lint {{{ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint From 3ef70538c81c5a93c035feb50107addcaa1d35cd Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 11 Apr 2012 00:49:05 +0400 Subject: [PATCH 068/513] Version 0.6.2 --- Changelog.rst | 2 +- plugin/pymode.vim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 28dcd5d5..73bd99aa 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -## 2012-04-10 0.6.1 +## 2012-04-10 0.6.2 ------------------- * Fix pymode_run for "unnamed" clipboard * Add 'pymode_lint_mccabe_complexity' option diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 52111a5b..197e96fc 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.6.1" +let g:pymode_version = "0.6.2" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 34a9d909a80f5ecf3597288eb504cf3403bff22f Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 11 Apr 2012 00:53:32 +0400 Subject: [PATCH 069/513] Update README --- README.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 5ef4e70e..73cfbf21 100644 --- a/README.rst +++ b/README.rst @@ -30,11 +30,15 @@ See (very old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is Changelog ========= -## 2012-02-12 0.5.8 +## 2012-04-10 0.6.2 ------------------- -* Fix pylint for Windows users -* Python documentation search running from Vim (delete g:pydoc option) -* Python code execution running from Vim (delete g:python option) +* Fix pymode_run for "unnamed" clipboard +* Add 'pymode_lint_mccabe_complexity' option +* Update Pep8 to version 1.0.1 +* Warning! Change 'pymode_rope_goto_def_newwin' option + for open "goto definition" in new window, set it to 'new' or 'vnew' + for horizontally or vertically split + If you use default behaviour (in the same buffer), not changes needed. Requirements From 2c9cfbb301a3f6cfbe5343ee0cb798f64e156cda Mon Sep 17 00:00:00 2001 From: Boris Filippov Date: Tue, 17 Apr 2012 04:09:23 +0400 Subject: [PATCH 070/513] Add missing StringIO import to pymode#doc#Show --- autoload/pymode/doc.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/autoload/pymode/doc.vim b/autoload/pymode/doc.vim index ab119869..36bb6ba6 100644 --- a/autoload/pymode/doc.vim +++ b/autoload/pymode/doc.vim @@ -5,6 +5,7 @@ fun! pymode#doc#Show(word) "{{{ if a:word == '' echoerr "No name/symbol under cursor!" else + py import StringIO py sys.stdout, _ = StringIO.StringIO(), sys.stdout py help(vim.eval('a:word')) py sys.stdout, out = _, sys.stdout.getvalue() From a77c11c6762dc172c71d16b45b18103076324dce Mon Sep 17 00:00:00 2001 From: Boris Filippov Date: Tue, 17 Apr 2012 06:11:31 +0400 Subject: [PATCH 071/513] Don't repeat "Error detected while processing function pymode#run#Run" after every line in case when script throws exception when beign runned by r --- autoload/pymode/run.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index ed685bb5..0bb4848f 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -2,7 +2,7 @@ fun! pymode#run#Run(line1, line2) "{{{ if &modifiable && &modified | write | endif redi @"> - sil!py execfile(vim.eval('expand("%s:p")')) + py execfile(vim.eval('expand("%s:p")')) or True redi END call pymode#TempBuffer() normal ""Pdd From 9956aba64cc0f7bfe029fa0f3ea6a3c33037bcad Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 18 Apr 2012 16:29:35 +0400 Subject: [PATCH 072/513] Fix pydocs --- Changelog.rst | 4 ++++ ftplugin/python/pymode.vim | 2 +- plugin/pymode.vim | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 73bd99aa..dab962e2 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,10 @@ Changelog ========= +## 2012-04-18 0.6.3 +------------------- +* Fix pydocs integration + ## 2012-04-10 0.6.2 ------------------- * Fix pymode_run for "unnamed" clipboard diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 79701b29..e18d9445 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -45,7 +45,7 @@ if g:pymode_doc " DESC: Set keys exe "nnoremap " g:pymode_doc_key ":call pymode#doc#Show(expand(''))" - exe "vnoremap " g:pymode_doc_key ":call pymode#doc#Show('')" + exe "vnoremap " g:pymode_doc_key ":call pymode#doc#Show(@*)" endif diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 197e96fc..6bd21518 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.6.2" +let g:pymode_version = "0.6.3" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From be3544ee8b80f3797fd650f4fd12be032862046f Mon Sep 17 00:00:00 2001 From: Naoya Inada Date: Mon, 7 May 2012 02:23:18 +0900 Subject: [PATCH 073/513] Fix an issue that default syntax file doesn't load when a g:pymode_syntax is true --- syntax/python.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syntax/python.vim b/syntax/python.vim index 0a1f66ab..29ce5e86 100644 --- a/syntax/python.vim +++ b/syntax/python.vim @@ -4,7 +4,7 @@ call pymode#Default('g:pymode_syntax', 1) " DESC: Disable script loading -if pymode#Default('b:current_syntax', 'python') || !g:pymode_syntax +if !g:pymode_syntax || pymode#Default('b:current_syntax', 'python') finish endif From 569a58dd61c9a711395629cd5e488f09e471ab53 Mon Sep 17 00:00:00 2001 From: Mohammed Badran Date: Mon, 21 May 2012 21:35:05 +1000 Subject: [PATCH 074/513] fix empty pydoc window for 'unnamed' clipboard --- autoload/pymode/doc.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/pymode/doc.vim b/autoload/pymode/doc.vim index 36bb6ba6..37872393 100644 --- a/autoload/pymode/doc.vim +++ b/autoload/pymode/doc.vim @@ -13,7 +13,7 @@ fun! pymode#doc#Show(word) "{{{ sil!py print out redi END call pymode#TempBuffer() - normal Pdd + normal ""Pdd wincmd p endif endfunction "}}} From 4327fdfb95bd45207bbfefeaff7fc7a0f3006775 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 23 May 2012 21:49:28 +0400 Subject: [PATCH 075/513] Add 'g:pymode_paths'. --- Changelog.rst | 2 ++ README.rst | 3 +++ doc/pymode.txt | 9 +++++++++ plugin/pymode.vim | 6 +++++- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index dab962e2..a77624ca 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,8 @@ Changelog ========= +* Add 'pymode_paths' option + ## 2012-04-18 0.6.3 ------------------- * Fix pydocs integration diff --git a/README.rst b/README.rst index 73cfbf21..5c935386 100644 --- a/README.rst +++ b/README.rst @@ -263,6 +263,9 @@ Other stuff Default values: :: + " Additional python paths + let g:pymode_paths = [] + " Load breakpoints plugin let g:pymode_breakpoint = 1 diff --git a/doc/pymode.txt b/doc/pymode.txt index 61bca576..b6ad4bb4 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -43,6 +43,8 @@ to install the pylint or rope library on your system. The script provides the following options that can customise the behaviour the PythonMode. These options should be set in your vimrc. +|'pymode_paths'| Additional python paths for pymode + |'pymode_doc'| Turns off the documentation script |'pymode_doc_key'| Key for show documentation @@ -116,6 +118,13 @@ PythonMode. These options should be set in your vimrc. To enable any of the below options you should put the given line in your '$HOME/.vimrc'. See |vimrc-intro|. +------------------------------------------------------------------------------ + *'pymode_paths'* +Values: List of strings +Default: []. + +This option set additional python import paths + ------------------------------------------------------------------------------ *'pymode_doc'* Values: 0 or 1. diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 6bd21518..3664650e 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -36,6 +36,9 @@ endif " DESC: Fix python path if !pymode#Default('g:pymode_path', 1) || g:pymode_path + + call pymode#Default('g:pymode_paths', []) + python << EOF import sys, vim, os @@ -43,8 +46,9 @@ curpath = vim.eval("getcwd()") libpath = os.path.join(os.path.dirname(os.path.dirname( vim.eval("expand(':p')"))), 'pylibs') -sys.path = [libpath, curpath] + sys.path +sys.path = [libpath, curpath] + vim.eval("g:pymode_paths") + sys.path EOF + endif From 35f8eaba280a0179cbb5f22ba22e02835a078193 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 23 May 2012 21:50:09 +0400 Subject: [PATCH 076/513] Fix docs. --- autoload/pymode/doc.vim | 2 +- autoload/pymode/folding.vim | 56 +++++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/autoload/pymode/doc.vim b/autoload/pymode/doc.vim index 36bb6ba6..37872393 100644 --- a/autoload/pymode/doc.vim +++ b/autoload/pymode/doc.vim @@ -13,7 +13,7 @@ fun! pymode#doc#Show(word) "{{{ sil!py print out redi END call pymode#TempBuffer() - normal Pdd + normal ""Pdd wincmd p endif endfunction "}}} diff --git a/autoload/pymode/folding.vim b/autoload/pymode/folding.vim index d97d22f2..ddc86106 100644 --- a/autoload/pymode/folding.vim +++ b/autoload/pymode/folding.vim @@ -35,50 +35,70 @@ fun! pymode#folding#expr(lnum) "{{{ let line = getline(a:lnum) let indent = indent(a:lnum) - if line == '' | return getline(a:lnum+1) == ''?'=':'-1' | endif + if line == '' + return getline(a:lnum+1) == '' ? '=' : '-1' + endif if line =~ s:defpat && getline(prevnonblank(a:lnum-1)) !~ '^\s*@' let n = a:lnum - while getline(n) =~ '^\s*@' | let n = nextnonblank(n + 1) | endwhile + while getline(n) =~ '^\s*@' + let n = nextnonblank(n + 1) + endwhile if getline(n) =~ s:defpat return ">".(indent/&shiftwidth+1) endif endif let p = prevnonblank(a:lnum-1) - while p>0 && getline(p) =~ '^\s*#' | let p = prevnonblank(p-1) + while p>0 && getline(p) =~ '^\s*#' + let p = prevnonblank(p-1) endwhile let pind = indent(p) if getline(p) =~ s:defpat && getline(prevnonblank(a:lnum - 1)) !~ '^\s*@' let pind = pind + &shiftwidth - elseif p==0 | let pind = 0 + elseif p==0 + let pind = 0 endif - if indent>0 && indent==pind | return '=' - elseif indent>pind | return '=' + if (indent>0 && indent==pind) || indent>pind + return '=' elseif indent==0 - if pind==0 && line =~ '^#' | return 0 + if pind==0 && line =~ '^#' + return 0 elseif line !~'^#' - if 01' - elseif 0==pind && getline(prevnonblank(a:lnum-1)) =~ '^\s*#' | return '>1' - else | return '=' + if 01' + elseif 0==pind && getline(prevnonblank(a:lnum-1)) =~ '^\s*#' + return '>1' + else + return '=' endif endif let n = nextnonblank(a:lnum+1) - while n>0 && getline(n) =~'^\s*#' | let n = nextnonblank(n+1) + while n>0 && getline(n) =~'^\s*#' + let n = nextnonblank(n+1) endwhile - if indent(n)==0 | return 0 - else | return -1 + if indent(n)==0 + return 0 + else + return -1 end endif let blockindent = indent(pymode#BlockStart(a:lnum)) + &shiftwidth - if blockindent==0 | return 1 | endif + if blockindent==0 + return 1 + endif let n = nextnonblank(a:lnum+1) - while n>0 && getline(n) =~'^\s*#' | let n = nextnonblank(n+1) | endwhile + while n>0 && getline(n) =~'^\s*#' + let n = nextnonblank(n+1) + endwhile let nind = indent(n) - if line =~ '^\s*#' && indent>=nind | return -1 - elseif line =~ '^\s*#' | return nind / &shiftwidth - else | return blockindent / &shiftwidth + if line =~ '^\s*#' && indent>=nind + return -1 + elseif line =~ '^\s*#' + return nind / &shiftwidth + else + return blockindent / &shiftwidth endif endfunction "}}} From f53eb9bfea1d01762208c751e22c16dab28de622 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 23 May 2012 21:57:20 +0400 Subject: [PATCH 077/513] Disable signs for VIM compiled without +signs. --- autoload/pymode.vim | 10 ++++++---- plugin/pymode.vim | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index cb2d1dd6..7a552abc 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -40,10 +40,12 @@ endfunction "}}} fun! pymode#PlaceSigns() "{{{ " DESC: Place error signs " - sign unplace * - for item in filter(getqflist(), 'v:val.bufnr != ""') - execute printf('silent! sign place 1 line=%d name=%s buffer=%d', item.lnum, item.type, item.bufnr) - endfor + if has('signs') + sign unplace * + for item in filter(getqflist(), 'v:val.bufnr != ""') + execute printf('silent! sign place 1 line=%d name=%s buffer=%d', item.lnum, item.type, item.bufnr) + endfor + endif endfunction "}}} diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 3664650e..17f05dcf 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -99,7 +99,7 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint call pymode#Default("g:pymode_lint_mccabe_complexity", 8) " OPTION: g:pymode_lint_signs -- bool. Place error signs - if !pymode#Default("g:pymode_lint_signs", 1) || g:pymode_lint_signs + if (!pymode#Default("g:pymode_lint_signs", 1) || g:pymode_lint_signs) && has('signs') " DESC: Signs definition sign define W text=WW texthl=Todo From 46db3e787b1488b081b81d73588b56817c058f6d Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 23 May 2012 22:11:08 +0400 Subject: [PATCH 078/513] Fix encoding. --- pylibs/ropevim.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 920f2ab2..8cd23b7d 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -29,22 +29,26 @@ def ask(self, prompt, default=None, starting=None): def ask_values(self, prompt, values, default=None, starting=None, show_values=None): + if show_values or (show_values is None and len(values) < 14): self._print_values(values) + if default is not None: prompt = prompt + ('[%s] ' % default) + starting = starting or '' _completer.values = values answer = call('input("%s", "%s", "customlist,RopeValueCompleter")' % (prompt, starting)) if answer is None: - if 'cancel' in values: - return 'cancel' - return + return 'cancel' if 'cancel' in values else None + if default is not None and not answer: return default + if answer.isdigit() and 0 <= int(answer) < len(values): return values[int(answer)] + return answer def ask_directory(self, prompt, default=None, starting=None): @@ -108,7 +112,7 @@ def get_offset(self): @staticmethod def _get_encoding(): - return vim.eval('&encoding') + return vim.eval('&encoding') or 'utf-8' def _encode_line(self, line): return line.encode(self._get_encoding()) @@ -117,8 +121,8 @@ def _decode_line(self, line): return line.decode(self._get_encoding()) def _position_to_offset(self, lineno, colno): - result = min(colno, len(vim.current.buffer[lineno -1]) + 1) - for line in vim.current.buffer[:lineno-1]: + result = min(colno, len(vim.current.buffer[lineno - 1]) + 1) + for line in vim.current.buffer[:lineno - 1]: line = self._decode_line(line) result += len(line) + 1 return result @@ -308,10 +312,10 @@ def _extended_completion(self, proposal): # `ci` means "completion item". see `:help complete-items` word, _, menu = map(lambda x: x.strip(), proposal.name.partition(':')) ci = dict( - word = word, - info = '', - kind = ''.join(s if s not in 'aeyuo' else '' for s in proposal.type)[:3], - menu = menu or '') + word=word, + info='', + kind=''.join(s if s not in 'aeyuo' else '' for s in proposal.type)[:3], + menu=menu or '') if proposal.scope == 'parameter_keyword': default = proposal.get_default() @@ -362,14 +366,17 @@ def echo(message): message = message.encode(vim.eval('&encoding')) print message + def status(message): if isinstance(message, unicode): message = message.encode(vim.eval('&encoding')) vim.command('redraw | echon "%s"' % message) + def call(command): return vim.eval(command) + class _ValueCompleter(object): def __init__(self): @@ -393,7 +400,6 @@ def __call__(self, arg_lead, cmd_line, cursor_pos): vim.command('let s:completions = %s' % result) - ropemode.decorators.logger.message = echo ropemode.decorators.logger.only_short = True From f24d5d4b45067fbee4a8aeb98437411d7858966a Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 23 May 2012 23:05:38 +0400 Subject: [PATCH 079/513] Fix docs and code execution. --- autoload/pymode/doc.vim | 5 +---- autoload/pymode/run.vim | 9 +++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/autoload/pymode/doc.vim b/autoload/pymode/doc.vim index 37872393..eaf96091 100644 --- a/autoload/pymode/doc.vim +++ b/autoload/pymode/doc.vim @@ -9,11 +9,8 @@ fun! pymode#doc#Show(word) "{{{ py sys.stdout, _ = StringIO.StringIO(), sys.stdout py help(vim.eval('a:word')) py sys.stdout, out = _, sys.stdout.getvalue() - redi @"> - sil!py print out - redi END call pymode#TempBuffer() - normal ""Pdd + py vim.current.buffer.append(out.split('\n'), 0) wincmd p endif endfunction "}}} diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index 0bb4848f..ad8ba582 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -1,10 +1,11 @@ " DESC: Save file if it modified and run python code fun! pymode#run#Run(line1, line2) "{{{ if &modifiable && &modified | write | endif - redi @"> - py execfile(vim.eval('expand("%s:p")')) or True - redi END + py import StringIO + py sys.stdout, _ = StringIO.StringIO(), sys.stdout + py execfile(vim.eval('expand("%s:p")'), {}, {}) + py sys.stdout, out = _, sys.stdout.getvalue() call pymode#TempBuffer() - normal ""Pdd + py vim.current.buffer.append(out.split('\n'), 0) wincmd p endfunction "}}} From 1af6030cae13b4c5bde5638ac4c5e945a799c27c Mon Sep 17 00:00:00 2001 From: Ronald Kaiser Date: Wed, 23 May 2012 19:42:46 -0300 Subject: [PATCH 080/513] fixing installation instructions --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 5c935386..a238876e 100644 --- a/README.rst +++ b/README.rst @@ -78,7 +78,7 @@ Manually :: % git clone git://github.com/klen/python-mode.git - % cd python-mode.vim + % cd python-mode % cp -R * ~/.vim Then rebuild **helptags** in vim:: From 23cf7e5d7c0debdd5f97000ded8b9c69587f3392 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 24 May 2012 21:04:58 +0400 Subject: [PATCH 081/513] Migrate to Rope 0.9.4 --- pylibs/rope/__init__.py | 6 +++--- pylibs/rope/base/libutils.py | 4 ++-- pylibs/rope/base/pycore.py | 9 +++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pylibs/rope/__init__.py b/pylibs/rope/__init__.py index 74f9753e..19466380 100644 --- a/pylibs/rope/__init__.py +++ b/pylibs/rope/__init__.py @@ -1,10 +1,10 @@ """rope, a python refactoring library""" INFO = __doc__ -VERSION = '0.9.3' +VERSION = '0.9.4' COPYRIGHT = """\ -Copyright (C) 2006-2010 Ali Gholami Rudi -Copyright (C) 2009-2010 Anton Gritsay +Copyright (C) 2006-2012 Ali Gholami Rudi +Copyright (C) 2009-2012 Anton Gritsay This program is free software; you can redistribute it and/or modify it under the terms of GNU General Public License as published by the diff --git a/pylibs/rope/base/libutils.py b/pylibs/rope/base/libutils.py index bb1b1106..cb9381e3 100644 --- a/pylibs/rope/base/libutils.py +++ b/pylibs/rope/base/libutils.py @@ -33,9 +33,9 @@ def relative(root, path): root = rope.base.project._realpath(root).replace(os.path.sep, '/') path = rope.base.project._realpath(path).replace(os.path.sep, '/') if path == root: - return '' + return '' if path.startswith(root + '/'): - return path[len(root) + 1:] + return path[len(root) + 1:] def report_change(project, path, old_content): """Report that the contents of file at `path` was changed diff --git a/pylibs/rope/base/pycore.py b/pylibs/rope/base/pycore.py index 61532f00..32056a0f 100644 --- a/pylibs/rope/base/pycore.py +++ b/pylibs/rope/base/pycore.py @@ -75,12 +75,13 @@ def is_python_file(self, resource): def get_module(self, name, folder=None): """Returns a `PyObject` if the module was found.""" + # check if this is a builtin module + pymod = self._builtin_module(name) + if pymod is not None: + return pymod module = self.find_module(name, folder) if module is None: - module = self._builtin_module(name) - if module is None: - raise ModuleNotFoundError('Module %s not found' % name) - return module + raise ModuleNotFoundError('Module %s not found' % name) return self.resource_to_pyobject(module) def _builtin_submodules(self, modname): From 441d1990ba1d6bba0e84bf5f96c59f482559940f Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 24 May 2012 21:47:36 +0400 Subject: [PATCH 082/513] Fixed ShowStr. --- autoload/pymode.vim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index 7a552abc..a39286c0 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -79,9 +79,8 @@ fun! pymode#ShowStr(str) "{{{ let g:pymode_curbuf = bufnr("%") call pymode#TempBuffer() put! =a:str - redraw - normal gg wincmd p + redraw endfunction "}}} From 14aa358b7468ee3758a9ee9001c0cd24d146ccfd Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 24 May 2012 21:55:50 +0400 Subject: [PATCH 083/513] Ropemode updated to version 0.2.0. --- pylibs/ropemode/__init__.py | 4 +-- pylibs/ropemode/decorators.py | 4 --- pylibs/ropemode/dialog.py | 16 +++------ pylibs/ropemode/environment.py | 8 ++--- pylibs/ropemode/filter.py | 8 ++--- pylibs/ropemode/interface.py | 64 ++++++++++++++++++---------------- pylibs/ropemode/refactor.py | 47 +++---------------------- 7 files changed, 51 insertions(+), 100 deletions(-) diff --git a/pylibs/ropemode/__init__.py b/pylibs/ropemode/__init__.py index c1b3cb56..e1bf29d9 100644 --- a/pylibs/ropemode/__init__.py +++ b/pylibs/ropemode/__init__.py @@ -1,9 +1,9 @@ """ropemode, a helper for using rope refactoring library in IDEs""" INFO = __doc__ -VERSION = '0.1-rc2' +VERSION = '0.2' COPYRIGHT = """\ -Copyright (C) 2007-2008 Ali Gholami Rudi +Copyright (C) 2007-2012 Ali Gholami Rudi This program is free software; you can redistribute it and/or modify it under the terms of GNU General Public License as published by the diff --git a/pylibs/ropemode/decorators.py b/pylibs/ropemode/decorators.py index 68eab4f1..21eaaf0c 100644 --- a/pylibs/ropemode/decorators.py +++ b/pylibs/ropemode/decorators.py @@ -20,7 +20,6 @@ def _show(self, message): else: self.message(message) - logger = Logger() @@ -48,7 +47,6 @@ def lispfunction(func): exceptions.ModuleSyntaxError, exceptions.BadIdentifierError) - def _exception_handler(func): def newfunc(*args, **kwds): try: @@ -62,11 +60,9 @@ def newfunc(*args, **kwds): newfunc.__doc__ = func.__doc__ return newfunc - def _exception_message(e): return '%s: %s' % (e.__class__.__name__, str(e)) - def rope_hook(hook): def decorator(func): func = lisphook(func) diff --git a/pylibs/ropemode/dialog.py b/pylibs/ropemode/dialog.py index 5dac796c..8d558133 100644 --- a/pylibs/ropemode/dialog.py +++ b/pylibs/ropemode/dialog.py @@ -20,8 +20,7 @@ def __init__(self, prompt=None, default=False): Data.__init__(self, prompt, self._encode(default), [self._encode(True), self._encode(False)]) - @staticmethod - def _encode(value): + def _encode(self, value): if value: return 'yes' return 'no' @@ -32,19 +31,11 @@ def decode(self, value): return False -def show_dialog(askdata, actions, confs=None, optionals=None, initial_asking=True): +def show_dialog(askdata, actions, confs={}, optionals={}, initial_asking=True): result = {} - - if confs is None: - confs = dict() - - if optionals is None: - optionals = dict() - if initial_asking: for name, conf in confs.items(): result[name] = askdata(conf) - actions.append('batchset') names = list(actions) names.extend(optionals.keys()) @@ -95,8 +86,9 @@ def _parse_batchset(sets): else: if not line.strip(): continue - multiline = False + multiline= False tokens = line.split(None, 1) + value = '' if len(tokens) > 1: result.append([tokens[0], tokens[1].rstrip('\r\n')]) else: diff --git a/pylibs/ropemode/environment.py b/pylibs/ropemode/environment.py index ea8fe32f..8f3ddb8b 100644 --- a/pylibs/ropemode/environment.py +++ b/pylibs/ropemode/environment.py @@ -57,7 +57,7 @@ def filenames(self): def save_files(self, filenames): pass - def reload_files(self, filenames, moves=None): + def reload_files(self, filenames, moves={}): pass def find_file(self, filename, readonly=False, other=False): @@ -96,9 +96,9 @@ def global_command(self, name, callback, key=None, prefix=False): def add_hook(self, name, callback, hook): pass - @staticmethod - def _completion_text(proposal): - return proposal.name.partition(':')[0].strip() + def _completion_text(self, proposal): + return proposal.name def _completion_data(self, proposal): return self._completion_text(proposal) + diff --git a/pylibs/ropemode/filter.py b/pylibs/ropemode/filter.py index 40c49d06..7147c44c 100644 --- a/pylibs/ropemode/filter.py +++ b/pylibs/ropemode/filter.py @@ -10,7 +10,7 @@ def resources(project, rules): exclusion. """ - all_files = set(project.pycore.get_python_files()) + all = set(project.pycore.get_python_files()) files = None for line in rules.splitlines(): if not line.strip(): @@ -23,7 +23,7 @@ def resources(project, rules): except exceptions.ResourceNotFoundError: continue if resource.is_folder(): - matches = set(filter(resource.contains, all_files)) + matches = set(filter(lambda item: resource.contains(item), all)) else: matches = set([resource]) if first == '+': @@ -32,8 +32,8 @@ def resources(project, rules): files.update(matches) if first == '-': if files is None: - files = set(all_files) + files = set(all) files -= matches if files is None: - return all_files + return all return files diff --git a/pylibs/ropemode/interface.py b/pylibs/ropemode/interface.py index 551e60a4..0f3c2a78 100644 --- a/pylibs/ropemode/interface.py +++ b/pylibs/ropemode/interface.py @@ -91,10 +91,8 @@ def open_project(self, root=None): if not self.env.y_or_n('Project not exists in %s, create one?' % root): self.env.message("Project creation aborted") return - progress = self.env.create_progress('Opening [%s] project' % root) self.project = rope.base.project.Project(root) - if self.env.get('enable_autoimport'): underlined = self.env.get('autoimport_underlineds') self.autoimport = autoimport.AutoImport(self.project, @@ -182,10 +180,10 @@ def _base_definition_location(self): @decorators.local_command('a d', 'P', 'C-c d') def show_doc(self, prefix): self._check_project() - self._base_show_doc(prefix, codeassist.get_doc) + self._base_show_doc(prefix, self._base_get_doc(codeassist.get_doc)) - @decorators.local_command('a c', 'P') - def show_calltip(self, prefix): + @decorators.local_command() + def get_calltip(self): self._check_project() def _get_doc(project, text, offset, *args, **kwds): try: @@ -193,10 +191,13 @@ def _get_doc(project, text, offset, *args, **kwds): except ValueError: return None return codeassist.get_calltip(project, text, offset, *args, **kwds) - self._base_show_doc(prefix, _get_doc) + return self._base_get_doc(_get_doc) - def _base_show_doc(self, prefix, get_doc): - docs = self._base_get_doc(get_doc) + @decorators.local_command('a c', 'P') + def show_calltip(self, prefix): + self._base_show_doc(prefix, self.get_calltip()) + + def _base_show_doc(self, prefix, docs): if docs: self.env.show_doc(docs, prefix) else: @@ -302,31 +303,29 @@ def _check_autoimport(self): @decorators.global_command('g') def generate_autoimport_cache(self): - if not self._check_autoimport(): return - modules = self.env.get('autoimport_modules') modules = [ m if isinstance(m, basestring) else m.value() for m in modules ] - def gen(handle): + def generate(handle): self.autoimport.generate_cache(task_handle=handle) self.autoimport.generate_modules_cache(modules, task_handle=handle) - refactor.runtask(self.env, gen, 'Generate autoimport cache') + refactor.runtask(self.env, generate, 'Generate autoimport cache') self.write_project() @decorators.global_command('f', 'P') def find_file(self, prefix): - f = self._base_find_file(prefix) - if f is not None: - self.env.find_file(f.real_path) + file = self._base_find_file(prefix) + if file is not None: + self.env.find_file(file.real_path) @decorators.global_command('4 f', 'P') def find_file_other_window(self, prefix): - f = self._base_find_file(prefix) - if f is not None: - self.env.find_file(f.real_path, other=True) + file = self._base_find_file(prefix) + if file is not None: + self.env.find_file(file.real_path, other=True) def _base_find_file(self, prefix): self._check_project() @@ -338,14 +337,14 @@ def _base_find_file(self, prefix): def _ask_file(self, files): names = [] - for f in files: - names.append('<'.join(reversed(f.path.split('/')))) + for file in files: + names.append('<'.join(reversed(file.path.split('/')))) result = self.env.ask_values('Rope Find File: ', names) if result is not None: path = '/'.join(reversed(result.split('<'))) - f = self.project.get_file(path) - return f - self.env.message('No f selected') + file = self.project.get_file(path) + return file + self.env.message('No file selected') @decorators.local_command('a j') def jump_to_global(self): @@ -464,6 +463,13 @@ def resource(self): if resource and resource.exists(): return resource + @decorators.global_command() + def get_project_root(self): + if self.project is not None: + return self.project.root.real_path + else: + return None + def _check_project(self): if self.project is None: if self.env.get('guess_project'): @@ -490,12 +496,10 @@ def _reload_buffers(self, changes, undo=False): changes.get_changed_resources(), self._get_moved_resources(changes, undo)) - def _reload_buffers_for_changes(self, changed, moved=None): - if moved is None: - moved = dict() - + def _reload_buffers_for_changes(self, changed, moved={}): filenames = [resource.real_path for resource in changed] - moved = dict((resource.real_path, moved[resource].real_path) for resource in moved) + moved = dict([(resource.real_path, moved[resource].real_path) + for resource in moved]) self.env.reload_files(filenames, moved) def _get_moved_resources(self, changes, undo=False): @@ -595,7 +599,6 @@ def lucky_assist(self, prefix): self._apply_assist(result) def auto_import(self): - if not self.interface._check_autoimport(): return @@ -604,7 +607,6 @@ def auto_import(self): name = self.env.current_word() modules = self.autoimport.get_modules(name) - if modules: if len(modules) == 1: module = modules[0] @@ -644,7 +646,7 @@ def _calculate_proposals(self): proposals = codeassist.code_assist( self.interface.project, self.source, self.offset, resource, maxfixes=maxfixes) - if self.env.get('sorted_completions'): + if self.env.get('sorted_completions', True): proposals = codeassist.sorted_proposals(proposals) if self.autoimport is not None: if self.starting.strip() and '.' not in self.expression: diff --git a/pylibs/ropemode/refactor.py b/pylibs/ropemode/refactor.py index 738c1201..b29f7778 100644 --- a/pylibs/ropemode/refactor.py +++ b/pylibs/ropemode/refactor.py @@ -113,10 +113,6 @@ class Rename(Refactoring): key = 'r' saveall = True - def __init__(self, *args): - self.renamer = None - super(Rename, self).__init__(*args) - def _create_refactoring(self): self.renamer = rope.refactor.rename.Rename( self.project, self.resource, self.offset) @@ -141,8 +137,7 @@ def _get_confs(self): oldname = str(self.renamer.get_old_name()) return {'new_name': dialog.Data('New name: ', default=oldname)} - @staticmethod - def _decode_unsure(value): + def _decode_unsure(self, value): unsure = value == 'match' return lambda occurrence: unsure @@ -170,8 +165,7 @@ def _get_optionals(self): 'imports': dialog.Data('Imports: ', decode=self._decode_imports), 'resources': self.resources_option} - @staticmethod - def _decode_args(value): + def _decode_args(self, value): if value: args = {} for raw_check in value.split('\n'): @@ -180,8 +174,7 @@ def _decode_args(value): args[key.strip()] = value.strip() return args - @staticmethod - def _decode_imports(value): + def _decode_imports(self, value): if value: return [line.strip() for line in value.split('\n')] @@ -189,10 +182,6 @@ def _decode_imports(value): class UseFunction(Refactoring): key = 'u' - def __init__(self, *args): - super(UseFunction, self).__init__(*args) - self.user = None - def _create_refactoring(self): self.user = rope.refactor.usefunction.UseFunction( self.project, self.resource, self.offset) @@ -206,9 +195,6 @@ def _get_optionals(self): class Move(Refactoring): key = 'v' - def __init__(self, *args): - super(Move, self).__init__(*args) - self.mover = None def _create_refactoring(self): self.mover = rope.refactor.move.create_move(self.project, @@ -261,9 +247,6 @@ class MoveCurrentModule(Move): class ModuleToPackage(Refactoring): key = '1 p' saveall = False - def __init__(self, *args): - super(ModuleToPackage, self).__init__(*args) - self.packager = None def _create_refactoring(self): self.packager = rope.refactor.ModuleToPackage( @@ -275,9 +258,6 @@ def _calculate_changes(self, values, task_handle): class Inline(Refactoring): key = 'i' - def __init__(self, *args): - super(Inline, self).__init__(*args) - self.inliner = None def _create_refactoring(self): self.inliner = rope.refactor.inline.create_inline( @@ -340,10 +320,6 @@ class OrganizeImports(Refactoring): key = 'o' saveall = False - def __init__(self, *args): - self.organizer = None - super(OrganizeImports, self).__init__(*args) - def _create_refactoring(self): self.organizer = rope.refactor.ImportOrganizer(self.project) @@ -355,9 +331,6 @@ class MethodObject(Refactoring): saveall = False confs = {'classname': dialog.Data('New class name: ', default='_ExtractedClass')} - def __init__(self, *args): - super(MethodObject, self).__init__(*args) - self.objecter = None def _create_refactoring(self): self.objecter = rope.refactor.method_object.MethodObject( @@ -371,9 +344,6 @@ def _calculate_changes(self, values, task_handle): class IntroduceFactory(Refactoring): saveall = True key = 'f' - def __init__(self, *args): - super(IntroduceFactory, self).__init__(*args) - self.factory = None def _create_refactoring(self): self.factory = rope.refactor.introduce_factory.IntroduceFactory( @@ -394,9 +364,6 @@ def _get_optionals(self): class ChangeSignature(Refactoring): saveall = True key = 's' - def __init__(self, *args): - super(ChangeSignature, self).__init__(*args) - self.changer = None def _create_refactoring(self): self.changer = rope.refactor.change_signature.ChangeSignature( @@ -436,7 +403,7 @@ def _get_args(self): def _get_confs(self): args = [] - for arg, _ in self._get_args(): + for arg, default in self._get_args(): args.append(arg) signature = '(' + ', '.join(args) + ')' return {'signature': dialog.Data('Change the signature: ', @@ -451,9 +418,6 @@ def _get_optionals(self): class _GenerateElement(Refactoring): - def __init__(self, *args): - super(_GenerateElement, self).__init__(*args) - self.generator = None def _create_refactoring(self): kind = self.name.split('_')[-1] @@ -507,7 +471,6 @@ def _resources(project, text): def runtask(env, command, name, interrupts=True): return RunTask(env, command, name, interrupts)() - class RunTask(object): def __init__(self, env, task, name, interrupts=True): @@ -519,14 +482,12 @@ def __init__(self, env, task, name, interrupts=True): def __call__(self): handle = taskhandle.TaskHandle(name=self.name) progress = self.env.create_progress(self.name) - def update_progress(): jobset = handle.current_jobset() if jobset: percent = jobset.get_percent_done() if percent is not None: progress.update(percent) - handle.add_observer(update_progress) result = self.task(handle) progress.done() From 0271137e109b7f223445e4f1fa6f20d123e84fb5 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 24 May 2012 22:10:30 +0400 Subject: [PATCH 084/513] Update ropevim --- pylibs/ropevim.py | 72 ++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 8cd23b7d..5c45b24e 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -29,28 +29,30 @@ def ask(self, prompt, default=None, starting=None): def ask_values(self, prompt, values, default=None, starting=None, show_values=None): - if show_values or (show_values is None and len(values) < 14): self._print_values(values) - if default is not None: prompt = prompt + ('[%s] ' % default) - starting = starting or '' _completer.values = values answer = call('input("%s", "%s", "customlist,RopeValueCompleter")' % (prompt, starting)) if answer is None: - return 'cancel' if 'cancel' in values else None - + if 'cancel' in values: + return 'cancel' + return if default is not None and not answer: return default - if answer.isdigit() and 0 <= int(answer) < len(values): return values[int(answer)] - return answer + def _print_values(self, values): + numbered = [] + for index, value in enumerate(values): + numbered.append('%s. %s' % (index, str(value))) + echo('\n'.join(numbered) + '\n') + def ask_directory(self, prompt, default=None, starting=None): return call('input("%s", ".", "dir")' % prompt) @@ -86,21 +88,16 @@ def ask_completion(self, prompt, values, starting=None): def message(self, message): echo(message) - @staticmethod - def _print_values(values): - numbered = [] - for index, value in enumerate(values): - numbered.append('%s. %s' % (index, str(value))) - echo('\n'.join(numbered) + '\n') - def yes_or_no(self, prompt): return self.ask_values(prompt, ['yes', 'no']) == 'yes' def y_or_n(self, prompt): return self.yes_or_no(prompt) - def get(self, name): + def get(self, name, default=None): vimname = 'g:pymode_rope_%s' % name + if str(vim.eval('exists("%s")' % vimname)) == '0': + return default result = vim.eval(vimname) if isinstance(result, str) and result.isdigit(): return int(result) @@ -121,20 +118,24 @@ def _decode_line(self, line): return line.decode(self._get_encoding()) def _position_to_offset(self, lineno, colno): - result = min(colno, len(vim.current.buffer[lineno - 1]) + 1) - for line in vim.current.buffer[:lineno - 1]: + result = min(colno, len(self.buffer[lineno -1]) + 1) + for line in self.buffer[:lineno-1]: line = self._decode_line(line) result += len(line) + 1 return result def get_text(self): - return self._decode_line('\n'.join(vim.current.buffer)) + u'\n' + return self._decode_line('\n'.join(self.buffer)) + u'\n' def get_region(self): - start = self._position_to_offset(*vim.current.buffer.mark('<')) - end = self._position_to_offset(*vim.current.buffer.mark('>')) + start = self._position_to_offset(*self.buffer.mark('<')) + end = self._position_to_offset(*self.buffer.mark('>')) return start, end + @property + def buffer(self): + return vim.current.buffer + def _get_cursor(self): lineno, col = vim.current.window.cursor line = self._decode_line(vim.current.line[:col]) @@ -155,7 +156,7 @@ def get_cur_dir(): return vim.eval('getcwd()') def filename(self): - return vim.current.buffer.name + return self.buffer.name def is_modified(self): return vim.eval('&modified') @@ -164,12 +165,12 @@ def goto_line(self, lineno): self.cursor = (lineno, 0) def insert_line(self, line, lineno): - vim.current.buffer[lineno - 1:lineno - 1] = [line] + self.buffer[lineno - 1:lineno - 1] = [line] def insert(self, text): lineno, colno = self.cursor - line = vim.current.buffer[lineno - 1] - vim.current.buffer[lineno - 1] = line[:colno] + text + line[colno:] + line = self.buffer[lineno - 1] + self.buffer[lineno - 1] = line[:colno] + text + line[colno:] self.cursor = (lineno, colno + len(text)) def delete(self, start, end): @@ -177,8 +178,8 @@ def delete(self, start, end): lineno2, colno2 = self._offset_to_position(end - 1) lineno, colno = self.cursor if lineno1 == lineno2: - line = vim.current.buffer[lineno1 - 1] - vim.current.buffer[lineno1 - 1] = line[:colno1] + line[colno2:] + line = self.buffer[lineno1 - 1] + self.buffer[lineno1 - 1] = line[:colno1] + line[colno2:] if lineno == lineno1 and colno >= colno1: diff = colno2 - colno1 self.cursor = (lineno, max(0, colno - diff)) @@ -194,17 +195,15 @@ def _offset_to_position(self, offset): def filenames(self): result = [] - for b in vim.buffers: - if b.name: - result.append(b.name) + for buffer in vim.buffers: + if buffer.name: + result.append(buffer.name) return result def save_files(self, filenames): vim.command('wall') - def reload_files(self, filenames, moves=None): - if moves is None: - moves = dict() + def reload_files(self, filenames, moves={}): initial = self.filename() for filename in filenames: self.find_file(moves.get(filename, filename), force=True) @@ -249,13 +248,13 @@ def _quickfixdefs(self, locations): finally: os.remove(filename) - @staticmethod - def _writedefs(locations, filename): + def _writedefs(self, locations, filename): tofile = open(filename, 'w') try: for location in locations: err = '%s:%d: - %s\n' % (location.filename, location.lineno, location.note) + echo(err) tofile.write(err) finally: tofile.close() @@ -263,7 +262,6 @@ def _writedefs(locations, filename): def show_doc(self, docs, altview=False): if docs: cmd = 'call pymode#ShowStr("%s")' % str(docs.replace('"', '\\"')) - print cmd vim.command(cmd) def preview_changes(self, diffs): @@ -294,8 +292,7 @@ def _add_command(self, name, callback, key, prefix, prekey): key = prekey + key.replace(' ', '') vim.command('noremap %s :call %s()' % (key, _vim_name(name))) - @staticmethod - def _add_function(name, callback, prefix=False): + def _add_function(self, name, callback, prefix=False): globals()[name] = callback arg = 'None' if prefix else '' vim.command('function! %s()\n' % _vim_name(name) + @@ -306,7 +303,6 @@ def _completion_data(self, proposal): return proposal _docstring_re = re.compile('^[\s\t\n]*([^\n]*)') - def _extended_completion(self, proposal): # we are using extended complete and return dicts instead of strings. # `ci` means "completion item". see `:help complete-items` From 5b2bb31c8aede358f6b8f92d7cc84dee84a19a7b Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 24 May 2012 22:12:33 +0400 Subject: [PATCH 085/513] Version 0.9.4 --- Changelog.rst | 3 +++ doc/pymode.txt | 2 +- plugin/pymode.vim | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index a77624ca..6e60c85d 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,10 @@ Changelog ========= +## 2012-05-24 0.6.4 +------------------- * Add 'pymode_paths' option +* Rope updated to version 0.9.4 ## 2012-04-18 0.6.3 ------------------- diff --git a/doc/pymode.txt b/doc/pymode.txt index b6ad4bb4..48464a0a 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.1 + Version: 0.6.4 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 17f05dcf..365aa96e 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.6.3" +let g:pymode_version = "0.6.4" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 308efa7ea876679b5d3699659cba126042ebd8ea Mon Sep 17 00:00:00 2001 From: Stjujsckij Nickolaj Date: Wed, 30 May 2012 20:43:03 +0400 Subject: [PATCH 086/513] Do not force `setlocal number` if user have `relativenumber` option set --- ftplugin/python/pymode.vim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index e18d9445..71524317 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -28,7 +28,9 @@ endif if !pymode#Default('g:pymode_options_other', 1) || g:pymode_options_other setlocal complete+=t setlocal formatoptions-=t - setlocal number + if !&relativenumber + setlocal number + endif setlocal nowrap setlocal textwidth=79 endif From 9f976bb5754fd1252198fd1d8da4508a1a612ef6 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 31 May 2012 15:26:39 +0400 Subject: [PATCH 087/513] Check vim version --- ftplugin/python/pymode.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 71524317..62292deb 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -28,7 +28,7 @@ endif if !pymode#Default('g:pymode_options_other', 1) || g:pymode_options_other setlocal complete+=t setlocal formatoptions-=t - if !&relativenumber + if v:version > 702 && !&relativenumber setlocal number endif setlocal nowrap From 699cb937204d5fbfa23920e93b56b87d8b68720a Mon Sep 17 00:00:00 2001 From: Piet Delport Date: Thu, 12 Jul 2012 22:42:49 +0200 Subject: [PATCH 088/513] Fix typos. --- doc/pymode.txt | 10 +++++----- doc/ropevim.txt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/pymode.txt b/doc/pymode.txt index 48464a0a..65866c90 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -26,7 +26,7 @@ CONTENTS *Python-mode-contents* Python-mode is a vim plugin that allows you to use the pylint, rope, pydoc library in vim to provide features like python code looking for bugs, -refactoring and some other usefull things. +refactoring and some other useful things. This plugin allow you create python code in vim very easily. There is no need to install the pylint or rope library on your system. @@ -63,7 +63,7 @@ PythonMode. These options should be set in your vimrc. |'pymode_lint_onfly'| Run linter on the fly -|'pymode_lint_config'| Filepath to pylinc configuration +|'pymode_lint_config'| Filepath to pylint configuration |'pymode_lint_write'| Check code every save @@ -381,7 +381,7 @@ iM Select inner function or method. Ex: viM, diM, yiM, ciM (norma 3. Default Keys ~ *PythonModeKeys* -For redifine keys see: |PythonModeOptions| +For redefine keys see: |PythonModeOptions| ================ ============================ Key Command @@ -447,12 +447,12 @@ Solutions: Pylint check is very slow ------------------------- -In some projects pylint_ may check slowly, because it also scan imported modules if posible. +In some projects pylint_ may check slowly, because it also scan imported modules if possible. Try use pyflakes, see |'pymode_lint_checker'|. You may set |exrc| and |secure| in your |vimrc| for auto set custom settings from `.vimrc` from your projects directories. > - Example: On Flask projects I automaticly set 'g:pymode_lint_checker = "pyflakes"', on django 'g:pymode_lint_cheker = "pylint"' + Example: On Flask projects I automatically set 'g:pymode_lint_checker = "pyflakes"', on Django 'g:pymode_lint_checker = "pylint"' < diff --git a/doc/ropevim.txt b/doc/ropevim.txt index c1651a7d..94a76216 100644 --- a/doc/ropevim.txt +++ b/doc/ropevim.txt @@ -166,7 +166,7 @@ When you use ropevim dialogs there is a command called ``batchset``. It can set many options at the same time. After selecting this command from dialog base prompt, you are asked to enter a string. -``batchset`` strijgs can set the value of configs in two ways. The +``batchset`` strings can set the value of configs in two ways. The single line form is like this: > name1 value1 From b9ebc12e487948412f12ccfe29277ab07e9d1493 Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Sat, 14 Jul 2012 13:20:24 +0200 Subject: [PATCH 089/513] Update pep8 to version 1.3.3 --- Changelog.rst | 4 + pylibs/pep8.py | 1501 ++++++++++++++++++++++++++++++++-------------- pylibs/pymode.py | 57 +- 3 files changed, 1069 insertions(+), 493 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 6e60c85d..7d2065e5 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,10 @@ Changelog ========= +## 2012-XX-XX ..... +------------------- +* Update Pep8 to version 1.3.3 + ## 2012-05-24 0.6.4 ------------------- * Add 'pymode_paths' option diff --git a/pylibs/pep8.py b/pylibs/pep8.py index 09409452..35072ece 100644 --- a/pylibs/pep8.py +++ b/pylibs/pep8.py @@ -22,7 +22,7 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -""" +r""" Check Python source code formatting, according to PEP 8: http://www.python.org/dev/peps/pep-0008/ @@ -42,6 +42,7 @@ 500 line length 600 deprecation 700 statements +900 syntax error You can add checks to this program by writing plugins. Each plugin is a simple function that is called for each line of source code, either @@ -92,7 +93,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number) """ -__version__ = '1.0.1' +__version__ = '1.3.3' import os import sys @@ -104,14 +105,37 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number) from optparse import OptionParser from fnmatch import fnmatch try: - frozenset -except NameError: - from sets import ImmutableSet as frozenset - + from configparser import RawConfigParser + from io import TextIOWrapper +except ImportError: + from ConfigParser import RawConfigParser DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git' DEFAULT_IGNORE = 'E24' +if sys.platform == 'win32': + DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8') +else: + DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or + os.path.expanduser('~/.config'), 'pep8') MAX_LINE_LENGTH = 79 +REPORT_FORMAT = { + 'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s', + 'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s', +} + + +SINGLETONS = frozenset(['False', 'None', 'True']) +KEYWORDS = frozenset(keyword.kwlist + ['print']) - SINGLETONS +BINARY_OPERATORS = frozenset([ + '**=', '*=', '+=', '-=', '!=', '<>', + '%=', '^=', '&=', '|=', '==', '/=', '//=', '<=', '>=', '<<=', '>>=', + '%', '^', '&', '|', '=', '/', '//', '<', '>', '<<']) +UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) +OPERATORS = BINARY_OPERATORS | UNARY_OPERATORS +WHITESPACE = frozenset(' \t') +SKIP_TOKENS = frozenset([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE, + tokenize.INDENT, tokenize.DEDENT]) +BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines'] INDENT_REGEX = re.compile(r'([ \t]*)') RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*(,)') @@ -119,29 +143,20 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number) SELFTEST_REGEX = re.compile(r'(Okay|[EW]\d{3}):\s(.*)') ERRORCODE_REGEX = re.compile(r'[EW]\d{3}') DOCSTRING_REGEX = re.compile(r'u?r?["\']') -WHITESPACE_AROUND_OPERATOR_REGEX = \ - re.compile('([^\w\s]*)\s*(\t| )\s*([^\w\s]*)') EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]') -WHITESPACE_AROUND_NAMED_PARAMETER_REGEX = \ - re.compile(r'[()]|\s=[^=]|[^=!<>]=\s') +WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)') +COMPARE_SINGLETON_REGEX = re.compile(r'([=!]=)\s*(None|False|True)') +COMPARE_TYPE_REGEX = re.compile(r'([=!]=|is|is\s+not)\s*type(?:s\.(\w+)Type' + r'|\(\s*(\(\s*\)|[^)]*[^ )])\s*\))') +KEYWORD_REGEX = re.compile(r'(?:[^\s])(\s*)\b(?:%s)\b(\s*)' % + r'|'.join(KEYWORDS)) +OPERATOR_REGEX = re.compile(r'(?:[^\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') +HUNK_REGEX = re.compile(r'^@@ -\d+,\d+ \+(\d+),(\d+) @@.*$') - -WHITESPACE = ' \t' - -BINARY_OPERATORS = frozenset(['**=', '*=', '+=', '-=', '!=', '<>', - '%=', '^=', '&=', '|=', '==', '/=', '//=', '<=', '>=', '<<=', '>>=', - '%', '^', '&', '|', '=', '/', '//', '<', '>', '<<']) -UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) -OPERATORS = BINARY_OPERATORS | UNARY_OPERATORS -SKIP_TOKENS = frozenset([tokenize.COMMENT, tokenize.NL, tokenize.INDENT, - tokenize.DEDENT, tokenize.NEWLINE]) -E225NOT_KEYWORDS = (frozenset(keyword.kwlist + ['print']) - - frozenset(['False', 'None', 'True'])) -BENCHMARK_KEYS = ('directories', 'files', 'logical lines', 'physical lines') - -options = None -args = None +# Work around Python < 2.6 behaviour, which does not generate NL after +# a comment which is on a line by itself. +COMMENT_WITH_NL = tokenize.generate_tokens(['#\n'].pop).send(None)[1] == '#\n' ############################################################################## @@ -178,7 +193,7 @@ def tabs_obsolete(physical_line): W191: if True:\n\treturn """ indent = INDENT_REGEX.match(physical_line).group(1) - if indent.count('\t'): + if '\t' in indent: return indent.index('\t'), "W191 indentation contains tabs" @@ -204,7 +219,7 @@ def trailing_whitespace(physical_line): physical_line = physical_line.rstrip('\n') # chr(10), newline physical_line = physical_line.rstrip('\r') # chr(13), carriage return physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L - stripped = physical_line.rstrip() + stripped = physical_line.rstrip(' \t\v') if physical_line != stripped: if stripped: return len(stripped), "W291 trailing whitespace" @@ -219,19 +234,21 @@ def trailing_blank_lines(physical_line, lines, line_number): Okay: spam(1) W391: spam(1)\n """ - if physical_line.strip() == '' and line_number == len(lines): + if not physical_line.rstrip() and line_number == len(lines): return 0, "W391 blank line at end of file" def missing_newline(physical_line): """ JCR: The last line should have a newline. + + Reports warning W292. """ if physical_line.rstrip() == physical_line: return len(physical_line), "W292 no newline at end of file" -def maximum_line_length(physical_line): +def maximum_line_length(physical_line, max_line_length): """ Limit all lines to a maximum of 79 characters. @@ -241,20 +258,21 @@ def maximum_line_length(physical_line): ugly. Therefore, please limit all lines to a maximum of 79 characters. For flowing long blocks of text (docstrings or comments), limiting the length to 72 characters is recommended. + + Reports error E501. """ line = physical_line.rstrip() length = len(line) - if length > options.max_line_length: - try: + if length > max_line_length: + if hasattr(line, 'decode'): # Python 2 # The line could contain multi-byte characters - if not hasattr(line, 'decode'): # Python 3 - line = line.encode('latin-1') - length = len(line.decode('utf-8')) - except UnicodeError: - pass - if length > options.max_line_length: - return options.max_line_length, \ - "E501 line too long (%d characters)" % length + try: + length = len(line.decode('utf-8')) + except UnicodeError: + pass + if length > max_line_length: + return (max_line_length, "E501 line too long " + "(%d > %d characters)" % (length, max_line_length)) ############################################################################## @@ -263,8 +281,7 @@ def maximum_line_length(physical_line): def blank_lines(logical_line, blank_lines, indent_level, line_number, - previous_logical, previous_indent_level, - blank_lines_before_comment): + previous_logical, previous_indent_level): r""" Separate top-level function and class definitions with two blank lines. @@ -287,21 +304,18 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, """ if line_number == 1: return # Don't expect blank lines before the first line - max_blank_lines = max(blank_lines, blank_lines_before_comment) if previous_logical.startswith('@'): - if max_blank_lines: - return 0, "E304 blank lines found after function decorator" - elif max_blank_lines > 2 or (indent_level and max_blank_lines == 2): - return 0, "E303 too many blank lines (%d)" % max_blank_lines - elif (logical_line.startswith('def ') or - logical_line.startswith('class ') or - logical_line.startswith('@')): + if blank_lines: + yield 0, "E304 blank lines found after function decorator" + elif blank_lines > 2 or (indent_level and blank_lines == 2): + yield 0, "E303 too many blank lines (%d)" % blank_lines + elif logical_line.startswith(('def ', 'class ', '@')): if indent_level: - if not (max_blank_lines or previous_indent_level < indent_level or + if not (blank_lines or previous_indent_level < indent_level or DOCSTRING_REGEX.match(previous_logical)): - return 0, "E301 expected 1 blank line, found 0" - elif max_blank_lines != 2: - return 0, "E302 expected 2 blank lines, found %d" % max_blank_lines + yield 0, "E301 expected 1 blank line, found 0" + elif blank_lines != 2: + yield 0, "E302 expected 2 blank lines, found %d" % blank_lines def extraneous_whitespace(logical_line): @@ -329,13 +343,36 @@ def extraneous_whitespace(logical_line): text = match.group() char = text.strip() found = match.start() - if text == char + ' ' and char in '([{': - return found + 1, "E201 whitespace after '%s'" % char - if text == ' ' + char and line[found - 1] != ',': - if char in '}])': - return found, "E202 whitespace before '%s'" % char - if char in ',;:': - return found, "E203 whitespace before '%s'" % char + if text == char + ' ': + # assert char in '([{' + yield found + 1, "E201 whitespace after '%s'" % char + elif line[found - 1] != ',': + code = ('E202' if char in '}])' else 'E203') # if char in ',;:' + yield found, "%s whitespace before '%s'" % (code, char) + + +def whitespace_around_keywords(logical_line): + r""" + Avoid extraneous whitespace around keywords. + + Okay: True and False + E271: True and False + E272: True and False + E273: True and\tFalse + E274: True\tand False + """ + for match in KEYWORD_REGEX.finditer(logical_line): + before, after = match.groups() + + if '\t' in before: + yield match.start(1), "E274 tab before keyword" + elif len(before) > 1: + yield match.start(1), "E272 multiple spaces before keyword" + + if '\t' in after: + yield match.start(2), "E273 tab after keyword" + elif len(after) > 1: + yield match.start(2), "E271 multiple spaces after keyword" def missing_whitespace(logical_line): @@ -360,7 +397,7 @@ def missing_whitespace(logical_line): continue # Slice syntax, no space required if char == ',' and line[index + 1] == ')': continue # Allow tuple with only one element: (3,) - return index, "E231 missing whitespace after '%s'" % char + yield index, "E231 missing whitespace after '%s'" % char def indentation(logical_line, previous_logical, indent_char, @@ -382,12 +419,171 @@ def indentation(logical_line, previous_logical, indent_char, E113: a = 1\n b = 2 """ if indent_char == ' ' and indent_level % 4: - return 0, "E111 indentation is not a multiple of four" + yield 0, "E111 indentation is not a multiple of four" indent_expect = previous_logical.endswith(':') if indent_expect and indent_level <= previous_indent_level: - return 0, "E112 expected an indented block" + yield 0, "E112 expected an indented block" if indent_level > previous_indent_level and not indent_expect: - return 0, "E113 unexpected indentation" + yield 0, "E113 unexpected indentation" + + +def continuation_line_indentation(logical_line, tokens, indent_level, verbose): + r""" + Continuation lines should align wrapped elements either vertically using + Python's implicit line joining inside parentheses, brackets and braces, or + using a hanging indent. + + When using a hanging indent the following considerations should be applied: + + - there should be no arguments on the first line, and + + - further indentation should be used to clearly distinguish itself as a + continuation line. + + Okay: a = (\n) + E123: a = (\n ) + + Okay: a = (\n 42) + E121: a = (\n 42) + E122: a = (\n42) + E123: a = (\n 42\n ) + E124: a = (24,\n 42\n) + E125: if (a or\n b):\n pass + E126: a = (\n 42) + E127: a = (24,\n 42) + E128: a = (24,\n 42) + """ + first_row = tokens[0][2][0] + nrows = 1 + tokens[-1][2][0] - first_row + if nrows == 1: + return + + # indent_next tells us whether the next block is indented; assuming + # that it is indented by 4 spaces, then we should not allow 4-space + # indents on the final continuation line; in turn, some other + # indents are allowed to have an extra 4 spaces. + indent_next = logical_line.endswith(':') + + row = depth = 0 + # remember how many brackets were opened on each line + parens = [0] * nrows + # relative indents of physical lines + rel_indent = [0] * nrows + # visual indents + indent = [indent_level] + indent_chances = {} + last_indent = (0, 0) + if verbose >= 3: + print(">>> " + tokens[0][4].rstrip()) + + for token_type, text, start, end, line in tokens: + newline = row < start[0] - first_row + if newline: + row = start[0] - first_row + newline = (not last_token_multiline and + token_type not in (tokenize.NL, tokenize.NEWLINE)) + + if newline: + # this is the beginning of a continuation line. + last_indent = start + if verbose >= 3: + print("... " + line.rstrip()) + + # record the initial indent. + rel_indent[row] = start[1] - indent_level + + if depth: + # a bracket expression in a continuation line. + # find the line that it was opened on + for open_row in range(row - 1, -1, -1): + if parens[open_row]: + break + else: + # an unbracketed continuation line (ie, backslash) + open_row = 0 + hang = rel_indent[row] - rel_indent[open_row] + visual_indent = indent_chances.get(start[1]) + + if token_type == tokenize.OP and text in ']})': + # this line starts with a closing bracket + if indent[depth]: + if start[1] != indent[depth]: + yield (start, 'E124 closing bracket does not match ' + 'visual indentation') + elif hang: + yield (start, 'E123 closing bracket does not match ' + 'indentation of opening bracket\'s line') + elif visual_indent is True: + # visual indent is verified + if not indent[depth]: + indent[depth] = start[1] + elif visual_indent in (text, str): + # ignore token lined up with matching one from a previous line + pass + elif indent[depth] and start[1] < indent[depth]: + # visual indent is broken + yield (start, 'E128 continuation line ' + 'under-indented for visual indent') + elif hang == 4 or (indent_next and rel_indent[row] == 8): + # hanging indent is verified + pass + else: + # indent is broken + if hang <= 0: + error = 'E122', 'missing indentation or outdented' + elif indent[depth]: + error = 'E127', 'over-indented for visual indent' + elif hang % 4: + error = 'E121', 'indentation is not a multiple of four' + else: + error = 'E126', 'over-indented for hanging indent' + yield start, "%s continuation line %s" % error + + # look for visual indenting + if parens[row] and token_type != tokenize.NL and not indent[depth]: + indent[depth] = start[1] + indent_chances[start[1]] = True + if verbose >= 4: + print("bracket depth %s indent to %s" % (depth, start[1])) + # deal with implicit string concatenation + elif token_type == tokenize.STRING or text in ('u', 'ur', 'b', 'br'): + indent_chances[start[1]] = str + + # keep track of bracket depth + if token_type == tokenize.OP: + if text in '([{': + depth += 1 + indent.append(0) + parens[row] += 1 + if verbose >= 4: + print("bracket depth %s seen, col %s, visual min = %s" % + (depth, start[1], indent[depth])) + elif text in ')]}' and depth > 0: + # parent indents should not be more than this one + prev_indent = indent.pop() or last_indent[1] + for d in range(depth): + if indent[d] > prev_indent: + indent[d] = 0 + for ind in list(indent_chances): + if ind >= prev_indent: + del indent_chances[ind] + depth -= 1 + if depth: + indent_chances[indent[depth]] = True + for idx in range(row, -1, -1): + if parens[idx]: + parens[idx] -= 1 + break + assert len(indent) == depth + 1 + if start[1] not in indent_chances: + # allow to line up tokens + indent_chances[start[1]] = text + + last_token_multiline = (start[0] != end[0]) + + if indent_next and rel_indent[-1] == 4: + yield (last_indent, "E125 continuation line does not distinguish " + "itself from next logical line") def whitespace_before_parameters(logical_line, tokens): @@ -418,16 +614,16 @@ def whitespace_before_parameters(logical_line, tokens): (prev_type == tokenize.NAME or prev_text in '}])') and # Syntax "class A (B):" is allowed, but avoid it (index < 2 or tokens[index - 2][1] != 'class') and - # Allow "return (a.foo for a in range(5))" - (not keyword.iskeyword(prev_text))): - return prev_end, "E211 whitespace before '%s'" % text + # Allow "return (a.foo for a in range(5))" + not keyword.iskeyword(prev_text)): + yield prev_end, "E211 whitespace before '%s'" % text prev_type = token_type prev_text = text prev_end = end def whitespace_around_operator(logical_line): - """ + r""" Avoid extraneous whitespace in the following situations: - More than one space around an assignment (or other) operator to @@ -439,16 +635,18 @@ def whitespace_around_operator(logical_line): E223: a = 4\t+ 5 E224: a = 4 +\t5 """ - for match in WHITESPACE_AROUND_OPERATOR_REGEX.finditer(logical_line): - before, whitespace, after = match.groups() - tab = whitespace == '\t' - offset = match.start(2) - if before in OPERATORS: - return offset, (tab and "E224 tab after operator" or - "E222 multiple spaces after operator") - elif after in OPERATORS: - return offset, (tab and "E223 tab before operator" or - "E221 multiple spaces before operator") + for match in OPERATOR_REGEX.finditer(logical_line): + before, after = match.groups() + + if '\t' in before: + yield match.start(1), "E223 tab before operator" + elif len(before) > 1: + yield match.start(1), "E221 multiple spaces before operator" + + if '\t' in after: + yield match.start(2), "E224 tab after operator" + elif len(after) > 1: + yield match.start(2), "E222 multiple spaces after operator" def missing_whitespace_around_operator(logical_line, tokens): @@ -496,11 +694,13 @@ def missing_whitespace_around_operator(logical_line, tokens): if need_space: if start != prev_end: need_space = False - elif text == '>' and prev_text == '<': + elif text == '>' and prev_text in ('<', '-'): # Tolerate the "<>" operator, even if running Python 3 + # Deal with Python 3's annotated return value "->" pass else: - return prev_end, "E225 missing whitespace around operator" + yield prev_end, "E225 missing whitespace around operator" + need_space = False elif token_type == tokenize.OP and prev_end is not None: if text == '=' and parens: # Allow keyword args or defaults: foo(bar=None). @@ -514,25 +714,25 @@ def missing_whitespace_around_operator(logical_line, tokens): if prev_text in '}])': need_space = True elif prev_type == tokenize.NAME: - if prev_text not in E225NOT_KEYWORDS: + if prev_text not in KEYWORDS: need_space = True - else: + elif prev_type not in SKIP_TOKENS: need_space = True if need_space and start == prev_end: - return prev_end, "E225 missing whitespace around operator" + yield prev_end, "E225 missing whitespace around operator" + need_space = False prev_type = token_type prev_text = text prev_end = end def whitespace_around_comma(logical_line): - """ + r""" Avoid extraneous whitespace in the following situations: - More than one space around an assignment (or other) operator to align it with another. - JCR: This should also be applied around comma etc. Note: these checks are disabled by default Okay: a = (1, 2) @@ -540,16 +740,15 @@ def whitespace_around_comma(logical_line): E242: a = (1,\t2) """ line = logical_line - for separator in ',;:': - found = line.find(separator + ' ') - if found > -1: - return found + 1, "E241 multiple spaces after '%s'" % separator - found = line.find(separator + '\t') - if found > -1: - return found + 1, "E242 tab after '%s'" % separator + for m in WHITESPACE_AFTER_COMMA_REGEX.finditer(line): + found = m.start() + 1 + if '\t' in m.group(): + yield found, "E242 tab after '%s'" % m.group()[0] + else: + yield found, "E241 multiple spaces after '%s'" % m.group()[0] -def whitespace_around_named_parameter_equals(logical_line): +def whitespace_around_named_parameter_equals(logical_line, tokens): """ Don't use spaces around the '=' sign when used to indicate a keyword argument or a default parameter value. @@ -565,16 +764,25 @@ def whitespace_around_named_parameter_equals(logical_line): E251: return magic(r = real, i = imag) """ parens = 0 - for match in WHITESPACE_AROUND_NAMED_PARAMETER_REGEX.finditer( - logical_line): - text = match.group() - if parens and len(text) == 3: - issue = "E251 no spaces around keyword / parameter equals" - return match.start(), issue - if text == '(': - parens += 1 - elif text == ')': - parens -= 1 + no_space = False + prev_end = None + for token_type, text, start, end, line in tokens: + if no_space: + no_space = False + if start != prev_end: + yield (prev_end, + "E251 no spaces around keyword / parameter equals") + elif token_type == tokenize.OP: + if text == '(': + parens += 1 + elif text == ')': + parens -= 1 + elif parens and text == '=': + no_space = True + if start != prev_end: + yield (prev_end, + "E251 no spaces around keyword / parameter equals") + prev_end = end def whitespace_before_inline_comment(logical_line, tokens): @@ -593,18 +801,15 @@ def whitespace_before_inline_comment(logical_line, tokens): """ prev_end = (0, 0) for token_type, text, start, end, line in tokens: - if token_type == tokenize.NL: - continue if token_type == tokenize.COMMENT: if not line[:start[1]].strip(): continue if prev_end[0] == start[0] and start[1] < prev_end[1] + 2: - return (prev_end, - "E261 at least two spaces before inline comment") - if (len(text) > 1 and text.startswith('# ') - or not text.startswith('# ')): - return start, "E262 inline comment should start with '# '" - else: + yield (prev_end, + "E261 at least two spaces before inline comment") + if text.startswith('# ') or not text.startswith('# '): + yield start, "E262 inline comment should start with '# '" + elif token_type != tokenize.NL: prev_end = end @@ -624,8 +829,8 @@ def imports_on_separate_lines(logical_line): line = logical_line if line.startswith('import '): found = line.find(',') - if found > -1: - return found, "E401 multiple imports on one line" + if -1 < found: + yield found, "E401 multiple imports on one line" def compound_statements(logical_line): @@ -659,24 +864,113 @@ def compound_statements(logical_line): before = line[:found] if (before.count('{') <= before.count('}') and # {'a': 1} (dict) before.count('[') <= before.count(']') and # [1:2] (slice) - not LAMBDA_REGEX.search(before)): # lambda x: x - return found, "E701 multiple statements on one line (colon)" + before.count('(') <= before.count(')') and # (Python 3 annotation) + not LAMBDA_REGEX.search(before)): # lambda x: x + yield found, "E701 multiple statements on one line (colon)" found = line.find(';') if -1 < found: - return found, "E702 multiple statements on one line (semicolon)" + yield found, "E702 multiple statements on one line (semicolon)" -def python_3000_has_key(logical_line): +def explicit_line_join(logical_line, tokens): + r""" + Avoid explicit line join between brackets. + + The preferred way of wrapping long lines is by using Python's implied line + continuation inside parentheses, brackets and braces. Long lines can be + broken over multiple lines by wrapping expressions in parentheses. These + should be used in preference to using a backslash for line continuation. + + E502: aaa = [123, \\n 123] + E502: aaa = ("bbb " \\n "ccc") + + Okay: aaa = [123,\n 123] + Okay: aaa = ("bbb "\n "ccc") + Okay: aaa = "bbb " \\n "ccc" + """ + prev_start = prev_end = parens = 0 + for token_type, text, start, end, line in tokens: + if start[0] != prev_start and parens and backslash: + yield backslash, "E502 the backslash is redundant between brackets" + if end[0] != prev_end: + if line.rstrip('\r\n').endswith('\\'): + backslash = (end[0], len(line.splitlines()[-1]) - 1) + else: + backslash = None + prev_start = prev_end = end[0] + else: + prev_start = start[0] + if token_type == tokenize.OP: + if text in '([{': + parens += 1 + elif text in ')]}': + parens -= 1 + + +def comparison_to_singleton(logical_line): + """ + Comparisons to singletons like None should always be done + with "is" or "is not", never the equality operators. + + Okay: if arg is not None: + E711: if arg != None: + E712: if arg == True: + + Also, beware of writing if x when you really mean if x is not None -- + e.g. when testing whether a variable or argument that defaults to None was + set to some other value. The other value might have a type (such as a + container) that could be false in a boolean context! """ + match = COMPARE_SINGLETON_REGEX.search(logical_line) + if match: + same = (match.group(1) == '==') + singleton = match.group(2) + msg = "'if cond is %s:'" % (('' if same else 'not ') + singleton) + if singleton in ('None',): + code = 'E711' + else: + code = 'E712' + nonzero = ((singleton == 'True' and same) or + (singleton == 'False' and not same)) + msg += " or 'if %scond:'" % ('' if nonzero else 'not ') + yield match.start(1), ("%s comparison to %s should be %s" % + (code, singleton, msg)) + + +def comparison_type(logical_line): + """ + Object type comparisons should always use isinstance() instead of + comparing types directly. + + Okay: if isinstance(obj, int): + E721: if type(obj) is type(1): + + When checking if an object is a string, keep in mind that it might be a + unicode string too! In Python 2.3, str and unicode have a common base + class, basestring, so you can do: + + Okay: if isinstance(obj, basestring): + Okay: if type(a1) is type(b1): + """ + match = COMPARE_TYPE_REGEX.search(logical_line) + if match: + inst = match.group(3) + if inst and isidentifier(inst) and inst not in SINGLETONS: + return # Allow comparison for types which are not obvious + yield match.start(1), "E721 do not compare types, use 'isinstance()'" + + +def python_3000_has_key(logical_line): + r""" The {}.has_key() method will be removed in the future version of - Python. Use the 'in' operation instead, like: - d = {"a": 1, "b": 2} - if "b" in d: - print d["b"] + Python. Use the 'in' operation instead. + + Okay: if "alph" in d:\n print d["alph"] + W601: assert d.has_key('alph') """ pos = logical_line.find('.has_key(') if pos > -1: - return pos, "W601 .has_key() is deprecated, use 'in'" + yield pos, "W601 .has_key() is deprecated, use 'in'" def python_3000_raise_comma(logical_line): @@ -688,10 +982,13 @@ def python_3000_raise_comma(logical_line): are long or include string formatting, you don't need to use line continuation characters thanks to the containing parentheses. The older form will be removed in Python 3000. + + Okay: raise DummyError("Message") + W602: raise DummyError, "Message" """ match = RAISE_COMMA_REGEX.match(logical_line) if match and not RERAISE_COMMA_REGEX.match(logical_line): - return match.start(1), "W602 deprecated form of raising exception" + yield match.start(1), "W602 deprecated form of raising exception" def python_3000_not_equal(logical_line): @@ -699,20 +996,26 @@ def python_3000_not_equal(logical_line): != can also be written <>, but this is an obsolete usage kept for backwards compatibility only. New code should always use !=. The older syntax is removed in Python 3000. + + Okay: if a != 'no': + W603: if a <> 'no': """ pos = logical_line.find('<>') if pos > -1: - return pos, "W603 '<>' is deprecated, use '!='" + yield pos, "W603 '<>' is deprecated, use '!='" def python_3000_backticks(logical_line): """ Backticks are removed in Python 3000. Use repr() instead. + + Okay: val = repr(1 + 2) + W604: val = `1 + 2` """ pos = logical_line.find('`') if pos > -1: - return pos, "W604 backticks are deprecated, use 'repr()'" + yield pos, "W604 backticks are deprecated, use 'repr()'" ############################################################################## @@ -723,31 +1026,53 @@ def python_3000_backticks(logical_line): if '' == ''.encode(): # Python 2: implicit encoding. def readlines(filename): - return open(filename).readlines() + f = open(filename) + try: + return f.readlines() + finally: + f.close() + + isidentifier = re.compile(r'[a-zA-Z_]\w*').match + stdin_get_value = sys.stdin.read else: - # Python 3: decode to latin-1. - # This function is lazy, it does not read the encoding declaration. - # XXX: use tokenize.detect_encoding() + # Python 3 def readlines(filename): - return open(filename, encoding='latin-1').readlines() + f = open(filename, 'rb') + try: + coding, lines = tokenize.detect_encoding(f.readline) + f = TextIOWrapper(f, coding, line_buffering=True) + return [l.decode(coding) for l in lines] + f.readlines() + except (LookupError, SyntaxError, UnicodeError): + f.close() + # Fall back if files are improperly declared + f = open(filename, encoding='latin-1') + return f.readlines() + finally: + f.close() + + isidentifier = str.isidentifier + stdin_get_value = TextIOWrapper(sys.stdin.buffer, errors='ignore').read +readlines.__doc__ = " Read the source code." def expand_indent(line): - """ + r""" Return the amount of indentation. Tabs are expanded to the next multiple of 8. >>> expand_indent(' ') 4 - >>> expand_indent('\\t') + >>> expand_indent('\t') 8 - >>> expand_indent(' \\t') + >>> expand_indent(' \t') 8 - >>> expand_indent(' \\t') + >>> expand_indent(' \t') 8 - >>> expand_indent(' \\t') + >>> expand_indent(' \t') 16 """ + if '\t' not in line: + return len(line) - len(line.lstrip()) result = 0 for char in line: if char == '\t': @@ -770,20 +1095,47 @@ def mute_string(text): >>> mute_string("r'abc'") "r'xxx'" """ - start = 1 - end = len(text) - 1 # String modifiers (e.g. u or r) - if text.endswith('"'): - start += text.index('"') - elif text.endswith("'"): - start += text.index("'") + start = text.index(text[-1]) + 1 + end = len(text) - 1 # Triple quotes - if text.endswith('"""') or text.endswith("'''"): + if text[-3:] in ('"""', "'''"): start += 2 end -= 2 return text[:start] + 'x' * (end - start) + text[end:] +def parse_udiff(diff, patterns=None, parent='.'): + rv = {} + path = nrows = None + for line in diff.splitlines(): + if nrows: + if line[:1] != '-': + nrows -= 1 + continue + if line[:3] == '@@ ': + row, nrows = [int(g) for g in HUNK_REGEX.match(line).groups()] + rv[path].update(range(row, row + nrows)) + elif line[:3] == '+++': + path = line[4:].split('\t', 1)[0] + if path[:2] == 'b/': + path = path[2:] + rv[path] = set() + return dict([(os.path.join(parent, path), rows) + for (path, rows) in rv.items() + if rows and filename_match(path, patterns)]) + + +def filename_match(filename, patterns, default=True): + """ + Check if patterns contains a pattern that matches filename. + If patterns is unspecified, this always returns True. + """ + if not patterns: + return default + return any(fnmatch(filename, pattern) for pattern in patterns) + + ############################################################################## # Framework to run all checks ############################################################################## @@ -794,19 +1146,13 @@ def find_checks(argument_name): Find all globally visible functions where the first argument name starts with argument_name. """ - checks = [] for name, function in globals().items(): if not inspect.isfunction(function): continue args = inspect.getargspec(function)[0] if args and args[0].startswith(argument_name): - codes = ERRORCODE_REGEX.findall(inspect.getdoc(function) or '') - for code in codes or ['']: - if not code or not ignore_code(code): - checks.append((name, function, args)) - break - checks.sort() - return checks + codes = ERRORCODE_REGEX.findall(function.__doc__ or '') + yield name, codes, function, args class Checker(object): @@ -814,17 +1160,32 @@ class Checker(object): Load a Python source file, tokenize it, check coding style. """ - def __init__(self, filename, lines=None): + def __init__(self, filename, lines=None, + options=None, report=None, **kwargs): + if options is None: + options = StyleGuide(kwargs).options + else: + assert not kwargs + self._io_error = None + self._physical_checks = options.physical_checks + self._logical_checks = options.logical_checks + self.max_line_length = options.max_line_length + self.verbose = options.verbose self.filename = filename if filename is None: self.filename = 'stdin' self.lines = lines or [] elif lines is None: - self.lines = readlines(filename) + try: + self.lines = readlines(filename) + except IOError: + exc_type, exc = sys.exc_info()[:2] + self._io_error = '%s: %s' % (exc_type.__name__, exc) + self.lines = [] else: self.lines = lines - options.counters['physical lines'] += len(self.lines) - self.errors = [] + self.report = report or options.report + self.report_error = self.report.error def readline(self): """ @@ -859,9 +1220,9 @@ def check_physical(self, line): Run all physical checks on a raw input line. """ self.physical_line = line - if self.indent_char is None and len(line) and line[0] in ' \t': + if self.indent_char is None and line[:1] in WHITESPACE: self.indent_char = line[0] - for name, check, argument_names in options.physical_checks: + for name, check, argument_names in self._physical_checks: result = self.run_check(check, argument_names) if result is not None: offset, text = result @@ -882,16 +1243,16 @@ def build_tokens_line(self): if token_type == tokenize.STRING: text = mute_string(text) if previous: - end_line, end = previous[3] - start_line, start = token[2] - if end_line != start_line: # different row - prev_text = self.lines[end_line - 1][end - 1] + end_row, end = previous[3] + start_row, start = token[2] + if end_row != start_row: # different row + prev_text = self.lines[end_row - 1][end - 1] if prev_text == ',' or (prev_text not in '{[(' and text not in '}])'): logical.append(' ') length += 1 elif end != start: # different column - fill = self.lines[end_line - 1][end:start] + fill = self.lines[end_row - 1][end:start] logical.append(fill) length += len(fill) self.mapping.append((length, token)) @@ -899,250 +1260,405 @@ def build_tokens_line(self): length += len(text) previous = token self.logical_line = ''.join(logical) - assert self.logical_line.lstrip() == self.logical_line - assert self.logical_line.rstrip() == self.logical_line + assert self.logical_line.strip() == self.logical_line def check_logical(self): """ Build a line from tokens and run all logical checks on it. """ - options.counters['logical lines'] += 1 self.build_tokens_line() + self.report.increment_logical_line() first_line = self.lines[self.mapping[0][1][2][0] - 1] indent = first_line[:self.mapping[0][1][2][1]] self.previous_indent_level = self.indent_level self.indent_level = expand_indent(indent) - if options.verbose >= 2: + if self.verbose >= 2: print(self.logical_line[:80].rstrip()) - for name, check, argument_names in options.logical_checks: - if options.verbose >= 4: + for name, check, argument_names in self._logical_checks: + if self.verbose >= 4: print(' ' + name) - result = self.run_check(check, argument_names) - if result is not None: + for result in self.run_check(check, argument_names): offset, text = result if isinstance(offset, tuple): - original_number, original_offset = offset + orig_number, orig_offset = offset else: for token_offset, token in self.mapping: if offset >= token_offset: - original_number = token[2][0] - original_offset = (token[2][1] - + offset - token_offset) - self.report_error(original_number, original_offset, - text, check) + orig_number = token[2][0] + orig_offset = (token[2][1] + offset - token_offset) + self.report_error(orig_number, orig_offset, text, check) self.previous_logical = self.logical_line + def generate_tokens(self): + if self._io_error: + self.report_error(1, 0, 'E902 %s' % self._io_error, readlines) + tokengen = tokenize.generate_tokens(self.readline_check_physical) + try: + for token in tokengen: + yield token + except (SyntaxError, tokenize.TokenError): + exc_type, exc = sys.exc_info()[:2] + offset = exc.args[1] + if len(offset) > 2: + offset = offset[1:3] + self.report_error(offset[0], offset[1], + 'E901 %s: %s' % (exc_type.__name__, exc.args[0]), + self.generate_tokens) + generate_tokens.__doc__ = " Check if the syntax is valid." + def check_all(self, expected=None, line_offset=0): """ Run all checks on the input file. """ - self.expected = expected or () - self.line_offset = line_offset + self.report.init_file(self.filename, self.lines, expected, line_offset) self.line_number = 0 - self.file_errors = 0 self.indent_char = None self.indent_level = 0 self.previous_logical = '' - self.blank_lines = 0 - self.blank_lines_before_comment = 0 self.tokens = [] + self.blank_lines = blank_lines_before_comment = 0 parens = 0 - for token in tokenize.generate_tokens(self.readline_check_physical): - if options.verbose >= 3: + for token in self.generate_tokens(): + self.tokens.append(token) + token_type, text = token[0:2] + if self.verbose >= 3: if token[2][0] == token[3][0]: pos = '[%s:%s]' % (token[2][1] or '', token[3][1]) else: pos = 'l.%s' % token[3][0] print('l.%s\t%s\t%s\t%r' % - (token[2][0], pos, tokenize.tok_name[token[0]], token[1])) - self.tokens.append(token) - token_type, text = token[0:2] - if token_type == tokenize.OP and text in '([{': - parens += 1 - if token_type == tokenize.OP and text in '}])': - parens -= 1 - if token_type == tokenize.NEWLINE and not parens: - self.check_logical() - self.blank_lines = 0 - self.blank_lines_before_comment = 0 - self.tokens = [] - if token_type == tokenize.NL and not parens: - if len(self.tokens) <= 1: - # The physical line contains only this token. - self.blank_lines += 1 - self.tokens = [] - if token_type == tokenize.COMMENT: - source_line = token[4] - token_start = token[2][1] - if source_line[:token_start].strip() == '': - self.blank_lines_before_comment = max(self.blank_lines, - self.blank_lines_before_comment) - self.blank_lines = 0 - if text.endswith('\n') and not parens: - # The comment also ends a physical line. This works around - # Python < 2.6 behaviour, which does not generate NL after - # a comment which is on a line by itself. + (token[2][0], pos, tokenize.tok_name[token[0]], text)) + if token_type == tokenize.OP: + if text in '([{': + parens += 1 + elif text in '}])': + parens -= 1 + elif not parens: + if token_type == tokenize.NEWLINE: + if self.blank_lines < blank_lines_before_comment: + self.blank_lines = blank_lines_before_comment + self.check_logical() self.tokens = [] - return self.file_errors + self.blank_lines = blank_lines_before_comment = 0 + elif token_type == tokenize.NL: + if len(self.tokens) == 1: + # The physical line contains only this token. + self.blank_lines += 1 + self.tokens = [] + elif token_type == tokenize.COMMENT and len(self.tokens) == 1: + if blank_lines_before_comment < self.blank_lines: + blank_lines_before_comment = self.blank_lines + self.blank_lines = 0 + if COMMENT_WITH_NL: + # The comment also ends a physical line + self.tokens = [] + return self.report.get_file_results() + + +class BaseReport(object): + """Collect the results of the checks.""" + print_filename = False + + def __init__(self, options): + self._benchmark_keys = options.benchmark_keys + self._ignore_code = options.ignore_code + # Results + self.elapsed = 0 + self.total_errors = 0 + self.counters = dict.fromkeys(self._benchmark_keys, 0) + self.messages = {} + + def start(self): + """Start the timer.""" + self._start_time = time.time() + + def stop(self): + """Stop the timer.""" + self.elapsed = time.time() - self._start_time + + def init_file(self, filename, lines, expected, line_offset): + """Signal a new file.""" + self.filename = filename + self.lines = lines + self.expected = expected or () + self.line_offset = line_offset + self.file_errors = 0 + self.counters['files'] += 1 + self.counters['physical lines'] += len(lines) - def report_error(self, line_number, offset, text, check): - """ - Report an error, according to options. - """ + def increment_logical_line(self): + """Signal a new logical line.""" + self.counters['logical lines'] += 1 + + def error(self, line_number, offset, text, check): + """Report an error, according to options.""" code = text[:4] - if ignore_code(code): + if self._ignore_code(code): return - self.errors.append(dict( - text = text, - type = code[0], - col = offset + 1, - lnum = self.line_offset + line_number, - )) - - -def input_file(filename): - """ - Run all checks on a Python source file. - """ - if options.verbose: - print('checking ' + filename) - errors = Checker(filename).check_all() - - -def input_dir(dirname, runner=None): - """ - Check all Python source files in this directory and all subdirectories. - """ - dirname = dirname.rstrip('/') - if excluded(dirname): - return - if runner is None: - runner = input_file - for root, dirs, files in os.walk(dirname): - if options.verbose: - print('directory ' + root) - options.counters['directories'] += 1 - dirs.sort() - for subdir in dirs[:]: - if excluded(subdir): - dirs.remove(subdir) - files.sort() - for filename in files: - if filename_match(filename) and not excluded(filename): - options.counters['files'] += 1 - runner(os.path.join(root, filename)) - - -def excluded(filename): - """ - Check if options.exclude contains a pattern that matches filename. - """ - basename = os.path.basename(filename) - for pattern in options.exclude: - if fnmatch(basename, pattern): - # print basename, 'excluded because it matches', pattern - return True + if code in self.counters: + self.counters[code] += 1 + else: + self.counters[code] = 1 + self.messages[code] = text[5:] + # Don't care about expected errors or warnings + if code in self.expected: + return + if self.print_filename and not self.file_errors: + print(self.filename) + self.file_errors += 1 + self.total_errors += 1 + return code + + def get_file_results(self): + """Return the count of errors and warnings for this file.""" + return self.file_errors + def get_count(self, prefix=''): + """Return the total count of errors and warnings.""" + return sum([self.counters[key] + for key in self.messages if key.startswith(prefix)]) -def filename_match(filename): - """ - Check if options.filename contains a pattern that matches filename. - If options.filename is unspecified, this always returns True. - """ - if not options.filename: - return True - for pattern in options.filename: - if fnmatch(filename, pattern): - return True + def get_statistics(self, prefix=''): + """ + Get statistics for message codes that start with the prefix. + prefix='' matches all errors and warnings + prefix='E' matches all errors + prefix='W' matches all warnings + prefix='E4' matches all errors that have to do with imports + """ + return ['%-7s %s %s' % (self.counters[key], key, self.messages[key]) + for key in sorted(self.messages) if key.startswith(prefix)] -def ignore_code(code): - """ - Check if options.ignore contains a prefix of the error code. - If options.select contains a prefix of the error code, do not ignore it. - """ - for select in options.select: - if code.startswith(select): - return False - for ignore in options.ignore: - if code.startswith(ignore): - return True + def print_statistics(self, prefix=''): + """Print overall statistics (number of errors and warnings).""" + for line in self.get_statistics(prefix): + print(line) + def print_benchmark(self): + """Print benchmark numbers.""" + print('%-7.2f %s' % (self.elapsed, 'seconds elapsed')) + if self.elapsed: + for key in self._benchmark_keys: + print('%-7d %s per second (%d total)' % + (self.counters[key] / self.elapsed, key, + self.counters[key])) -def reset_counters(): - for key in list(options.counters.keys()): - if key not in BENCHMARK_KEYS: - del options.counters[key] - options.messages = {} +class FileReport(BaseReport): + print_filename = True -def get_error_statistics(): - """Get error statistics.""" - return get_statistics("E") +class StandardReport(BaseReport): + """Collect and print the results of the checks.""" -def get_warning_statistics(): - """Get warning statistics.""" - return get_statistics("W") + def __init__(self, options): + super(StandardReport, self).__init__(options) + self._fmt = REPORT_FORMAT.get(options.format.lower(), + options.format) + self._repeat = options.repeat + self._show_source = options.show_source + self._show_pep8 = options.show_pep8 + def error(self, line_number, offset, text, check): + """ + Report an error, according to options. + """ + code = super(StandardReport, self).error(line_number, offset, + text, check) + if code and (self.counters[code] == 1 or self._repeat): + print(self._fmt % { + 'path': self.filename, + 'row': self.line_offset + line_number, 'col': offset + 1, + 'code': code, 'text': text[5:], + }) + if self._show_source: + if line_number > len(self.lines): + line = '' + else: + line = self.lines[line_number - 1] + print(line.rstrip()) + print(' ' * offset + '^') + if self._show_pep8: + print(check.__doc__.lstrip('\n').rstrip()) + return code -def get_statistics(prefix=''): - """ - Get statistics for message codes that start with the prefix. - prefix='' matches all errors and warnings - prefix='E' matches all errors - prefix='W' matches all warnings - prefix='E4' matches all errors that have to do with imports - """ - stats = [] - keys = list(options.messages.keys()) - keys.sort() - for key in keys: - if key.startswith(prefix): - stats.append('%-7s %s %s' % - (options.counters[key], key, options.messages[key])) - return stats +class DiffReport(StandardReport): + """Collect and print the results for the changed lines only.""" + def __init__(self, options): + super(DiffReport, self).__init__(options) + self._selected = options.selected_lines -def get_count(prefix=''): - """Return the total count of errors and warnings.""" - keys = list(options.messages.keys()) - count = 0 - for key in keys: - if key.startswith(prefix): - count += options.counters[key] - return count + def error(self, line_number, offset, text, check): + if line_number not in self._selected[self.filename]: + return + return super(DiffReport, self).error(line_number, offset, text, check) + + +class TestReport(StandardReport): + """Collect the results for the tests.""" + + def __init__(self, options): + options.benchmark_keys += ['test cases', 'failed tests'] + super(TestReport, self).__init__(options) + self._verbose = options.verbose + + def get_file_results(self): + # Check if the expected errors were found + label = '%s:%s:1' % (self.filename, self.line_offset) + codes = sorted(self.expected) + for code in codes: + if not self.counters.get(code): + self.file_errors += 1 + self.total_errors += 1 + print('%s: error %s not found' % (label, code)) + if self._verbose and not self.file_errors: + print('%s: passed (%s)' % + (label, ' '.join(codes) or 'Okay')) + self.counters['test cases'] += 1 + if self.file_errors: + self.counters['failed tests'] += 1 + # Reset counters + for key in set(self.counters) - set(self._benchmark_keys): + del self.counters[key] + self.messages = {} + return self.file_errors + def print_results(self): + results = ("%(physical lines)d lines tested: %(files)d files, " + "%(test cases)d test cases%%s." % self.counters) + if self.total_errors: + print(results % ", %s failures" % self.total_errors) + else: + print(results % "") + print("Test failed." if self.total_errors else "Test passed.") + + +class StyleGuide(object): + """Initialize a PEP-8 instance with few options.""" + + def __init__(self, *args, **kwargs): + # build options from the command line + parse_argv = kwargs.pop('parse_argv', False) + config_file = kwargs.pop('config_file', None) + options, self.paths = process_options(parse_argv=parse_argv, + config_file=config_file) + if args or kwargs: + # build options from dict + options_dict = dict(*args, **kwargs) + options.__dict__.update(options_dict) + if 'paths' in options_dict: + self.paths = options_dict['paths'] + + self.runner = self.input_file + self.options = options + + if not options.reporter: + options.reporter = BaseReport if options.quiet else StandardReport + + for index, value in enumerate(options.exclude): + options.exclude[index] = value.rstrip('/') + # Ignore all checks which are not explicitly selected + options.select = tuple(options.select or ()) + options.ignore = tuple(options.ignore or options.select and ('',)) + options.benchmark_keys = BENCHMARK_KEYS[:] + options.ignore_code = self.ignore_code + options.physical_checks = self.get_checks('physical_line') + options.logical_checks = self.get_checks('logical_line') + self.init_report() + + def init_report(self, reporter=None): + """Initialize the report instance.""" + self.options.report = (reporter or self.options.reporter)(self.options) + return self.options.report + + def check_files(self, paths=None): + """Run all checks on the paths.""" + if paths is None: + paths = self.paths + report = self.options.report + runner = self.runner + report.start() + for path in paths: + if os.path.isdir(path): + self.input_dir(path) + elif not self.excluded(path): + runner(path) + report.stop() + return report + + def input_file(self, filename, lines=None, expected=None, line_offset=0): + """Run all checks on a Python source file.""" + if self.options.verbose: + print('checking %s' % filename) + fchecker = Checker(filename, lines=lines, options=self.options) + return fchecker.check_all(expected=expected, line_offset=line_offset) + + def input_dir(self, dirname): + """Check all files in this directory and all subdirectories.""" + dirname = dirname.rstrip('/') + if self.excluded(dirname): + return 0 + counters = self.options.report.counters + verbose = self.options.verbose + filepatterns = self.options.filename + runner = self.runner + for root, dirs, files in os.walk(dirname): + if verbose: + print('directory ' + root) + counters['directories'] += 1 + for subdir in sorted(dirs): + if self.excluded(subdir): + dirs.remove(subdir) + for filename in sorted(files): + # contain a pattern that matches? + if ((filename_match(filename, filepatterns) and + not self.excluded(filename))): + runner(os.path.join(root, filename)) + + def excluded(self, filename): + """ + Check if options.exclude contains a pattern that matches filename. + """ + basename = os.path.basename(filename) + return filename_match(basename, self.options.exclude, default=False) -def print_statistics(prefix=''): - """Print overall statistics (number of errors and warnings).""" - for line in get_statistics(prefix): - print(line) + def ignore_code(self, code): + """ + Check if the error code should be ignored. + If 'options.select' contains a prefix of the error code, + return False. Else, if 'options.ignore' contains a prefix of + the error code, return True. + """ + return (code.startswith(self.options.ignore) and + not code.startswith(self.options.select)) -def print_benchmark(elapsed): - """ - Print benchmark numbers. - """ - print('%-7.2f %s' % (elapsed, 'seconds elapsed')) - for key in BENCHMARK_KEYS: - print('%-7d %s per second (%d total)' % ( - options.counters[key] / elapsed, key, - options.counters[key])) + def get_checks(self, argument_name): + """ + Find all globally visible functions where the first argument name + starts with argument_name and which contain selected tests. + """ + checks = [] + for name, codes, function, args in find_checks(argument_name): + if any(not (code and self.ignore_code(code)) for code in codes): + checks.append((name, function, args)) + return sorted(checks) -def run_tests(filename): +def init_tests(pep8style): """ - Run all the tests from a file. + Initialize testing framework. - A test file can provide many tests. Each test starts with a declaration. - This declaration is a single line starting with '#:'. + A test file can provide many tests. Each test starts with a + declaration. This declaration is a single line starting with '#:'. It declares codes of expected failures, separated by spaces or 'Okay' if no failure is expected. - If the file does not contain such declaration, it should pass all tests. - If the declaration is empty, following lines are not checked, until next - declaration. + If the file does not contain such declaration, it should pass all + tests. If the declaration is empty, following lines are not checked, + until next declaration. Examples: @@ -1150,44 +1666,46 @@ def run_tests(filename): * Following example is conform: #: Okay * Don't check these lines: #: """ - lines = readlines(filename) + ['#:\n'] - line_offset = 0 - codes = ['Okay'] - testcase = [] - for index, line in enumerate(lines): - if not line.startswith('#:'): - if codes: - # Collect the lines of the test case - testcase.append(line) - continue - if codes and index > 0: - label = '%s:%s:1' % (filename, line_offset + 1) - codes = [c for c in codes if c != 'Okay'] - # Run the checker - errors = Checker(filename, testcase).check_all(codes, line_offset) - # Check if the expected errors were found - for code in codes: - if not options.counters.get(code): - errors += 1 - print('%s: error %s not found' % (label, code)) - if options.verbose and not errors: - print('%s: passed (%s)' % (label, ' '.join(codes))) - # Keep showing errors for multiple tests - reset_counters() - # output the real line numbers - line_offset = index - # configure the expected errors - codes = line.split()[1:] - # empty the test case buffer - del testcase[:] + report = pep8style.init_report(TestReport) + runner = pep8style.input_file + + def run_tests(filename): + """Run all the tests from a file.""" + lines = readlines(filename) + ['#:\n'] + line_offset = 0 + codes = ['Okay'] + testcase = [] + count_files = report.counters['files'] + for index, line in enumerate(lines): + if not line.startswith('#:'): + if codes: + # Collect the lines of the test case + testcase.append(line) + continue + if codes and index: + codes = [c for c in codes if c != 'Okay'] + # Run the checker + runner(filename, testcase, expected=codes, + line_offset=line_offset) + # output the real line numbers + line_offset = index + 1 + # configure the expected errors + codes = line.split()[1:] + # empty the test case buffer + del testcase[:] + report.counters['files'] = count_files + 1 + return report.counters['failed tests'] + + pep8style.runner = run_tests -def selftest(): +def selftest(options): """ Test all check functions with test cases in docstrings. """ - count_passed = 0 - count_failed = 0 + count_failed = count_all = 0 + report = BaseReport(options) + counters = report.counters checks = options.physical_checks + options.logical_checks for name, check, argument_names in checks: for line in check.__doc__.splitlines(): @@ -1196,49 +1714,100 @@ def selftest(): if match is None: continue code, source = match.groups() - checker = Checker(None) + checker = Checker(None, options=options, report=report) for part in source.split(r'\n'): part = part.replace(r'\t', '\t') part = part.replace(r'\s', ' ') checker.lines.append(part + '\n') - options.quiet = 2 checker.check_all() error = None if code == 'Okay': - if len(options.counters) > len(BENCHMARK_KEYS): - codes = [key for key in options.counters.keys() - if key not in BENCHMARK_KEYS] + if len(counters) > len(options.benchmark_keys): + codes = [key for key in counters + if key not in options.benchmark_keys] error = "incorrectly found %s" % ', '.join(codes) - elif not options.counters.get(code): + elif not counters.get(code): error = "failed to find %s" % code - # Reset the counters - reset_counters() + # Keep showing errors for multiple tests + for key in set(counters) - set(options.benchmark_keys): + del counters[key] + report.messages = {} + count_all += 1 if not error: - count_passed += 1 + if options.verbose: + print("%s: %s" % (code, source)) else: count_failed += 1 - if len(checker.lines) == 1: - print("pep8.py: %s: %s" % - (error, checker.lines[0].rstrip())) - else: - print("pep8.py: %s:" % error) - for line in checker.lines: - print(line.rstrip()) - if options.verbose: - print("%d passed and %d failed." % (count_passed, count_failed)) - if count_failed: - print("Test failed.") - else: - print("Test passed.") + print("%s: %s:" % (__file__, error)) + for line in checker.lines: + print(line.rstrip()) + return count_failed, count_all -def process_options(arglist=None): - """ - Process options passed either via arglist or via command line args. - """ - global options, args +def read_config(options, args, arglist, parser): + """Read both user configuration and local configuration.""" + config = RawConfigParser() + + user_conf = options.config + if user_conf and os.path.isfile(user_conf): + if options.verbose: + print('user configuration: %s' % user_conf) + config.read(user_conf) + + parent = tail = args and os.path.abspath(os.path.commonprefix(args)) + while tail: + local_conf = os.path.join(parent, '.pep8') + if os.path.isfile(local_conf): + if options.verbose: + print('local configuration: %s' % local_conf) + config.read(local_conf) + break + parent, tail = os.path.split(parent) + + if config.has_section('pep8'): + option_list = dict([(o.dest, o.type or o.action) + for o in parser.option_list]) + + # First, read the default values + new_options, _ = parser.parse_args([]) + + # Second, parse the configuration + for opt in config.options('pep8'): + if options.verbose > 1: + print(' %s = %s' % (opt, config.get('pep8', opt))) + if opt.replace('_', '-') not in parser.config_options: + print('Unknown option: \'%s\'\n not in [%s]' % + (opt, ' '.join(parser.config_options))) + sys.exit(1) + normalized_opt = opt.replace('-', '_') + opt_type = option_list[normalized_opt] + if opt_type in ('int', 'count'): + value = config.getint('pep8', opt) + elif opt_type == 'string': + value = config.get('pep8', opt) + else: + assert opt_type in ('store_true', 'store_false') + value = config.getboolean('pep8', opt) + setattr(new_options, normalized_opt, value) + + # Third, overwrite with the command-line options + options, _ = parser.parse_args(arglist, values=new_options) + + return options + + +def process_options(arglist=None, parse_argv=False, config_file=None): + """Process options passed either via arglist or via command line args.""" + if not arglist and not parse_argv: + # Don't read the command line if the module is used as a library. + arglist = [] + if config_file is True: + config_file = DEFAULT_CONFIG parser = OptionParser(version=__version__, usage="%prog [options] input ...") + parser.config_options = [ + 'exclude', 'filename', 'select', 'ignore', 'max-line-length', 'count', + 'format', 'quiet', 'show-pep8', 'show-source', 'statistics', 'verbose'] parser.add_option('-v', '--verbose', default=0, action='count', help="print status messages, or debug with -vv") parser.add_option('-q', '--quiet', default=0, action='count', @@ -1249,12 +1818,11 @@ def process_options(arglist=None): help="show first occurrence of each error") parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE, help="exclude files or directories which match these " - "comma separated patterns (default: %s)" % - DEFAULT_EXCLUDE) + "comma separated patterns (default: %default)") parser.add_option('--filename', metavar='patterns', default='*.py', help="when parsing directories, only check filenames " - "matching these comma separated patterns (default: " - "*.py)") + "matching these comma separated patterns " + "(default: %default)") parser.add_option('--select', metavar='errors', default='', help="select errors and warnings (e.g. E,W6)") parser.add_option('--ignore', metavar='errors', default='', @@ -1262,85 +1830,100 @@ def process_options(arglist=None): parser.add_option('--show-source', action='store_true', help="show source code for each error") parser.add_option('--show-pep8', action='store_true', - help="show text of PEP 8 for each error") + help="show text of PEP 8 for each error " + "(implies --first)") parser.add_option('--statistics', action='store_true', help="count errors and warnings") parser.add_option('--count', action='store_true', help="print total number of errors and warnings " - "to standard error and set exit code to 1 if " - "total is not null") - parser.add_option('--benchmark', action='store_true', - help="measure processing speed") - parser.add_option('--testsuite', metavar='dir', - help="run regression tests from dir") + "to standard error and set exit code to 1 if " + "total is not null") parser.add_option('--max-line-length', type='int', metavar='n', default=MAX_LINE_LENGTH, - help="set maximum allowed line length (default: %d)" % - MAX_LINE_LENGTH) - parser.add_option('--doctest', action='store_true', - help="run doctest on myself") + help="set maximum allowed line length " + "(default: %default)") + parser.add_option('--format', metavar='format', default='default', + help="set the error format [default|pylint|]") + parser.add_option('--diff', action='store_true', + help="report only lines changed according to the " + "unified diff received on STDIN") + group = parser.add_option_group("Testing Options") + group.add_option('--testsuite', metavar='dir', + help="run regression tests from dir") + group.add_option('--doctest', action='store_true', + help="run doctest on myself") + group.add_option('--benchmark', action='store_true', + help="measure processing speed") + group = parser.add_option_group("Configuration", description=( + "The project options are read from the [pep8] section of the .pep8 " + "file located in any parent folder of the path(s) being processed. " + "Allowed options are: %s." % ', '.join(parser.config_options))) + group.add_option('--config', metavar='path', default=config_file, + help="config file location (default: %default)") + options, args = parser.parse_args(arglist) + options.reporter = None + if options.testsuite: args.append(options.testsuite) - if not args and not options.doctest: - parser.error('input not specified') - options.prog = os.path.basename(sys.argv[0]) - options.exclude = options.exclude.split(',') - for index in range(len(options.exclude)): - options.exclude[index] = options.exclude[index].rstrip('/') + elif not options.doctest: + if parse_argv and not args: + if os.path.exists('.pep8') or options.diff: + args = ['.'] + else: + parser.error('input not specified') + options = read_config(options, args, arglist, parser) + options.reporter = parse_argv and options.quiet == 1 and FileReport + if options.filename: options.filename = options.filename.split(',') + options.exclude = options.exclude.split(',') if options.select: options.select = options.select.split(',') - else: - options.select = [] if options.ignore: options.ignore = options.ignore.split(',') - elif options.select: - # Ignore all checks which are not explicitly selected - options.ignore = [''] - elif options.testsuite or options.doctest or not DEFAULT_IGNORE: - # For doctest and testsuite, all checks are required - options.ignore = [] - else: + elif not (options.select or + options.testsuite or options.doctest) and DEFAULT_IGNORE: # The default choice: ignore controversial checks + # (for doctest and testsuite, all checks are required) options.ignore = DEFAULT_IGNORE.split(',') - options.physical_checks = find_checks('physical_line') - options.logical_checks = find_checks('logical_line') - options.counters = dict.fromkeys(BENCHMARK_KEYS, 0) - options.messages = {} + + if options.diff: + options.reporter = DiffReport + stdin = stdin_get_value() + options.selected_lines = parse_udiff(stdin, options.filename, args[0]) + args = sorted(options.selected_lines) + return options, args def _main(): - """ - Parse options and run checks on Python source. - """ - options, args = process_options() + """Parse options and run checks on Python source.""" + pep8style = StyleGuide(parse_argv=True, config_file=True) + options = pep8style.options if options.doctest: import doctest - doctest.testmod(verbose=options.verbose) - selftest() + fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose) + fail_s, done_s = selftest(options) + count_failed = fail_s + fail_d + if not options.quiet: + count_passed = done_d + done_s - count_failed + print("%d passed and %d failed." % (count_passed, count_failed)) + print("Test failed." if count_failed else "Test passed.") + if count_failed: + sys.exit(1) if options.testsuite: - runner = run_tests - else: - runner = input_file - start_time = time.time() - for path in args: - if os.path.isdir(path): - input_dir(path, runner=runner) - elif not excluded(path): - options.counters['files'] += 1 - runner(path) - elapsed = time.time() - start_time + init_tests(pep8style) + report = pep8style.check_files() if options.statistics: - print_statistics() + report.print_statistics() if options.benchmark: - print_benchmark(elapsed) - count = get_count() - if count: + report.print_benchmark() + if options.testsuite and not options.quiet: + report.print_results() + if report.total_errors: if options.count: - sys.stderr.write(str(count) + '\n') + sys.stderr.write(str(report.total_errors) + '\n') sys.exit(1) diff --git a/pylibs/pymode.py b/pylibs/pymode.py index 294f9fe8..af9c6806 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -54,9 +54,8 @@ def mccabe(filename): def pep8(filename): PEP8 or _init_pep8() - checker = PEP8['module'].Checker(filename) - checker.check_all() - return checker.errors + style = PEP8['style'] + return style.input_file(filename) def pylint(filename): @@ -130,37 +129,27 @@ def _init_pep8(): import pep8 as p8 - class _PEP8Options(object): - # Default options taken from pep8.process_options() - verbose = False - quiet = False - repeat = True - exclude = [exc.rstrip('/') for exc in p8.DEFAULT_EXCLUDE.split(',')] - select = [] - ignore = p8.DEFAULT_IGNORE.split(',') # or []? - show_source = False - show_pep8 = False - statistics = False - count = False - benchmark = False - testsuite = '' - max_line_length = p8.MAX_LINE_LENGTH - filename = ['*.py'] - doctest = False - - logical_checks = physical_checks = None - messages = counters = None - - # default p8 setup - p8.options = _PEP8Options() - p8.options.physical_checks = p8.find_checks('physical_line') - p8.options.logical_checks = p8.find_checks('logical_line') - p8.options.counters = dict.fromkeys(p8.BENCHMARK_KEYS, 0) - p8.options.messages = {} - p8.args = [] - - PEP8['init'] = True - PEP8['module'] = p8 + class _PEP8Report(p8.BaseReport): + + def init_file(self, filename, lines, expected, line_offset): + super(_PEP8Report, self).init_file( + filename, lines, expected, line_offset) + self.errors = [] + + def error(self, line_number, offset, text, check): + code = super(_PEP8Report, self).error( + line_number, offset, text, check) + self.errors.append(dict( + text=text, + type=code, + col=offset + 1, + lnum=line_number, + )) + + def get_file_results(self): + return self.errors + + PEP8['style'] = p8.StyleGuide(reporter=_PEP8Report) def _ignore_error(e, select, ignore): From f677732a7bbe391a9ca27cc25a32fe253870c793 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 23 Jul 2012 13:43:34 +0400 Subject: [PATCH 090/513] Fixed path for windows virtualenv. --- autoload/pymode/virtualenv.vim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/autoload/pymode/virtualenv.vim b/autoload/pymode/virtualenv.vim index eea6cd1f..163634ad 100644 --- a/autoload/pymode/virtualenv.vim +++ b/autoload/pymode/virtualenv.vim @@ -18,6 +18,11 @@ import sys, vim, os ve_dir = os.environ['VIRTUAL_ENV'] ve_dir in sys.path or sys.path.insert(0, ve_dir) activate_this = os.path.join(os.path.join(ve_dir, 'bin'), 'activate_this.py') + +# Fix for windows +if not os.path.exists(activate_this): + activate_this = os.path.join(os.path.join(ve_dir, 'Scripts'), 'activate_this.py') + execfile(activate_this, dict(__file__=activate_this)) EOF endfunction "}}} From fb93f9e804bf5173f3ad2e2322c296d585cde831 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 26 Jul 2012 16:41:46 +0400 Subject: [PATCH 091/513] Added pymode modelines --- Changelog.rst | 4 +++- autoload/pymode.vim | 20 ++++++++++++++++++++ doc/pymode.txt | 27 +++++++++++++++++++++++++++ ftplugin/python/pymode.vim | 31 +++++++++++++++++-------------- plugin/pymode.vim | 9 +++++++++ pylibs/pymode.py | 26 +++++++++++++------------- 6 files changed, 89 insertions(+), 28 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 7d2065e5..79cbf1c9 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -3,7 +3,9 @@ Changelog ## 2012-XX-XX ..... ------------------- -* Update Pep8 to version 1.3.3 +* Updated Pep8 to version 1.3.3 +* Fixed virtualenv support for windows users +* Added pymode modeline ':help PythonModeModeline' ## 2012-05-24 0.6.4 ------------------- diff --git a/autoload/pymode.vim b/autoload/pymode.vim index a39286c0..cb3c9c0b 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -12,6 +12,15 @@ fun! pymode#Default(name, default) "{{{ endfunction "}}} +fun! pymode#Option(name) "{{{ + if exists('b:pymode_'.a:name) + return {'b:pymode_'.a:name} + else + return {'g:pymode_'.a:name} + endif +endfunction "}}} + + fun! pymode#QuickfixOpen(onlyRecognized, holdCursor, maxHeight, minHeight, jumpError) "{{{ " DESC: Open quickfix window " @@ -149,4 +158,15 @@ fun! pymode#BlockEnd(lnum, ...) "{{{ endfunction "}}} +fun! pymode#Modeline() "{{{ + let modeline = getline('$') + if modeline =~ '^#\s\+pymode:' + for ex in split(modeline, ':')[1:] + let [name, value] = split(ex, '=') + let {'b:pymode_'.name} = value + endfor + endif +endfunction "}}} + + " vim: fdm=marker:fdl=0 diff --git a/doc/pymode.txt b/doc/pymode.txt index 65866c90..58851e76 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -14,6 +14,7 @@ CONTENTS *Python-mode-contents* 1.Intro...................................|PythonMode| 2.Options.................................|PythonModeOptions| 2.1.Customisation details.............|PythonModeOptionsDetails| + 2.2.Modeline..........................|PythonModeModeline| 3.Default Keys............................|PythonModeKeys| 4.Commands................................|PythonModeCommands| 5.FAQ.....................................|PythonModeFAQ| @@ -118,6 +119,32 @@ PythonMode. These options should be set in your vimrc. To enable any of the below options you should put the given line in your '$HOME/.vimrc'. See |vimrc-intro|. +------------------------------------------------------------------------------ +2.2. Modeline ~ + *PythonModeModeline* + +Feature like VIM modeline `:help modeline`. Allow changing pymode options for +edited file. Pymode modeline should always be the last line in the file and +look like: + +> + # pymode:lint_ignore=E0202:doc=0:lint_write=0 +< + +Examples: + +Disable folding on current file: +> + # pymode:folding=0 +< + +Set linters and mccabe complexity. +> + # pymode:lint_checker=pip,mccabe:lint_mccabe_complexity=10 +< + +This changes will work only in current buffer. + ------------------------------------------------------------------------------ *'pymode_paths'* Values: List of strings diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 62292deb..63166722 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -3,15 +3,19 @@ if pymode#Default('b:pymode', 1) endif " Syntax highlight -if !pymode#Default('g:pymode_syntax', 1) || g:pymode_syntax +if pymode#Option('syntax') let python_highlight_all=1 endif +" Parse pymode modeline +call pymode#Modeline() + + " Options {{{ " Python indent options -if !pymode#Default('g:pymode_options_indent', 1) || g:pymode_options_indent +if pymode#Option('options_indent') setlocal cinwords=if,elif,else,for,while,try,except,finally,def,class setlocal cindent setlocal tabstop=4 @@ -25,7 +29,7 @@ if !pymode#Default('g:pymode_options_indent', 1) || g:pymode_options_indent endif " Python other options -if !pymode#Default('g:pymode_options_other', 1) || g:pymode_options_other +if pymode#Option('options_other') setlocal complete+=t setlocal formatoptions-=t if v:version > 702 && !&relativenumber @@ -40,7 +44,7 @@ endif " Documentation {{{ -if g:pymode_doc +if pymode#Option('doc') " DESC: Set commands command! -buffer -nargs=1 Pydoc call pymode#doc#Show("") @@ -56,7 +60,7 @@ endif " Lint {{{ -if g:pymode_lint +if pymode#Option('lint') let b:qf_list = [] @@ -67,15 +71,15 @@ if g:pymode_lint command! -buffer -nargs=0 PyLint :call pymode#lint#Check() " DESC: Set autocommands - if g:pymode_lint_write + if pymode#Option('lint_write') au BufWritePost PyLint endif - if g:pymode_lint_onfly + if pymode#Option('lint_onfly') au InsertLeave PyLint endif - if g:pymode_lint_message + if pymode#Option('lint_message') " DESC: Show message flag let b:show_message = 0 @@ -95,7 +99,7 @@ endif " Rope {{{ -if g:pymode_rope +if pymode#Option('rope') " DESC: Set keys exe "noremap " . g:pymode_rope_short_prefix . "g :RopeGotoDefinition" @@ -115,7 +119,7 @@ endif " Execution {{{ -if g:pymode_run +if pymode#Option('run') " DESC: Set commands command! -buffer -nargs=0 -range=% Pyrun call pymode#run#Run(, ) @@ -131,7 +135,7 @@ endif " Breakpoints {{{ -if g:pymode_breakpoint +if pymode#Option('breakpoint') " DESC: Set keys exe "nnoremap " g:pymode_breakpoint_key ":call pymode#breakpoint#Set(line('.'))" @@ -143,7 +147,7 @@ endif " Utils {{{ -if g:pymode_utils_whitespaces +if pymode#Option('utils_whitespaces') au BufWritePre :call setline(1,map(getline(1,"$"),'substitute(v:val,"\\s\\+$","","")')) endif @@ -152,7 +156,7 @@ endif " Folding {{{ -if g:pymode_folding +if pymode#Option('folding') setlocal foldmethod=expr setlocal foldexpr=pymode#folding#expr(v:lnum) @@ -162,5 +166,4 @@ endif " }}} - " vim: fdm=marker:fdl=0 diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 365aa96e..32b6cff1 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -272,7 +272,16 @@ endif " OPTION: g:pymode_folding -- bool. Enable python-mode folding for pyfiles. call pymode#Default("g:pymode_folding", 1) +" OPTION: g:pymode_syntax -- bool. Enable python-mode syntax for pyfiles. +call pymode#Default("g:pymode_syntax", 1) + " OPTION: g:pymode_utils_whitespaces -- bool. Remove unused whitespaces on save call pymode#Default("g:pymode_utils_whitespaces", 1) +" OPTION: g:pymode_options_indent -- bool. To set indent options. +call pymode#Default("g:pymode_options_indent", 1) + +" OPTION: g:pymode_options_other -- bool. To set other options. +call pymode#Default("g:pymode_options_other", 1) + " vim: fdm=marker:fdl=0 diff --git a/pylibs/pymode.py b/pylibs/pymode.py index af9c6806..17c7b854 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -9,10 +9,10 @@ def check_file(): filename = vim.current.buffer.name - checkers = vim.eval('g:pymode_lint_checker').split(',') - ignore = vim.eval("g:pymode_lint_ignore") + checkers = vim.eval("pymode#Option('lint_checker')").split(',') + ignore = vim.eval("pymode#Option('lint_ignore')") ignore = ignore and ignore.split(',') or [] - select = vim.eval("g:pymode_lint_select") + select = vim.eval("pymode#Option('lint_select')") select = select and select.split(',') or [] errors = [] @@ -20,7 +20,15 @@ def check_file(): checker = globals().get(c) if checker: try: - errors += checker(filename) + for e in checker(filename): + e.update( + col=e.get('col') or '', + text="%s [%s]" % (e.get('text', '').replace("'", "\"").split('\n')[0], c), + filename=filename, + bufnr=vim.current.buffer.number, + ) + errors.append(e) + except SyntaxError, e: errors.append(dict( lnum=e.lineno, @@ -31,14 +39,6 @@ def check_file(): except Exception, e: print e - for e in errors: - e.update( - col=e.get('col') or '', - text=e.get('text', '').replace("'", "\"").split('\n')[0], - filename=filename, - bufnr=vim.current.buffer.number, - ) - errors = filter(lambda e: _ignore_error(e, select, ignore), errors) errors = sorted(errors, key=lambda x: x['lnum']) @@ -48,7 +48,7 @@ def check_file(): def mccabe(filename): import mccabe as mc - complexity = int(vim.eval("g:pymode_lint_mccabe_complexity")) + complexity = int(vim.eval("pymode#Option('lint_mccabe_complexity')")) return mc.get_module_complexity(filename, min=complexity) From dcf559347098095d0f3a9af5e2e8f87c576a9b81 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 26 Jul 2012 17:01:30 +0400 Subject: [PATCH 092/513] Disable syntax from pymodeline --- ftplugin/python/pymode.vim | 9 +++++---- syntax/python.vim | 5 +---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 63166722..3e9fcb87 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -2,16 +2,17 @@ if pymode#Default('b:pymode', 1) finish endif + +" Parse pymode modeline +call pymode#Modeline() + + " Syntax highlight if pymode#Option('syntax') let python_highlight_all=1 endif -" Parse pymode modeline -call pymode#Modeline() - - " Options {{{ " Python indent options diff --git a/syntax/python.vim b/syntax/python.vim index 29ce5e86..2f03511b 100644 --- a/syntax/python.vim +++ b/syntax/python.vim @@ -1,10 +1,7 @@ " vim: ft=vim:fdm=marker -" OPTION: g:pymode_syntax -- bool. -call pymode#Default('g:pymode_syntax', 1) - " DESC: Disable script loading -if !g:pymode_syntax || pymode#Default('b:current_syntax', 'python') +if !pymode#Option('syntax') || pymode#Default('b:current_syntax', 'python') finish endif From a28f275adf0b3ead2474eab79e117fa87603943f Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 26 Jul 2012 17:29:34 +0400 Subject: [PATCH 093/513] Easier search --- autoload/pymode.vim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index cb3c9c0b..24fd6174 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -159,7 +159,7 @@ endfunction "}}} fun! pymode#Modeline() "{{{ - let modeline = getline('$') + let modeline = getline(prevnonblank('$')) if modeline =~ '^#\s\+pymode:' for ex in split(modeline, ':')[1:] let [name, value] = split(ex, '=') @@ -170,3 +170,6 @@ endfunction "}}} " vim: fdm=marker:fdl=0 + + + From eda55a7ff6663f94c5ecf5a5c2f4c71957765181 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 27 Jul 2012 01:00:53 +0400 Subject: [PATCH 094/513] Add troubleshooting tool --- Changelog.rst | 1 + README.rst | 12 ++++++++++++ doc/pymode.txt | 8 ++++++++ plugin/pymode.vim | 2 +- 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index 79cbf1c9..d48b51f5 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -6,6 +6,7 @@ Changelog * Updated Pep8 to version 1.3.3 * Fixed virtualenv support for windows users * Added pymode modeline ':help PythonModeModeline' +* Added diagnostic tool ':call pymode#troubleshooting#Test()' ## 2012-05-24 0.6.4 ------------------- diff --git a/README.rst b/README.rst index a238876e..9bf03296 100644 --- a/README.rst +++ b/README.rst @@ -90,6 +90,16 @@ Then rebuild **helptags** in vim:: must be enabled for use python-mode. +Troubleshooting +=============== + +If your python-mode dont work, type command: :: + + :call pymode#troubleshooting#Test() + +And fix warnings or copy output and send it to me (ex. with github issue). + + Settings ======== @@ -470,6 +480,8 @@ If you like this plugin, you can send me postcard :) My address is here: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Klenov". **Thanks for support!** +Version 0.6.5: I still haven't received any postcard, guys :( + .. _GNU lesser general public license: http://www.gnu.org/copyleft/lesser.html .. _klen: http://klen.github.com/ diff --git a/doc/pymode.txt b/doc/pymode.txt index 58851e76..5fb42a93 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -456,6 +456,12 @@ iM Operation with inner function or method. 5. FAQ ~ *PythonModeFAQ* +Python-mode dont work +--------------------- + +Run ":call pymode#troubleshooting#Test()", fix warning or send me output. + + Rope completion is very slow ---------------------------- @@ -522,6 +528,8 @@ If you like this plugin, you can send me postcard :) My address is here: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Klenov". Thanks for support! +Version 0.6.5: I still haven't received any postcard, guys :( + ------------------------------------------------------------------------------ diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 32b6cff1..ca9f5cad 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.6.4" +let g:pymode_version = "0.6.5" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 59d7fd770fe795cd59d8535bae534115127d41ee Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 27 Jul 2012 05:21:59 +0400 Subject: [PATCH 095/513] Async code checking --- autoload/pymode.vim | 2 +- autoload/pymode/lint.vim | 46 ++++++++--------- autoload/pymode/troubleshooting.vim | 57 ++++++++++++++++++++++ ftplugin/python/pymode.vim | 10 +--- plugin/pymode.vim | 1 + pylibs/pymode.py | 76 +++++++++++++++++------------ 6 files changed, 127 insertions(+), 65 deletions(-) create mode 100644 autoload/pymode/troubleshooting.vim diff --git a/autoload/pymode.vim b/autoload/pymode.vim index 24fd6174..f751e56f 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -31,7 +31,7 @@ fun! pymode#QuickfixOpen(onlyRecognized, holdCursor, maxHeight, minHeight, jumpE exe max([min([line("$"), a:maxHeight]), a:minHeight]) . "wincmd _" if a:jumpError cc - elseif a:holdCursor + elseif !a:holdCursor wincmd p endif else diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 18a91aa5..4f21ff65 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -2,8 +2,6 @@ fun! pymode#lint#Check() if !g:pymode_lint | return | endif - let b:errors = {} - if &modifiable && &modified try write @@ -13,31 +11,25 @@ fun! pymode#lint#Check() endtry endif - py check_file() - - if g:qf_list != b:qf_list + let g:pymode_lint_buffer = bufnr('%') - call setqflist(b:qf_list, 'r') + py check_file() - let g:qf_list = b:qf_list +endfunction - if g:pymode_lint_message - for v in b:qf_list - let b:errors[v['lnum']] = v['text'] - endfor - call pymode#lint#show_errormessage() - endif - if g:pymode_lint_cwindow - call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, g:pymode_lint_jump) - endif +fun! pymode#lint#Parse() - endif + call setqflist(g:qf_list, 'r') if g:pymode_lint_signs call pymode#PlaceSigns() endif + if g:pymode_lint_cwindow + call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, g:pymode_lint_jump) + endif + endfunction @@ -74,13 +66,17 @@ endfunction "}}} fun! pymode#lint#show_errormessage() "{{{ - if !len(b:errors) | return | endif - let cursor = getpos(".") - if has_key(b:errors, l:cursor[1]) - call pymode#WideMessage(b:errors[l:cursor[1]]) - let b:show_message = 1 - else - let b:show_message = 0 - echo + if g:pymode_lint_buffer != bufnr('%') + return 0 endif + let errors = getqflist() + if !len(errors) | return | endif + let [_, line, _, _] = getpos(".") + for e in errors + if e['lnum'] == line + call pymode#WideMessage(e['text']) + else + echo + endif + endfor endfunction " }}} diff --git a/autoload/pymode/troubleshooting.vim b/autoload/pymode/troubleshooting.vim new file mode 100644 index 00000000..b04f2506 --- /dev/null +++ b/autoload/pymode/troubleshooting.vim @@ -0,0 +1,57 @@ +" DESC: Get debug information about pymode problem +fun! pymode#troubleshooting#Test() "{{{ + new + setlocal buftype=nofile bufhidden=delete noswapfile nowrap + call append('0', ['Pymode diagnostic', + \ '===================', + \ 'VIM:' . v:version . ' multi_byte:' . has('multi_byte') . ' pymode: ' . g:pymode_version, + \ '']) + + let python = 1 + let output = [] + + if !exists('#filetypeplugin') + call append('$', ['WARNING: ', 'Python-mode required :filetype plugin indent on', '']) + endif + + if !has('python') + call append('$', ['WARNING: ', 'Python-mode required vim compiled with +python.', + \ '"lint, rope, run, doc, virtualenv" features disabled.', '']) + let python = 0 + endif + + call append('$', 'Pymode variables:') + call append('$', '-------------------') + call append('$', 'pymode:' . g:pymode) + call append('$', 'pymode_lint:' . g:pymode_lint) + call append('$', 'pymode_rope:' . g:pymode_rope) + call append('$', 'pymode_path:' . g:pymode_path) + call append('$', 'pymode_doc:' . g:pymode_doc) + call append('$', 'pymode_run:' . g:pymode_run) + call append('$', 'pymode_virtualenv:' . g:pymode_virtualenv) + call append('$', 'pymode_breakpoint:' . g:pymode_breakpoint) + call append('$', 'pymode_path:' . g:pymode_path) + call append('$', 'pymode_folding:' . g:pymode_folding) + call append('$', 'pymode_syntax:' . g:pymode_syntax) + call append('$', 'pymode_utils_whitespaces:' . g:pymode_utils_whitespaces) + call append('$', 'pymode_options_indent:' . g:pymode_options_indent) + call append('$', 'pymode_options_other:' . g:pymode_options_other) + + if len(g:pymode_virtualenv_enabled) + call append('$', 'Enabled virtualenv:') + call append('$', '-------------------') + call append('$', g:pymode_virtualenv_enabled) + call append('$', '') + endif + + if python + call append('$', 'VIM python paths:') + call append('$', '-----------------') +python << EOF +vim.command('let l:output = %s' % repr(sys.path)) +EOF + call append('$', output) + call append('$', '') + endif + +endfunction "}}} diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 3e9fcb87..b5ceac1d 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -63,7 +63,7 @@ endif if pymode#Option('lint') - let b:qf_list = [] + let b:errors = [] " DESC: Set commands command! -buffer -nargs=0 PyLintToggle :call pymode#lint#Toggle() @@ -81,16 +81,8 @@ if pymode#Option('lint') endif if pymode#Option('lint_message') - - " DESC: Show message flag - let b:show_message = 0 - - " DESC: Errors dict - let b:errors = {} - au CursorHold call pymode#lint#show_errormessage() au CursorMoved call pymode#lint#show_errormessage() - endif endif diff --git a/plugin/pymode.vim b/plugin/pymode.vim index ca9f5cad..de60eca6 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -57,6 +57,7 @@ endif if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint let g:qf_list = [] + let g:pymode_lint_buffer = 0 " OPTION: g:pymode_lint_write -- bool. Check code every save. call pymode#Default("g:pymode_lint_write", 1) diff --git a/pylibs/pymode.py b/pylibs/pymode.py index 17c7b854..f7613a3e 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -1,5 +1,6 @@ import StringIO import locale +import threading import vim @@ -8,41 +9,56 @@ def check_file(): - filename = vim.current.buffer.name checkers = vim.eval("pymode#Option('lint_checker')").split(',') ignore = vim.eval("pymode#Option('lint_ignore')") ignore = ignore and ignore.split(',') or [] select = vim.eval("pymode#Option('lint_select')") select = select and select.split(',') or [] - errors = [] - - for c in checkers: - checker = globals().get(c) - if checker: - try: - for e in checker(filename): - e.update( - col=e.get('col') or '', - text="%s [%s]" % (e.get('text', '').replace("'", "\"").split('\n')[0], c), - filename=filename, - bufnr=vim.current.buffer.number, - ) - errors.append(e) - - except SyntaxError, e: - errors.append(dict( - lnum=e.lineno, - col=e.offset, - text=e.args[0] - )) - break - except Exception, e: - print e - - errors = filter(lambda e: _ignore_error(e, select, ignore), errors) - errors = sorted(errors, key=lambda x: x['lnum']) - - vim.command(('let b:qf_list = %s' % repr(errors)).replace('\': u', '\': ')) + thread = Checker(vim.current.buffer, select, ignore, checkers) + thread.start() + + +class Checker(threading.Thread): + def __init__(self, buffer, select, ignore, checkers): + self.buffer = buffer.number + self.filename = buffer.name + self.select = select + self.ignore = ignore + self.checkers = checkers + super(Checker, self).__init__() + + def run(self): + + errors = [] + + for c in self.checkers: + checker = globals().get(c) + if checker: + try: + for e in checker(self.filename): + e.update( + col=e.get('col') or '', + text="%s [%s]" % (e.get('text', '').replace("'", "\"").split('\n')[0], c), + filename=self.filename, + bufnr=self.buffer, + ) + errors.append(e) + + except SyntaxError, e: + errors.append(dict( + lnum=e.lineno, + col=e.offset, + text=e.args[0] + )) + break + except Exception, e: + print e + + errors = filter(lambda e: _ignore_error(e, self.select, self.ignore), errors) + errors = sorted(errors, key=lambda x: x['lnum']) + + vim.command(('let g:qf_list = %s' % repr(errors)).replace('\': u', '\': ')) + vim.command('call pymode#lint#Parse()') def mccabe(filename): From b183a90face3e3c16c22396ca32933d179028435 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 27 Jul 2012 14:30:28 +0400 Subject: [PATCH 096/513] Fix some errors in async code checking --- Changelog.rst | 1 + autoload/pymode/lint.vim | 14 ++++++++++---- pylibs/pymode.py | 13 ++++++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index d48b51f5..186a76f8 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -7,6 +7,7 @@ Changelog * Fixed virtualenv support for windows users * Added pymode modeline ':help PythonModeModeline' * Added diagnostic tool ':call pymode#troubleshooting#Test()' +* Async code checking ## 2012-05-24 0.6.4 ------------------- diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 4f21ff65..14b08096 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -1,5 +1,6 @@ -fun! pymode#lint#Check() - +fun! pymode#lint#Check() "{{{ + " DESC: Run checkers on current file. + " if !g:pymode_lint | return | endif if &modifiable && &modified @@ -15,11 +16,12 @@ fun! pymode#lint#Check() py check_file() -endfunction +endfunction " }}} fun! pymode#lint#Parse() - + " DESC: Parse result of code checking. + " call setqflist(g:qf_list, 'r') if g:pymode_lint_signs @@ -30,6 +32,10 @@ fun! pymode#lint#Parse() call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, g:pymode_lint_jump) endif + if !len(g:qf_list) + call pymode#WideMessage('Code checking is completed. No errors found.') + endif + endfunction diff --git a/pylibs/pymode.py b/pylibs/pymode.py index f7613a3e..0606964a 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -10,10 +10,13 @@ def check_file(): checkers = vim.eval("pymode#Option('lint_checker')").split(',') - ignore = vim.eval("pymode#Option('lint_ignore')") - ignore = ignore and ignore.split(',') or [] - select = vim.eval("pymode#Option('lint_select')") - select = select and select.split(',') or [] + + ignore = set(filter(lambda i: i, vim.eval("pymode#Option('lint_ignore')").split(',') + + vim.eval("g:pymode_lint_ignore").split(','))) + + select = set(filter(lambda s: s, vim.eval("pymode#Option('lint_select')").split(',') + + vim.eval("g:pymode_lint_select").split(','))) + thread = Checker(vim.current.buffer, select, ignore, checkers) thread.start() @@ -38,7 +41,7 @@ def run(self): for e in checker(self.filename): e.update( col=e.get('col') or '', - text="%s [%s]" % (e.get('text', '').replace("'", "\"").split('\n')[0], c), + text="%s [%s]" % (e.get('text', '').strip().replace("'", "\"").split('\n')[0], c), filename=self.filename, bufnr=self.buffer, ) From 70012a6d23bf1b1ce2285ed7846bacce6b15d845 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 27 Jul 2012 16:12:45 +0400 Subject: [PATCH 097/513] Stop code checking thread when buffer leave. --- autoload/pymode/lint.vim | 15 ++++++++++++++- ftplugin/python/pymode.vim | 1 + plugin/pymode.vim | 2 +- pylibs/pymode.py | 26 ++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 14b08096..f62ba542 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -16,6 +16,8 @@ fun! pymode#lint#Check() "{{{ py check_file() + call pymode#WideMessage('Code checking is run.') + endfunction " }}} @@ -39,6 +41,15 @@ fun! pymode#lint#Parse() endfunction +fun! pymode#lint#Stop() "{{{ + " DESC: Stop async threading. + " + py stop_checkers() + call pymode#WideMessage('Code checking is aborted.') + +endfunction "}}} + + fun! pymode#lint#Toggle() "{{{ let g:pymode_lint = g:pymode_lint ? 0 : 1 call pymode#lint#toggle_win(g:pymode_lint, "Pymode lint") @@ -76,7 +87,9 @@ fun! pymode#lint#show_errormessage() "{{{ return 0 endif let errors = getqflist() - if !len(errors) | return | endif + if !len(errors) + return + endif let [_, line, _, _] = getpos(".") for e in errors if e['lnum'] == line diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index b5ceac1d..34dc3d1c 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -74,6 +74,7 @@ if pymode#Option('lint') " DESC: Set autocommands if pymode#Option('lint_write') au BufWritePost PyLint + au BufLeave call pymode#lint#Stop() endif if pymode#Option('lint_onfly') diff --git a/plugin/pymode.vim b/plugin/pymode.vim index de60eca6..e04aa913 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -116,7 +116,7 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint let g:pymode_lint_config = expand(":p:h:h") . "/pylint.ini" endif - py from pymode import check_file + py from pymode import check_file, stop_checkers endif diff --git a/pylibs/pymode.py b/pylibs/pymode.py index 0606964a..a4e0dc2e 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode.py @@ -17,10 +17,20 @@ def check_file(): select = set(filter(lambda s: s, vim.eval("pymode#Option('lint_select')").split(',') + vim.eval("g:pymode_lint_select").split(','))) + # Stop current threads + stop_checkers() + + # Create new thread thread = Checker(vim.current.buffer, select, ignore, checkers) thread.start() +def stop_checkers(): + for thread in threading.enumerate(): + if isinstance(thread, Checker): + thread.stop() + + class Checker(threading.Thread): def __init__(self, buffer, select, ignore, checkers): self.buffer = buffer.number @@ -28,13 +38,19 @@ def __init__(self, buffer, select, ignore, checkers): self.select = select self.ignore = ignore self.checkers = checkers + self._stop = threading.Event() super(Checker, self).__init__() def run(self): + " Run code checking. " errors = [] for c in self.checkers: + + if self.stopped(): + return True + checker = globals().get(c) if checker: try: @@ -57,12 +73,22 @@ def run(self): except Exception, e: print e + if self.stopped(): + return True + errors = filter(lambda e: _ignore_error(e, self.select, self.ignore), errors) errors = sorted(errors, key=lambda x: x['lnum']) vim.command(('let g:qf_list = %s' % repr(errors)).replace('\': u', '\': ')) vim.command('call pymode#lint#Parse()') + def stop(self): + " Stop code checking. " + self._stop.set() + + def stopped(self): + return self._stop.isSet() + def mccabe(filename): import mccabe as mc From dcef91458c7f77c689a9a031f4f1197b075b9017 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 27 Jul 2012 23:51:59 +0400 Subject: [PATCH 098/513] Async linter working in GVIM! :v: --- Makefile | 3 + autoload/pymode/breakpoint.vim | 9 ++ autoload/pymode/lint.vim | 11 -- autoload/pymode/queue.vim | 5 + ftplugin/python/pymode.vim | 7 +- plugin/pymode.vim | 5 +- pylibs/pymode/__init__.py | 0 pylibs/pymode/interface.py | 21 ++++ pylibs/{pymode.py => pymode/lint.py} | 147 ++++++++++++--------------- pylibs/pymode/queue.py | 59 +++++++++++ 10 files changed, 172 insertions(+), 95 deletions(-) create mode 100644 Makefile create mode 100644 autoload/pymode/queue.vim create mode 100644 pylibs/pymode/__init__.py create mode 100644 pylibs/pymode/interface.py rename pylibs/{pymode.py => pymode/lint.py} (51%) create mode 100644 pylibs/pymode/queue.py diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e2ef0d0d --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +.PHONY: clean +clean: + find . -name "*.pyc" -delete diff --git a/autoload/pymode/breakpoint.vim b/autoload/pymode/breakpoint.vim index c4e67592..7d983276 100644 --- a/autoload/pymode/breakpoint.vim +++ b/autoload/pymode/breakpoint.vim @@ -7,5 +7,14 @@ fun! pymode#breakpoint#Set(lnum) "{{{ call append(line('.')-1, repeat(' ', indent(plnum)).g:pymode_breakpoint_cmd) normal k endif + + " Disable lint + let pymode_lint = g:pymode_lint + let g:pymode_lint = 0 + + " Save file if &modifiable && &modified | write | endif + + let g:pymode_lint = pymode_lint + endfunction "}}} diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index f62ba542..b28111d6 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -16,8 +16,6 @@ fun! pymode#lint#Check() "{{{ py check_file() - call pymode#WideMessage('Code checking is run.') - endfunction " }}} @@ -41,15 +39,6 @@ fun! pymode#lint#Parse() endfunction -fun! pymode#lint#Stop() "{{{ - " DESC: Stop async threading. - " - py stop_checkers() - call pymode#WideMessage('Code checking is aborted.') - -endfunction "}}} - - fun! pymode#lint#Toggle() "{{{ let g:pymode_lint = g:pymode_lint ? 0 : 1 call pymode#lint#toggle_win(g:pymode_lint, "Pymode lint") diff --git a/autoload/pymode/queue.vim b/autoload/pymode/queue.vim new file mode 100644 index 00000000..7a62d363 --- /dev/null +++ b/autoload/pymode/queue.vim @@ -0,0 +1,5 @@ +fun! pymode#queue#Poll() "{{{ + py from pymode import queue + py queue.check_task() + call feedkeys("\\", 't') +endfunction "}}} diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 34dc3d1c..2ab93fa9 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -63,8 +63,6 @@ endif if pymode#Option('lint') - let b:errors = [] - " DESC: Set commands command! -buffer -nargs=0 PyLintToggle :call pymode#lint#Toggle() command! -buffer -nargs=0 PyLintWindowToggle :call pymode#lint#ToggleWindow() @@ -74,7 +72,7 @@ if pymode#Option('lint') " DESC: Set autocommands if pymode#Option('lint_write') au BufWritePost PyLint - au BufLeave call pymode#lint#Stop() + au BufLeave py stop_queue() endif if pymode#Option('lint_onfly') @@ -86,6 +84,9 @@ if pymode#Option('lint') au CursorMoved call pymode#lint#show_errormessage() endif + au CursorHold call pymode#queue#Poll() + set updatetime=1000 + endif " }}} diff --git a/plugin/pymode.vim b/plugin/pymode.vim index e04aa913..a4a0022a 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -116,7 +116,10 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint let g:pymode_lint_config = expand(":p:h:h") . "/pylint.ini" endif - py from pymode import check_file, stop_checkers + py from pymode.lint import check_file + py from pymode.queue import stop_queue + + au VimLeavePre * py stop_queue() endif diff --git a/pylibs/pymode/__init__.py b/pylibs/pymode/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pylibs/pymode/interface.py b/pylibs/pymode/interface.py new file mode 100644 index 00000000..7f7cd20b --- /dev/null +++ b/pylibs/pymode/interface.py @@ -0,0 +1,21 @@ +import vim + + +def get_option(name): + return vim.eval("pymode#Option('%s')" % name) + + +def get_var(name): + return vim.eval("g:pymode_%s" % name) + + +def get_current_buffer(): + return vim.current.buffer + + +def show_message(message): + vim.command("call pymode#WideMessage('%s')" % message) + + +def command(cmd): + vim.command(cmd) diff --git a/pylibs/pymode.py b/pylibs/pymode/lint.py similarity index 51% rename from pylibs/pymode.py rename to pylibs/pymode/lint.py index a4e0dc2e..633a5b8e 100644 --- a/pylibs/pymode.py +++ b/pylibs/pymode/lint.py @@ -1,99 +1,84 @@ import StringIO import locale -import threading -import vim +from .interface import get_option, get_var, get_current_buffer, command +from .queue import add_task locale.setlocale(locale.LC_CTYPE, "C") def check_file(): - checkers = vim.eval("pymode#Option('lint_checker')").split(',') - - ignore = set(filter(lambda i: i, vim.eval("pymode#Option('lint_ignore')").split(',') + - vim.eval("g:pymode_lint_ignore").split(','))) - - select = set(filter(lambda s: s, vim.eval("pymode#Option('lint_select')").split(',') + - vim.eval("g:pymode_lint_select").split(','))) - - # Stop current threads - stop_checkers() - - # Create new thread - thread = Checker(vim.current.buffer, select, ignore, checkers) - thread.start() - - -def stop_checkers(): - for thread in threading.enumerate(): - if isinstance(thread, Checker): - thread.stop() - - -class Checker(threading.Thread): - def __init__(self, buffer, select, ignore, checkers): - self.buffer = buffer.number - self.filename = buffer.name - self.select = select - self.ignore = ignore - self.checkers = checkers - self._stop = threading.Event() - super(Checker, self).__init__() - - def run(self): - " Run code checking. " - - errors = [] - - for c in self.checkers: - - if self.stopped(): - return True - - checker = globals().get(c) - if checker: - try: - for e in checker(self.filename): - e.update( - col=e.get('col') or '', - text="%s [%s]" % (e.get('text', '').strip().replace("'", "\"").split('\n')[0], c), - filename=self.filename, - bufnr=self.buffer, - ) - errors.append(e) - - except SyntaxError, e: - errors.append(dict( - lnum=e.lineno, - col=e.offset, - text=e.args[0] - )) - break - except Exception, e: - print e - - if self.stopped(): - return True + checkers = get_option('lint_checker').split(',') + + ignore = set(filter(lambda i: i, get_option('lint_ignore').split(',') + + get_var('lint_ignore').split(','))) + + select = set(filter(lambda s: s, get_option('lint_select').split(',') + + get_var('lint_select').split(','))) + + buffer = get_current_buffer() + + add_task(run_checkers, checkers=checkers, ignore=ignore, title='Code checking', callback=parse_result, buffer=buffer, select=select) + + +def run_checkers(task=None, checkers=None, ignore=None, buffer=None, select=None): + + buffer = (task and task.buffer) or buffer + filename = buffer.name + result = [] + part = 100 / len(checkers) - errors = filter(lambda e: _ignore_error(e, self.select, self.ignore), errors) - errors = sorted(errors, key=lambda x: x['lnum']) + for c in checkers: - vim.command(('let g:qf_list = %s' % repr(errors)).replace('\': u', '\': ')) - vim.command('call pymode#lint#Parse()') + checker = globals().get(c) + if not checker: + continue - def stop(self): - " Stop code checking. " - self._stop.set() + try: + for e in checker(filename): + e.update( + col=e.get('col') or 0, + text="%s [%s]" % (e.get('text', '') + .strip().replace("'", "\"").split('\n')[0], c), + filename=filename, + bufnr=buffer.number, + ) + result.append(e) - def stopped(self): - return self._stop.isSet() + except SyntaxError, e: + result.append(dict( + lnum=e.lineno, + col=e.offset or 0, + text=e.args[0], + bufnr=buffer.number, + )) + break + + except Exception, e: + print e + + if task: + task.done += part + + result = filter(lambda e: _ignore_error(e, select, ignore), result) + result = sorted(result, key=lambda x: x['lnum']) + + if task: + task.result = result + task.finished = True + task.done = 100 + + +def parse_result(result): + command(('let g:qf_list = %s' % repr(result)).replace('\': u', '\': ')) + command('call pymode#lint#Parse()') def mccabe(filename): import mccabe as mc - complexity = int(vim.eval("pymode#Option('lint_mccabe_complexity')")) + complexity = int(get_option('lint_mccabe_complexity')) return mc.get_module_complexity(filename, min=complexity) @@ -157,10 +142,11 @@ def add_message(self, msg_id, location, msg): )) PYLINT['lint'] = lint.PyLinter() - PYLINT['re'] = re.compile('^(?:.:)?[^:]+:(\d+): \[([EWRCI]+)[^\]]*\] (.*)$') + PYLINT['re'] = re.compile( + '^(?:.:)?[^:]+:(\d+): \[([EWRCI]+)[^\]]*\] (.*)$') checkers.initialize(PYLINT['lint']) - PYLINT['lint'].load_file_configuration(vim.eval("g:pymode_lint_config")) + PYLINT['lint'].load_file_configuration(get_var('lint_config')) PYLINT['lint'].set_option("output-format", "parseable") PYLINT['lint'].set_option("include-ids", 1) PYLINT['lint'].set_option("reports", 0) @@ -184,6 +170,7 @@ def init_file(self, filename, lines, expected, line_offset): def error(self, line_number, offset, text, check): code = super(_PEP8Report, self).error( line_number, offset, text, check) + self.errors.append(dict( text=text, type=code, diff --git a/pylibs/pymode/queue.py b/pylibs/pymode/queue.py new file mode 100644 index 00000000..841e251f --- /dev/null +++ b/pylibs/pymode/queue.py @@ -0,0 +1,59 @@ +import threading +from .interface import show_message +import time + + +class Task(threading.Thread): + + def __init__(self, buffer, callback=None, title=None, *args, **kwargs): + self.buffer = buffer + self._stop = threading.Event() + self.result = None + self.callback = callback + self.done = 0 + self.finished = False + self.title = title + threading.Thread.__init__(self, *args, **kwargs) + + def run(self): + " Run tasks. " + self._Thread__target(task=self, *self._Thread__args, **self._Thread__kwargs) + + # Wait for result parsing + while not self.stopped(): + time.sleep(.2) + + def stop(self): + " Stop task. " + self._stop.set() + + def stopped(self): + return self._stop.isSet() + + +def stop_queue(): + " Stop all tasks. " + for thread in threading.enumerate(): + if isinstance(thread, Task): + thread.stop() + show_message('%s stopped.' % thread.title) + + +def add_task(target, callback=None, buffer=None, title=None, *args, **kwargs): + " Add all tasks. " + + task = Task(buffer, title=title, target=target, callback=callback, args=args, kwargs=kwargs) + task.start() + + show_message('%s started.' % task.title) + + +def check_task(): + " Check tasks for result. " + for thread in threading.enumerate(): + if isinstance(thread, Task): + if thread.finished: + thread.stop() + thread.callback(thread.result) + else: + show_message('%s %s%%' % (thread.title, thread.done)) From cecff2a9fcc4b102e1a4012044a6248f55231a00 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 27 Jul 2012 23:55:26 +0400 Subject: [PATCH 099/513] Minor fixes --- autoload/pymode/lint.vim | 2 +- autoload/pymode/queue.vim | 1 - ftplugin/python/pymode.vim | 5 +++-- plugin/pymode.vim | 5 ++--- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index b28111d6..581a2f50 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -14,7 +14,7 @@ fun! pymode#lint#Check() "{{{ let g:pymode_lint_buffer = bufnr('%') - py check_file() + py lint.check_file() endfunction " }}} diff --git a/autoload/pymode/queue.vim b/autoload/pymode/queue.vim index 7a62d363..15301db3 100644 --- a/autoload/pymode/queue.vim +++ b/autoload/pymode/queue.vim @@ -1,5 +1,4 @@ fun! pymode#queue#Poll() "{{{ - py from pymode import queue py queue.check_task() call feedkeys("\\", 't') endfunction "}}} diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 2ab93fa9..933a7986 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -72,7 +72,7 @@ if pymode#Option('lint') " DESC: Set autocommands if pymode#Option('lint_write') au BufWritePost PyLint - au BufLeave py stop_queue() + au BufLeave py queue.stop_queue() endif if pymode#Option('lint_onfly') @@ -84,8 +84,9 @@ if pymode#Option('lint') au CursorMoved call pymode#lint#show_errormessage() endif + " DESC: Run queue au CursorHold call pymode#queue#Poll() - set updatetime=1000 + setlocal updatetime=1000 endif diff --git a/plugin/pymode.vim b/plugin/pymode.vim index a4a0022a..051c9aa5 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -116,10 +116,9 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint let g:pymode_lint_config = expand(":p:h:h") . "/pylint.ini" endif - py from pymode.lint import check_file - py from pymode.queue import stop_queue + py from pymode import lint, queue - au VimLeavePre * py stop_queue() + au VimLeavePre * py queue.stop_queue() endif From b1fcd4ffb5320da0fc1a1451a1e2b589576ecf2a Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sat, 28 Jul 2012 00:49:31 +0400 Subject: [PATCH 100/513] More usefull messages --- autoload/pymode.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index f751e56f..53db997d 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -115,7 +115,7 @@ fun! pymode#WideMessage(msg) "{{{ let x=&ruler | let y=&showcmd set noruler noshowcmd redraw - echo strpart(a:msg, 0, &columns-1) + echohl Debug | echo strpart(a:msg, 0, &columns-1) | echohl none let &ruler=x | let &showcmd=y endfunction "}}} From 11fda200dda3a50701712dd4eff653cca31d4a41 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sat, 28 Jul 2012 04:03:05 +0400 Subject: [PATCH 101/513] Fix folding and pylint update --- Changelog.rst | 4 +- autoload/pymode/folding.vim | 84 +- pylibs/logilab/__init__.py | 5 - pylibs/logilab/astng/COPYING | 339 +++++++++ pylibs/logilab/astng/COPYING.LESSER | 510 +++++++++++++ pylibs/logilab/astng/README | 54 ++ pylibs/logilab/astng/README.Python3 | 26 + pylibs/logilab/astng/__init__.py | 14 +- pylibs/logilab/astng/__pkginfo__.py | 7 +- pylibs/logilab/astng/brain/py2stdlib.py | 119 +++ pylibs/logilab/astng/builder.py | 6 +- pylibs/logilab/astng/inference.py | 1 + pylibs/logilab/astng/raw_building.py | 10 +- pylibs/logilab/astng/scoped_nodes.py | 9 +- pylibs/logilab/astng/setup.py | 171 ----- pylibs/logilab/common/COPYING | 339 +++++++++ pylibs/logilab/common/COPYING.LESSER | 510 +++++++++++++ pylibs/logilab/common/README | 187 +++++ pylibs/logilab/common/README.Python3 | 29 + pylibs/logilab/common/__pkginfo__.py | 14 +- pylibs/logilab/common/announce.txt | 25 + pylibs/logilab/common/compat.py | 24 +- pylibs/logilab/common/daemon.py | 106 +-- pylibs/logilab/common/decorators.py | 52 +- pylibs/logilab/common/deprecation.py | 24 +- pylibs/logilab/common/modutils.py | 4 +- pylibs/logilab/common/registry.py | 973 ++++++++++++++++++++++++ pylibs/logilab/common/setup.py | 170 ----- pylibs/logilab/common/shellutils.py | 19 +- pylibs/logilab/common/testlib.py | 11 +- pylibs/logilab/common/textutils.py | 10 +- pylibs/pylint/README | 70 ++ pylibs/pylint/README.Python3 | 37 + pylibs/pylint/__pkginfo__.py | 4 +- pylibs/pylint/checkers/__init__.py | 2 +- pylibs/pylint/checkers/base.py | 35 +- pylibs/pylint/checkers/classes.py | 84 +- pylibs/pylint/checkers/exceptions.py | 2 +- pylibs/pylint/checkers/format.py | 2 +- pylibs/pylint/checkers/misc.py | 2 +- pylibs/pylint/checkers/similar.py | 2 +- pylibs/pylint/checkers/typecheck.py | 3 + pylibs/pylint/checkers/utils.py | 88 ++- pylibs/pylint/checkers/variables.py | 28 +- pylibs/pylint/epylint.py | 149 ---- pylibs/pylint/gui.py | 452 ----------- pylibs/pylint/lint.py | 224 +++--- pylibs/pylint/pyreverse/__init__.py | 5 - pylibs/pylint/pyreverse/diadefslib.py | 231 ------ pylibs/pylint/pyreverse/diagrams.py | 247 ------ pylibs/pylint/pyreverse/main.py | 129 ---- pylibs/pylint/pyreverse/utils.py | 131 ---- pylibs/pylint/pyreverse/writer.py | 196 ----- pylibs/pylint/reporters/__init__.py | 19 +- pylibs/pylint/reporters/guireporter.py | 2 +- pylibs/pylint/reporters/text.py | 2 +- pylibs/pylint/utils.py | 7 +- pylibs/pymode/lint.py | 8 +- 58 files changed, 3769 insertions(+), 2248 deletions(-) create mode 100644 pylibs/logilab/astng/COPYING create mode 100644 pylibs/logilab/astng/COPYING.LESSER create mode 100644 pylibs/logilab/astng/README create mode 100644 pylibs/logilab/astng/README.Python3 create mode 100644 pylibs/logilab/astng/brain/py2stdlib.py delete mode 100644 pylibs/logilab/astng/setup.py create mode 100644 pylibs/logilab/common/COPYING create mode 100644 pylibs/logilab/common/COPYING.LESSER create mode 100644 pylibs/logilab/common/README create mode 100644 pylibs/logilab/common/README.Python3 create mode 100644 pylibs/logilab/common/announce.txt create mode 100644 pylibs/logilab/common/registry.py delete mode 100644 pylibs/logilab/common/setup.py create mode 100644 pylibs/pylint/README create mode 100644 pylibs/pylint/README.Python3 delete mode 100644 pylibs/pylint/epylint.py delete mode 100644 pylibs/pylint/gui.py delete mode 100644 pylibs/pylint/pyreverse/__init__.py delete mode 100644 pylibs/pylint/pyreverse/diadefslib.py delete mode 100644 pylibs/pylint/pyreverse/diagrams.py delete mode 100644 pylibs/pylint/pyreverse/main.py delete mode 100644 pylibs/pylint/pyreverse/utils.py delete mode 100644 pylibs/pylint/pyreverse/writer.py diff --git a/Changelog.rst b/Changelog.rst index 186a76f8..ccbf72ba 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,13 +1,15 @@ Changelog ========= -## 2012-XX-XX ..... +## 2012-XX-XX 0.6.5 ------------------- * Updated Pep8 to version 1.3.3 +* Updated Pylint to version 0.25.2 * Fixed virtualenv support for windows users * Added pymode modeline ':help PythonModeModeline' * Added diagnostic tool ':call pymode#troubleshooting#Test()' * Async code checking +* Improve speed of pymode folding ## 2012-05-24 0.6.4 ------------------- diff --git a/autoload/pymode/folding.vim b/autoload/pymode/folding.vim index ddc86106..8ac4b625 100644 --- a/autoload/pymode/folding.vim +++ b/autoload/pymode/folding.vim @@ -1,7 +1,8 @@ " Python-mode folding functions -let s:defpat = '^\s*\(@\|class\s.*:\|def\s\)' +let s:blank_regex = '^\s*$' +let s:def_regex = '^\s*\(class\|def\) \w\+' fun! pymode#folding#text() " {{{ @@ -25,81 +26,34 @@ fun! pymode#folding#text() " {{{ endfunction "}}} -fun! pymode#folding#indent(lnum) "{{{ - let indent = indent(pymode#BlockStart(a:lnum)) - return indent ? indent + &shiftwidth : 0 -endfunction "}}} - - fun! pymode#folding#expr(lnum) "{{{ + let line = getline(a:lnum) let indent = indent(a:lnum) - if line == '' - return getline(a:lnum+1) == '' ? '=' : '-1' - endif - - if line =~ s:defpat && getline(prevnonblank(a:lnum-1)) !~ '^\s*@' - let n = a:lnum - while getline(n) =~ '^\s*@' - let n = nextnonblank(n + 1) - endwhile - if getline(n) =~ s:defpat - return ">".(indent/&shiftwidth+1) - endif + if line =~ s:def_regex + return ">".(indent / &shiftwidth + 1) endif - let p = prevnonblank(a:lnum-1) - while p>0 && getline(p) =~ '^\s*#' - let p = prevnonblank(p-1) - endwhile - let pind = indent(p) - if getline(p) =~ s:defpat && getline(prevnonblank(a:lnum - 1)) !~ '^\s*@' - let pind = pind + &shiftwidth - elseif p==0 - let pind = 0 + if line =~ '^\s*@' + return -1 endif - if (indent>0 && indent==pind) || indent>pind - return '=' - elseif indent==0 - if pind==0 && line =~ '^#' - return 0 - elseif line !~'^#' - if 01' - elseif 0==pind && getline(prevnonblank(a:lnum-1)) =~ '^\s*#' - return '>1' - else - return '=' - endif - endif - let n = nextnonblank(a:lnum+1) - while n>0 && getline(n) =~'^\s*#' - let n = nextnonblank(n+1) - endwhile - if indent(n)==0 - return 0 - else + if line =~ s:blank_regex + let prev_line = getline(a:lnum - 1) + if prev_line =~ s:blank_regex return -1 - end - endif - let blockindent = indent(pymode#BlockStart(a:lnum)) + &shiftwidth - if blockindent==0 - return 1 + else + return foldlevel(prevnonblank(a:lnum)) + endif endif - let n = nextnonblank(a:lnum+1) - while n>0 && getline(n) =~'^\s*#' - let n = nextnonblank(n+1) - endwhile - let nind = indent(n) - if line =~ '^\s*#' && indent>=nind - return -1 - elseif line =~ '^\s*#' - return nind / &shiftwidth - else - return blockindent / &shiftwidth + + if indent == 0 + return 0 endif + + return '=' + endfunction "}}} diff --git a/pylibs/logilab/__init__.py b/pylibs/logilab/__init__.py index c9634900..e69de29b 100644 --- a/pylibs/logilab/__init__.py +++ b/pylibs/logilab/__init__.py @@ -1,5 +0,0 @@ -"""generated file, don't modify or your data will be lost""" -# try: - # __import__('pkg_resources').declare_namespace(__name__) -# except ImportError: - # pass diff --git a/pylibs/logilab/astng/COPYING b/pylibs/logilab/astng/COPYING new file mode 100644 index 00000000..d511905c --- /dev/null +++ b/pylibs/logilab/astng/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/pylibs/logilab/astng/COPYING.LESSER b/pylibs/logilab/astng/COPYING.LESSER new file mode 100644 index 00000000..2d2d780e --- /dev/null +++ b/pylibs/logilab/astng/COPYING.LESSER @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/pylibs/logilab/astng/README b/pylibs/logilab/astng/README new file mode 100644 index 00000000..db8dca1a --- /dev/null +++ b/pylibs/logilab/astng/README @@ -0,0 +1,54 @@ +ASTNG +===== + +What's this ? +------------- + +The aim of this module is to provide a common base representation of +python source code for projects such as pychecker, pyreverse, +pylint... Well, actually the development of this library is essentially +governed by pylint's needs. + +It provides a compatible representation which comes from the `_ast` module. +It rebuilds the tree generated by the builtin _ast module by recursively +walking down the AST and building an extended ast (let's call it astng ;). The +new node classes have additional methods and attributes for different usages. +They include some support for static inference and local name scopes. +Furthermore, astng builds partial trees by inspecting living objects. + +Main modules are: + +* `bases`, `node_classses` and `scoped_nodes` contain the classes for the + different type of nodes of the tree. + +* the `manager` contains a high level object to get astng trees from + source files and living objects. It maintains a cache of previously + constructed tree for quick access + + +Installation +------------ + +Extract the tarball, jump into the created directory and run :: + + python setup.py install + +For installation options, see :: + + python setup.py install --help + + +If you have any questions, please mail the +python-project@lists.logilab.org mailing list for support. See +http://lists.logilab.org/mailman/listinfo/python-projects for +subscription information and archives. + +Test +---- + +Tests are in the 'test' subdirectory. To launch the whole tests suite +at once, you may use the 'pytest' utility from logilab-common (simply +type 'pytest' from within this directory) or if you're running python +>= 2.7, using discover, for instance:: + + python -m unittest discover -p "unittest*.py" diff --git a/pylibs/logilab/astng/README.Python3 b/pylibs/logilab/astng/README.Python3 new file mode 100644 index 00000000..55ea159c --- /dev/null +++ b/pylibs/logilab/astng/README.Python3 @@ -0,0 +1,26 @@ +Python3 +======= + +Approach +-------- + +We maintain a Python 2 base and use 2to3 to generate Python 3 code. + +2to3 is integrated into the distutils installation process and will be run as a +build step when invoked by the python3 interpreter:: + + python3 setup.py install --no-compile + + +Debian +------ + +For the Debian packaging, you can use the debian.py3k/ content against +the debian/ folder:: + + cp debian.py3k/* debian/ + + +Resources +--------- +http://wiki.python.org/moin/PortingPythonToPy3k diff --git a/pylibs/logilab/astng/__init__.py b/pylibs/logilab/astng/__init__.py index 70b2f3e3..12ffce33 100644 --- a/pylibs/logilab/astng/__init__.py +++ b/pylibs/logilab/astng/__init__.py @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # copyright 2003-2010 Sylvain Thenault, all rights reserved. # contact mailto:thenault@gmail.com @@ -71,3 +71,15 @@ from logilab.astng.manager import ASTNGManager, Project MANAGER = ASTNGManager() del ASTNGManager + +# load brain plugins +from os import listdir +from os.path import join, dirname +BRAIN_MODULES_DIR = join(dirname(__file__), 'brain') +if BRAIN_MODULES_DIR not in sys.path: + # add it to the end of the list so user path take precedence + sys.path.append(BRAIN_MODULES_DIR) +# load modules in this directory +for module in listdir(BRAIN_MODULES_DIR): + if module.endswith('.py'): + __import__(module[:-3]) diff --git a/pylibs/logilab/astng/__pkginfo__.py b/pylibs/logilab/astng/__pkginfo__.py index 5832acbe..c21926d9 100644 --- a/pylibs/logilab/astng/__pkginfo__.py +++ b/pylibs/logilab/astng/__pkginfo__.py @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # copyright 2003-2010 Sylvain Thenault, all rights reserved. # contact mailto:thenault@gmail.com @@ -24,7 +24,7 @@ modname = 'astng' subpackage_of = 'logilab' -numversion = (0, 23, 0) +numversion = (0, 24, 0) version = '.'.join([str(num) for num in numversion]) install_requires = ['logilab-common >= 0.53.0'] @@ -40,5 +40,6 @@ description = "rebuild a new abstract syntax tree from Python's ast" from os.path import join -include_dirs = [join('test', 'regrtest_data'), +include_dirs = ['brain', + join('test', 'regrtest_data'), join('test', 'data'), join('test', 'data2')] diff --git a/pylibs/logilab/astng/brain/py2stdlib.py b/pylibs/logilab/astng/brain/py2stdlib.py new file mode 100644 index 00000000..4c1c3b7e --- /dev/null +++ b/pylibs/logilab/astng/brain/py2stdlib.py @@ -0,0 +1,119 @@ +"""ASTNG hooks for the Python 2 standard library. + +Currently help understanding of : + +* hashlib.md5 and hashlib.sha1 +""" + +from logilab.astng import MANAGER +from logilab.astng.builder import ASTNGBuilder + +MODULE_TRANSFORMS = {} + +def hashlib_transform(module): + fake = ASTNGBuilder(MANAGER).string_build(''' + +class md5(object): + def __init__(self, value): pass + def hexdigest(self): + return u'' + +class sha1(object): + def __init__(self, value): pass + def hexdigest(self): + return u'' + +''') + for hashfunc in ('sha1', 'md5'): + module.locals[hashfunc] = fake.locals[hashfunc] + +def collections_transform(module): + fake = ASTNGBuilder(MANAGER).string_build(''' + +class defaultdict(dict): + default_factory = None + def __missing__(self, key): pass + +class deque(object): + maxlen = 0 + def __init__(iterable=None, maxlen=None): pass + def append(self, x): pass + def appendleft(self, x): pass + def clear(self): pass + def count(self, x): return 0 + def extend(self, iterable): pass + def extendleft(self, iterable): pass + def pop(self): pass + def popleft(self): pass + def remove(self, value): pass + def reverse(self): pass + def rotate(self, n): pass + +''') + + for klass in ('deque', 'defaultdict'): + module.locals[klass] = fake.locals[klass] + +def pkg_resources_transform(module): + fake = ASTNGBuilder(MANAGER).string_build(''' + +def resource_exists(package_or_requirement, resource_name): + pass + +def resource_isdir(package_or_requirement, resource_name): + pass + +def resource_filename(package_or_requirement, resource_name): + pass + +def resource_stream(package_or_requirement, resource_name): + pass + +def resource_string(package_or_requirement, resource_name): + pass + +def resource_listdir(package_or_requirement, resource_name): + pass + +def extraction_error(): + pass + +def get_cache_path(archive_name, names=()): + pass + +def postprocess(tempname, filename): + pass + +def set_extraction_path(path): + pass + +def cleanup_resources(force=False): + pass + +''') + + for func_name, func in fake.locals.items(): + module.locals[func_name] = func + + # for func in ('resource_exists', 'resource_isdir', 'resource_filename', + # 'resource_stream', 'resource_string', 'resource_listdir', + # 'extraction_error', 'get_cache_path', 'postprocess', + # 'set_extraction_path', 'cleanup_resources'): + + # module.locals[func] = fake.locals[func] + +MODULE_TRANSFORMS['hashlib'] = hashlib_transform +MODULE_TRANSFORMS['collections'] = collections_transform +MODULE_TRANSFORMS['pkg_resources'] = pkg_resources_transform + + +def transform(module): + try: + tr = MODULE_TRANSFORMS[module.name] + except KeyError: + pass + else: + tr(module) + +from logilab.astng import MANAGER +MANAGER.register_transformer(transform) diff --git a/pylibs/logilab/astng/builder.py b/pylibs/logilab/astng/builder.py index 16c92b06..9309793d 100644 --- a/pylibs/logilab/astng/builder.py +++ b/pylibs/logilab/astng/builder.py @@ -128,7 +128,6 @@ def file_build(self, path, modname=None): # build astng representation node = self.string_build(data, modname, path) node.file_encoding = encoding - node.file_stream = stream return node def string_build(self, data, modname='', path=None): @@ -141,8 +140,9 @@ def string_build(self, data, modname='', path=None): # handle delayed assattr nodes for delayed in module._delayed_assattr: self.delayed_assattr(delayed) - for transformer in self._manager.transformers: - transformer(module) + if modname: + for transformer in self._manager.transformers: + transformer(module) return module def _data_build(self, data, modname, path): diff --git a/pylibs/logilab/astng/inference.py b/pylibs/logilab/astng/inference.py index 62bd7d96..e33ea492 100644 --- a/pylibs/logilab/astng/inference.py +++ b/pylibs/logilab/astng/inference.py @@ -245,6 +245,7 @@ def infer_subscript(self, context=None): return try: # suppose it's a Tuple/List node (attribute error else) + # XXX infer self.value? assigned = self.value.getitem(index.value, context) except AttributeError: raise InferenceError() diff --git a/pylibs/logilab/astng/raw_building.py b/pylibs/logilab/astng/raw_building.py index 395c26ec..6a098c24 100644 --- a/pylibs/logilab/astng/raw_building.py +++ b/pylibs/logilab/astng/raw_building.py @@ -240,10 +240,14 @@ def object_build(self, node, obj): member = member.im_func if isfunction(member): # verify this is not an imported function - if member.func_code.co_filename != getattr(self._module, '__file__', None): + filename = getattr(member.func_code, 'co_filename', None) + if filename is None: + assert isinstance(member, object) + object_build_methoddescriptor(node, member, name) + elif filename != getattr(self._module, '__file__', None): attach_dummy_node(node, name, member) - continue - object_build_function(node, member, name) + else: + object_build_function(node, member, name) elif isbuiltin(member): if self.imported_member(node, member, name): #if obj is object: diff --git a/pylibs/logilab/astng/scoped_nodes.py b/pylibs/logilab/astng/scoped_nodes.py index 41ad0c64..1a1242c5 100644 --- a/pylibs/logilab/astng/scoped_nodes.py +++ b/pylibs/logilab/astng/scoped_nodes.py @@ -243,6 +243,12 @@ def __init__(self, name, doc, pure_python=True): self.locals = self.globals = {} self.body = [] + @property + def file_stream(self): + if self.file is not None: + return open(self.file) + return None + def block_range(self, lineno): """return block line numbers. @@ -460,6 +466,7 @@ class ListComp(_ListComp): class Lambda(LocalsDictNodeNG, FilterStmtsMixin): _astng_fields = ('args', 'body',) + name = '' # function's type, 'function' | 'method' | 'staticmethod' | 'classmethod' type = 'function' @@ -968,5 +975,3 @@ class node yield iface if missing: raise InferenceError() - - diff --git a/pylibs/logilab/astng/setup.py b/pylibs/logilab/astng/setup.py deleted file mode 100644 index e4858056..00000000 --- a/pylibs/logilab/astng/setup.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# pylint: disable=W0404,W0622,W0704,W0613 -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-astng. -# -# logilab-astng is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-astng 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . -"""Generic Setup script, takes package info from __pkginfo__.py file. -""" -__docformat__ = "restructuredtext en" - -import os -import sys -import shutil -from os.path import isdir, exists, join - -try: - if os.environ.get('NO_SETUPTOOLS'): - raise ImportError() - from setuptools import setup - from setuptools.command import install_lib - USE_SETUPTOOLS = 1 -except ImportError: - from distutils.core import setup - from distutils.command import install_lib - USE_SETUPTOOLS = 0 - -try: - # python3 - from distutils.command.build_py import build_py_2to3 as build_py -except ImportError: - # python2.x - from distutils.command.build_py import build_py - -sys.modules.pop('__pkginfo__', None) -# import optional features -__pkginfo__ = __import__("__pkginfo__") -# import required features -from __pkginfo__ import modname, version, license, description, \ - web, author, author_email -# import optional features - -distname = getattr(__pkginfo__, 'distname', modname) -scripts = getattr(__pkginfo__, 'scripts', []) -data_files = getattr(__pkginfo__, 'data_files', None) -subpackage_of = getattr(__pkginfo__, 'subpackage_of', None) -include_dirs = getattr(__pkginfo__, 'include_dirs', []) -ext_modules = getattr(__pkginfo__, 'ext_modules', None) -install_requires = getattr(__pkginfo__, 'install_requires', None) -dependency_links = getattr(__pkginfo__, 'dependency_links', []) - -STD_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build') - -IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~') - -if exists('README'): - long_description = open('README').read() -else: - long_description = '' - -def ensure_scripts(linux_scripts): - """Creates the proper script names required for each platform - (taken from 4Suite) - """ - from distutils import util - if util.get_platform()[:3] == 'win': - scripts_ = [script + '.bat' for script in linux_scripts] - else: - scripts_ = linux_scripts - return scripts_ - -def get_packages(directory, prefix): - """return a list of subpackages for the given directory""" - result = [] - for package in os.listdir(directory): - absfile = join(directory, package) - if isdir(absfile): - if exists(join(absfile, '__init__.py')) or \ - package in ('test', 'tests'): - if prefix: - result.append('%s.%s' % (prefix, package)) - else: - result.append(package) - result += get_packages(absfile, result[-1]) - return result - -EMPTY_FILE = '''"""generated file, don't modify or your data will be lost""" -try: - __import__('pkg_resources').declare_namespace(__name__) -except ImportError: - pass -''' - -class MyInstallLib(install_lib.install_lib): - """extend install_lib command to handle package __init__.py and - include_dirs variable if necessary - """ - def run(self): - """overridden from install_lib class""" - install_lib.install_lib.run(self) - # create Products.__init__.py if needed - if subpackage_of: - product_init = join(self.install_dir, subpackage_of, '__init__.py') - if not exists(product_init): - self.announce('creating %s' % product_init) - stream = open(product_init, 'w') - stream.write(EMPTY_FILE) - stream.close() - # manually install included directories if any - if include_dirs: - if subpackage_of: - base = join(subpackage_of, modname) - else: - base = modname - for directory in include_dirs: - dest = join(self.install_dir, base, directory) - shutil.rmtree(dest, ignore_errors=True) - shutil.copytree(directory, dest) - -def install(**kwargs): - """setup entry point""" - if USE_SETUPTOOLS: - if '--force-manifest' in sys.argv: - sys.argv.remove('--force-manifest') - # install-layout option was introduced in 2.5.3-1~exp1 - elif sys.version_info < (2, 5, 4) and '--install-layout=deb' in sys.argv: - sys.argv.remove('--install-layout=deb') - if subpackage_of: - package = subpackage_of + '.' + modname - kwargs['package_dir'] = {package : '.'} - packages = [package] + get_packages(os.getcwd(), package) - if USE_SETUPTOOLS: - kwargs['namespace_packages'] = [subpackage_of] - else: - kwargs['package_dir'] = {modname : '.'} - packages = [modname] + get_packages(os.getcwd(), modname) - if USE_SETUPTOOLS and install_requires: - kwargs['install_requires'] = install_requires - kwargs['dependency_links'] = dependency_links - kwargs['packages'] = packages - return setup(name = distname, - version = version, - license = license, - description = description, - long_description = long_description, - author = author, - author_email = author_email, - url = web, - scripts = ensure_scripts(scripts), - data_files = data_files, - ext_modules = ext_modules, - cmdclass = {'install_lib': MyInstallLib, - 'build_py': build_py}, - **kwargs - ) - -if __name__ == '__main__' : - install() diff --git a/pylibs/logilab/common/COPYING b/pylibs/logilab/common/COPYING new file mode 100644 index 00000000..d511905c --- /dev/null +++ b/pylibs/logilab/common/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/pylibs/logilab/common/COPYING.LESSER b/pylibs/logilab/common/COPYING.LESSER new file mode 100644 index 00000000..2d2d780e --- /dev/null +++ b/pylibs/logilab/common/COPYING.LESSER @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/pylibs/logilab/common/README b/pylibs/logilab/common/README new file mode 100644 index 00000000..9eb6b92a --- /dev/null +++ b/pylibs/logilab/common/README @@ -0,0 +1,187 @@ +Logilab's common library +======================== + +What's this ? +------------- + +This package contains some modules used by differents Logilab's projects. + +It is released under the GNU Lesser General Public License. + +There is no documentation available yet but the source code should be clean and +well documented. + +Designed to ease: + +* handling command line options and configuration files +* writing interactive command line tools +* manipulation of files and character strings +* manipulation of common structures such as graph, tree, and pattern such as visitor +* generating text and HTML reports +* accessing some external libraries such as OmniORB_, Pyro_... +* more... + + +Installation +------------ + +Extract the tarball, jump into the created directory and run :: + + python setup.py install + +For installation options, see :: + + python setup.py install --help + + +Provided modules +---------------- + +Here is a brief description of the available modules. + +Modules providing high-level features +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* `cache`, a cache implementation with a least recently used algorithm. + +* `changelog`, a tiny library to manipulate our simplified ChangeLog file format. + +* `clcommands`, high-level classes to define command line programs handling + different subcommands. It is based on `configuration` to get easy command line + / configuration file handling. + +* `cli`, a base class for interactive programs using the command line. + +* `configuration`, some classes to handle unified configuration from both + command line (using optparse) and configuration file (using ConfigParser). + +* `dbf`, read Visual Fox Pro DBF files. + +* `proc`, interface to Linux /proc. + +* `umessage`, unicode email support. + +* `ureports`, micro-reports, a way to create simple reports using python objects + without care of the final formatting. ReST and html formatters are provided. + + +Modules providing low-level functions and structures +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* `compat`, provides a transparent compatibility layer between different python + versions. + +* `date`, a set of date manipulation functions. + +* `daemon`, a daemon function and mix-in class to properly start an Unix daemon + process. + +* `decorators`, function decorators such as cached, timed... + +* `deprecation`, decorator, metaclass & all to mark functions / classes as + deprecated or moved + +* `fileutils`, some file / file path manipulation utilities. + +* `graph`, graph manipulations functions such as cycle detection, bases for dot + file generation. + +* `modutils`, python module manipulation functions. + +* `shellutils`, some powerful shell like functions to replace shell scripts with + python scripts. + +* `tasksqueue`, a prioritized tasks queue implementation. + +* `textutils`, some text manipulation functions (ansi colorization, line wrapping, + rest support...). + +* `tree`, base class to represent tree structure, and some others to make it + works with the visitor implementation (see below). + +* `visitor`, a generic visitor pattern implementation. + + +Modules extending some standard modules +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* `debugger`, `pdb` customization. + +* `logging_ext`, extensions to `logging` module such as a colorized formatter + and an easier initialization function. + +* `optik_ext`, defines some new option types (regexp, csv, color, date, etc.) + for `optik` / `optparse` + +* `xmlrpcutils`, auth support for XML-RPC + + +Modules extending some external modules +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* `corbautils`, useful functions for use with the OmniORB_ CORBA library. + +* `hg`, some Mercurial_ utility functions. + +* `pdf_ext`, pdf and fdf file manipulations, with pdftk. + +* `pyro_ext`, some Pyro_ utility functions. + +* `sphinx_ext`, Sphinx_ plugin defining a `autodocstring` directive. + +* `vcgutils` , utilities functions to generate file readable with Georg Sander's + vcg tool (Visualization of Compiler Graphs). + + +To be deprecated modules +~~~~~~~~~~~~~~~~~~~~~~~~ + +Those `logilab.common` modules will much probably be deprecated in future +versions: + +* `testlib`: use `unittest2`_ instead +* `pytest`: use `discover`_ instead +* `interface`: use `zope.interface`_ if you really want this +* `table`, `xmlutils`: is that used? +* `sphinxutils`: we won't go that way imo (i == syt) + + +Deprecated modules +~~~~~~~~~~~~~~~~~~ + +Those `logilab.common` modules are only there for backward compatibility. They +can go away at anytime. + +* `optparser`: use `clcommands` instead + +* `adbh`, `db`, `sqlgen`: see `logilab.database`_ instead + +* `contexts`: content move to `shellutils` + +* `html`: deprecated without replacement + + +Comments, support, bug reports +------------------------------ + +Project page http://www.logilab.org/project/logilab-common + +Use the python-projects@lists.logilab.org mailing list. Since we do not have +publicly available bug tracker yet, bug reports should be emailed +there too. + +You can subscribe to this mailing list at +http://lists.logilab.org/mailman/listinfo/python-projects + +Archives are available at +http://lists.logilab.org/pipermail/python-projects/ + + +.. _Pyro: http://pyro.sourceforge.net/ +.. _OmniORB: http://omniorb.sourceforge.net/ +.. _Mercurial: http://mercurial.selenic.com +.. _Sphinx: http://sphinx.pocoo.org/ +.. _`logilab.database`: http://www.logilab.org/project/logilab-database/ +.. _`unittest2`: http://pypi.python.org/pypi/unittest2 +.. _`discover`: http://pypi.python.org/pypi/discover +.. _`zope.interface`: http://pypi.python.org/pypi/zope.interface diff --git a/pylibs/logilab/common/README.Python3 b/pylibs/logilab/common/README.Python3 new file mode 100644 index 00000000..10009486 --- /dev/null +++ b/pylibs/logilab/common/README.Python3 @@ -0,0 +1,29 @@ +Python3 +======= + +Approach +-------- + +We maintain a Python 2 base and use 2to3 to generate Python 3 code. + +2to3 is integrated into the distutils installation process and will be run as a +build step when invoked by the python3 interpreter:: + + python3 setup.py install + +Tests +----- + +Set your PYTHONPATH and run pytest3 against the test directory. + +Debian +------ + +For the Debian packaging of python3-logilab-common, you can use the debian.sid/ +content against the debian/ folder:: + + cp debian.sid/* debian/ + +Resources +--------- +http://wiki.python.org/moin/PortingPythonToPy3k diff --git a/pylibs/logilab/common/__pkginfo__.py b/pylibs/logilab/common/__pkginfo__.py index a8a937be..d6c63323 100644 --- a/pylibs/logilab/common/__pkginfo__.py +++ b/pylibs/logilab/common/__pkginfo__.py @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of logilab-common. @@ -17,13 +17,14 @@ # with logilab-common. If not, see . """logilab.common packaging information""" __docformat__ = "restructuredtext en" +import sys distname = 'logilab-common' modname = 'common' subpackage_of = 'logilab' subpackage_master = True -numversion = (0, 56, 2) +numversion = (0, 58, 0) version = '.'.join([str(num) for num in numversion]) license = 'LGPL' # 2.1 or later @@ -39,4 +40,11 @@ scripts = [join('bin', 'pytest')] include_dirs = [join('test', 'data')] -install_requires = ['unittest2 >= 0.5.1'] +if sys.version_info < (2, 7): + install_requires = ['unittest2 >= 0.5.1'] + +classifiers = ["Topic :: Utilities", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + ] diff --git a/pylibs/logilab/common/announce.txt b/pylibs/logilab/common/announce.txt new file mode 100644 index 00000000..97960f9b --- /dev/null +++ b/pylibs/logilab/common/announce.txt @@ -0,0 +1,25 @@ +I'm pleased to announce the %(VERSION)s release of %(DISTNAME)s. + +What's new ? +------------ +%(CHANGELOG)s + + +What is %(DISTNAME)s ? +------------------------ +%(LONG_DESC)s + + +Home page +--------- +%(WEB)s + +Download +-------- +%(FTP)s + +Mailing list +------------ +%(MAILINGLIST)s + +%(ADDITIONAL_DESCR)s diff --git a/pylibs/logilab/common/compat.py b/pylibs/logilab/common/compat.py index 943b8175..8983ece9 100644 --- a/pylibs/logilab/common/compat.py +++ b/pylibs/logilab/common/compat.py @@ -20,7 +20,7 @@ 2.5, making them available in for earlier versions of python. See another compatibility snippets from other projects: - + :mod:`lib2to3.fixes` :mod:`coverage.backward` :mod:`unittest2.compatibility` @@ -32,6 +32,7 @@ import os import sys +import types from warnings import warn import __builtin__ as builtins # 2to3 will tranform '__builtin__' to 'builtins' @@ -50,14 +51,23 @@ def str_to_bytes(string): def str_encode(string, encoding): return str(string) -# XXX shouldn't we remove this and just let 2to3 do his job ? +# XXX callable built-in seems back in all python versions try: - callable = callable -except NameError:# callable removed from py3k - import collections + callable = builtins.callable +except AttributeError: + from collections import Callable def callable(something): - return isinstance(something, collections.Callable) - del collections + return isinstance(something, Callable) + del Callable + +# See also http://bugs.python.org/issue11776 +if sys.version_info[0] == 3: + def method_type(callable, instance, klass): + # api change. klass is no more considered + return types.MethodType(callable, instance) +else: + # alias types otherwise + method_type = types.MethodType if sys.version_info < (3, 0): raw_input = raw_input diff --git a/pylibs/logilab/common/daemon.py b/pylibs/logilab/common/daemon.py index 2eedca56..66a69601 100644 --- a/pylibs/logilab/common/daemon.py +++ b/pylibs/logilab/common/daemon.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License along # with logilab-common. If not, see . -"""A daemonize function (for Unices) and daemon mix-in class""" +"""A daemonize function (for Unices)""" __docformat__ = "restructuredtext en" @@ -46,6 +46,7 @@ def setugid(user): raise OSError(err, os.strerror(err), 'initgroups') os.setgid(passwd.pw_gid) os.setuid(passwd.pw_uid) + os.environ['HOME'] = passwd.pw_dir def daemonize(pidfile=None, uid=None, umask=077): @@ -92,109 +93,8 @@ def daemonize(pidfile=None, uid=None, umask=077): f = file(pidfile, 'w') f.write(str(os.getpid())) f.close() + os.chmod(pidfile, 0644) # change process uid if uid: setugid(uid) return None - - -class DaemonMixIn: - """Mixin to make a daemon from watchers/queriers. - """ - - def __init__(self, configmod) : - self.delay = configmod.DELAY - self.name = str(self.__class__).split('.')[-1] - self._pid_file = os.path.join('/tmp', '%s.pid'%self.name) - if os.path.exists(self._pid_file): - raise Exception('''Another instance of %s must be running. -If it i not the case, remove the file %s''' % (self.name, self._pid_file)) - self._alive = 1 - self._sleeping = 0 - self.config = configmod - - def _daemonize(self): - if not self.config.NODETACH: - if daemonize(self._pid_file) is None: - # put signal handler - signal.signal(signal.SIGTERM, self.signal_handler) - signal.signal(signal.SIGHUP, self.signal_handler) - else: - return -1 - - def run(self): - """ optionally go in daemon mode and - do what concrete class has to do and pauses for delay between runs - If self.delay is negative, do a pause before starting - """ - if self._daemonize() == -1: - return - if self.delay < 0: - self.delay = -self.delay - time.sleep(self.delay) - while True: - try: - self._run() - except Exception, ex: - # display for info, sleep, and hope the problem will be solved - # later. - self.config.exception('Internal error: %s', ex) - if not self._alive: - break - try: - self._sleeping = 1 - time.sleep(self.delay) - self._sleeping = 0 - except SystemExit: - break - self.config.info('%s instance exited', self.name) - # remove pid file - os.remove(self._pid_file) - - def signal_handler(self, sig_num, stack_frame): - if sig_num == signal.SIGTERM: - if self._sleeping: - # we are sleeping so we can exit without fear - self.config.debug('exit on SIGTERM') - sys.exit(0) - else: - self.config.debug('exit on SIGTERM (on next turn)') - self._alive = 0 - elif sig_num == signal.SIGHUP: - self.config.info('reloading configuration on SIGHUP') - reload(self.config) - - def _run(self): - """should be overridden in the mixed class""" - raise NotImplementedError() - - -import logging -from logilab.common.logging_ext import set_log_methods -set_log_methods(DaemonMixIn, logging.getLogger('lgc.daemon')) - -## command line utilities ###################################################### - -L_OPTIONS = ["help", "log=", "delay=", 'no-detach'] -S_OPTIONS = 'hl:d:n' - -def print_help(modconfig): - print """ --help or -h - displays this message - --log - log treshold (7 record everything, 0 record only emergency.) - Defaults to %s - --delay - the number of seconds between two runs. - Defaults to %s""" % (modconfig.LOG_TRESHOLD, modconfig.DELAY) - -def handle_option(modconfig, opt_name, opt_value, help_meth): - if opt_name in ('-h', '--help'): - help_meth() - sys.exit(0) - elif opt_name in ('-l', '--log'): - modconfig.LOG_TRESHOLD = int(opt_value) - elif opt_name in ('-d', '--delay'): - modconfig.DELAY = int(opt_value) - elif opt_name in ('-n', '--no-detach'): - modconfig.NODETACH = 1 diff --git a/pylibs/logilab/common/decorators.py b/pylibs/logilab/common/decorators.py index 7bb08fc9..43c36521 100644 --- a/pylibs/logilab/common/decorators.py +++ b/pylibs/logilab/common/decorators.py @@ -18,10 +18,11 @@ """ A few useful function/method decorators. """ __docformat__ = "restructuredtext en" -import types -import sys, re +import sys from time import clock, time +from logilab.common.compat import callable, method_type + # XXX rewrite so we can use the decorator syntax when keyarg has to be specified def _is_generator_function(callableobj): @@ -116,6 +117,45 @@ def cached(callableobj=None, keyarg=None, **kwargs): else: return decorator(callableobj) + +class cachedproperty(object): + """ Provides a cached property equivalent to the stacking of + @cached and @property, but more efficient. + + After first usage, the becomes part of the object's + __dict__. Doing: + + del obj. empties the cache. + + Idea taken from the pyramid_ framework and the mercurial_ project. + + .. _pyramid: http://pypi.python.org/pypi/pyramid + .. _mercurial: http://pypi.python.org/pypi/Mercurial + """ + __slots__ = ('wrapped',) + + def __init__(self, wrapped): + try: + wrapped.__name__ + except AttributeError: + raise TypeError('%s must have a __name__ attribute' % + wrapped) + self.wrapped = wrapped + + @property + def __doc__(self): + doc = getattr(self.wrapped, '__doc__', None) + return ('%s' + % ('\n%s' % doc if doc else '')) + + def __get__(self, inst, objtype=None): + if inst is None: + return self + val = self.wrapped(inst) + setattr(inst, self.wrapped.__name__, val) + return val + + def get_cache_impl(obj, funcname): cls = obj.__class__ member = getattr(cls, funcname) @@ -175,8 +215,8 @@ def __init__(self, func): self.func = func def __get__(self, instance, objtype): if instance is None: - return types.MethodType(self.func, objtype, objtype.__class__) - return types.MethodType(self.func, instance, objtype) + return method_type(self.func, objtype, objtype.__class__) + return method_type(self.func, instance, objtype) def __set__(self, instance, value): raise AttributeError("can't set attribute") @@ -233,8 +273,8 @@ def decorator(func): raise AttributeError('%s has no __name__ attribute: ' 'you should provide an explicit `methodname`' % func) - if callable(func): - setattr(klass, name, types.MethodType(func, None, klass)) + if callable(func) and sys.version_info < (3, 0): + setattr(klass, name, method_type(func, None, klass)) else: # likely a property # this is quite borderline but usage already in the wild ... diff --git a/pylibs/logilab/common/deprecation.py b/pylibs/logilab/common/deprecation.py index db0829ae..c14bd2af 100644 --- a/pylibs/logilab/common/deprecation.py +++ b/pylibs/logilab/common/deprecation.py @@ -71,7 +71,7 @@ def class_moved(new_class, old_name=None, message=None): old_name, new_class.__module__, new_class.__name__) return class_renamed(old_name, new_class, message) -def deprecated(reason=None, stacklevel=2): +def deprecated(reason=None, stacklevel=2, name=None, doc=None): """Decorator that raises a DeprecationWarning to print a message when the decorated function is called. """ @@ -83,10 +83,10 @@ def wrapped(*args, **kwargs): warn(message, DeprecationWarning, stacklevel=stacklevel) return func(*args, **kwargs) try: - wrapped.__name__ = func.__name__ + wrapped.__name__ = name or func.__name__ except TypeError: # readonly attribute in 2.3 pass - wrapped.__doc__ = func.__doc__ + wrapped.__doc__ = doc or func.__doc__ return wrapped return deprecated_decorator @@ -110,3 +110,21 @@ def callnew(*args, **kwargs): return callnew + +class DeprecationWrapper(object): + """proxy to print a warning on access to any attribute of the wrapped object + """ + def __init__(self, proxied, msg=None): + self._proxied = proxied + self._msg = msg + + def __getattr__(self, attr): + warn(self._msg, DeprecationWarning, stacklevel=2) + return getattr(self._proxied, attr) + + def __setattr__(self, attr, value): + if attr in ('_proxied', '_msg'): + self.__dict__[attr] = value + else: + warn(self._msg, DeprecationWarning, stacklevel=2) + setattr(self._proxied, attr, value) diff --git a/pylibs/logilab/common/modutils.py b/pylibs/logilab/common/modutils.py index ce0c2971..95c400a6 100644 --- a/pylibs/logilab/common/modutils.py +++ b/pylibs/logilab/common/modutils.py @@ -92,7 +92,7 @@ def __call__(self, *args, **kwargs): def load_module_from_name(dotted_name, path=None, use_sys=1): - """Load a Python module from it's name. + """Load a Python module from its name. :type dotted_name: str :param dotted_name: python name of a module or package @@ -117,7 +117,7 @@ def load_module_from_name(dotted_name, path=None, use_sys=1): def load_module_from_modpath(parts, path=None, use_sys=1): - """Load a python module from it's splitted name. + """Load a python module from its splitted name. :type parts: list(str) or tuple(str) :param parts: diff --git a/pylibs/logilab/common/registry.py b/pylibs/logilab/common/registry.py new file mode 100644 index 00000000..29f3c56b --- /dev/null +++ b/pylibs/logilab/common/registry.py @@ -0,0 +1,973 @@ +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of Logilab-common. +# +# Logilab-common is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# Logilab-common 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 Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with Logilab-common. If not, see . +"""This module provides bases for predicates dispatching (the pattern in use +here is similar to what's refered as multi-dispatch or predicate-dispatch in the +literature, though a bit different since the idea is to select across different +implementation 'e.g. classes), not to dispatch a message to a function or +method. It contains the following classes: + +* :class:`RegistryStore`, the top level object which loads implementation + objects and stores them into registries. You'll usually use it to access + registries and their contained objects; + +* :class:`Registry`, the base class which contains objects semantically grouped + (for instance, sharing a same API, hence the 'implementation' name). You'll + use it to select the proper implementation according to a context. Notice you + may use registries on their own without using the store. + +.. Note:: + + implementation objects are usually designed to be accessed through the + registry and not by direct instantiation, besides to use it as base classe. + +The selection procedure is delegated to a selector, which is responsible for +scoring the object according to some context. At the end of the selection, if an +implementation has been found, an instance of this class is returned. A selector +is built from one or more predicates combined together using AND, OR, NOT +operators (actually `&`, `|` and `~`). You'll thus find some base classes to +build predicates: + +* :class:`Predicate`, the abstract base predicate class + +* :class:`AndPredicate`, :class:`OrPredicate`, :class:`NotPredicate`, which you + shouldn't have to use directly. You'll use `&`, `|` and '~' operators between + predicates directly + +* :func:`objectify_predicate` + +You'll eventually find one concrete predicate: :class:`yes` + +.. autoclass:: RegistryStore +.. autoclass:: Registry + +Predicates +---------- +.. autoclass:: Predicate +.. autofunc:: objectify_predicate +.. autoclass:: yes + +Debugging +--------- +.. autoclass:: traced_selection + +Exceptions +---------- +.. autoclass:: RegistryException +.. autoclass:: RegistryNotFound +.. autoclass:: ObjectNotFound +.. autoclass:: NoSelectableObject +""" + +__docformat__ = "restructuredtext en" + +import sys +import types +import weakref +from os import listdir, stat +from os.path import join, isdir, exists +from logging import getLogger + +from logilab.common.logging_ext import set_log_methods + + +class RegistryException(Exception): + """Base class for registry exception.""" + +class RegistryNotFound(RegistryException): + """Raised when an unknown registry is requested. + + This is usually a programming/typo error. + """ + +class ObjectNotFound(RegistryException): + """Raised when an unregistered object is requested. + + This may be a programming/typo or a misconfiguration error. + """ + +class NoSelectableObject(RegistryException): + """Raised when no object is selectable for a given context.""" + def __init__(self, args, kwargs, objects): + self.args = args + self.kwargs = kwargs + self.objects = objects + + def __str__(self): + return ('args: %s, kwargs: %s\ncandidates: %s' + % (self.args, self.kwargs.keys(), self.objects)) + + +def _toload_info(path, extrapath, _toload=None): + """Return a dictionary of : and an ordered list of + (file, module name) to load + """ + from logilab.common.modutils import modpath_from_file + if _toload is None: + assert isinstance(path, list) + _toload = {}, [] + for fileordir in path: + if isdir(fileordir) and exists(join(fileordir, '__init__.py')): + subfiles = [join(fileordir, fname) for fname in listdir(fileordir)] + _toload_info(subfiles, extrapath, _toload) + elif fileordir[-3:] == '.py': + modpath = modpath_from_file(fileordir, extrapath) + # omit '__init__' from package's name to avoid loading that module + # once for each name when it is imported by some other object + # module. This supposes import in modules are done as:: + # + # from package import something + # + # not:: + # + # from package.__init__ import something + # + # which seems quite correct. + if modpath[-1] == '__init__': + modpath.pop() + modname = '.'.join(modpath) + _toload[0][modname] = fileordir + _toload[1].append((fileordir, modname)) + return _toload + + +def classid(cls): + """returns a unique identifier for an object class""" + return '%s.%s' % (cls.__module__, cls.__name__) + +def class_registries(cls, registryname): + """return a tuple of registry names (see __registries__)""" + if registryname: + return (registryname,) + return cls.__registries__ + + +class Registry(dict): + """The registry store a set of implementations associated to identifier: + + * to each identifier are associated a list of implementations + + * to select an implementation of a given identifier, you should use one of the + :meth:`select` or :meth:`select_or_none` method + + * to select a list of implementations for a context, you should use the + :meth:`possible_objects` method + + * dictionary like access to an identifier will return the bare list of + implementations for this identifier. + + To be usable in a registry, the only requirement is to have a `__select__` + attribute. + + At the end of the registration process, the :meth:`__registered__` + method is called on each registered object which have them, given the + registry in which it's registered as argument. + + Registration methods: + + .. automethod: register + .. automethod: unregister + + Selection methods: + + .. automethod: select + .. automethod: select_or_none + .. automethod: possible_objects + .. automethod: object_by_id + """ + def __init__(self, debugmode): + super(Registry, self).__init__() + self.debugmode = debugmode + + def __getitem__(self, name): + """return the registry (list of implementation objects) associated to + this name + """ + try: + return super(Registry, self).__getitem__(name) + except KeyError: + raise ObjectNotFound(name), None, sys.exc_info()[-1] + + def initialization_completed(self): + """call method __registered__() on registered objects when the callback + is defined""" + for objects in self.itervalues(): + for objectcls in objects: + registered = getattr(objectcls, '__registered__', None) + if registered: + registered(self) + if self.debugmode: + wrap_predicates(_lltrace) + + def register(self, obj, oid=None, clear=False): + """base method to add an object in the registry""" + assert not '__abstract__' in obj.__dict__ + assert obj.__select__ + oid = oid or obj.__regid__ + assert oid + if clear: + objects = self[oid] = [] + else: + objects = self.setdefault(oid, []) + assert not obj in objects, \ + 'object %s is already registered' % obj + objects.append(obj) + + def register_and_replace(self, obj, replaced): + """remove and register """ + # XXXFIXME this is a duplication of unregister() + # remove register_and_replace in favor of unregister + register + # or simplify by calling unregister then register here + if not isinstance(replaced, basestring): + replaced = classid(replaced) + # prevent from misspelling + assert obj is not replaced, 'replacing an object by itself: %s' % obj + registered_objs = self.get(obj.__regid__, ()) + for index, registered in enumerate(registered_objs): + if classid(registered) == replaced: + del registered_objs[index] + break + else: + self.warning('trying to replace %s that is not registered with %s', + replaced, obj) + self.register(obj) + + def unregister(self, obj): + """remove object from this registry""" + clsid = classid(obj) + oid = obj.__regid__ + for registered in self.get(oid, ()): + # use classid() to compare classes because vreg will probably + # have its own version of the class, loaded through execfile + if classid(registered) == clsid: + self[oid].remove(registered) + break + else: + self.warning('can\'t remove %s, no id %s in the registry', + clsid, oid) + + def all_objects(self): + """return a list containing all objects in this registry. + """ + result = [] + for objs in self.values(): + result += objs + return result + + # dynamic selection methods ################################################ + + def object_by_id(self, oid, *args, **kwargs): + """return object with the `oid` identifier. Only one object is expected + to be found. + + raise :exc:`ObjectNotFound` if not object with id in + + raise :exc:`AssertionError` if there is more than one object there + """ + objects = self[oid] + assert len(objects) == 1, objects + return objects[0](*args, **kwargs) + + def select(self, __oid, *args, **kwargs): + """return the most specific object among those with the given oid + according to the given context. + + raise :exc:`ObjectNotFound` if not object with id in + + raise :exc:`NoSelectableObject` if not object apply + """ + obj = self._select_best(self[__oid], *args, **kwargs) + if obj is None: + raise NoSelectableObject(args, kwargs, self[__oid] ) + return obj + + def select_or_none(self, __oid, *args, **kwargs): + """return the most specific object among those with the given oid + according to the given context, or None if no object applies. + """ + try: + return self.select(__oid, *args, **kwargs) + except (NoSelectableObject, ObjectNotFound): + return None + + def possible_objects(self, *args, **kwargs): + """return an iterator on possible objects in this registry for the given + context + """ + for objects in self.itervalues(): + obj = self._select_best(objects, *args, **kwargs) + if obj is None: + continue + yield obj + + def _select_best(self, objects, *args, **kwargs): + """return an instance of the most specific object according + to parameters + + return None if not object apply (don't raise `NoSelectableObject` since + it's costly when searching objects using `possible_objects` + (e.g. searching for hooks). + """ + score, winners = 0, None + for obj in objects: + objectscore = obj.__select__(obj, *args, **kwargs) + if objectscore > score: + score, winners = objectscore, [obj] + elif objectscore > 0 and objectscore == score: + winners.append(obj) + if winners is None: + return None + if len(winners) > 1: + # log in production environement / test, error while debugging + msg = 'select ambiguity: %s\n(args: %s, kwargs: %s)' + if self.debugmode: + # raise bare exception in debug mode + raise Exception(msg % (winners, args, kwargs.keys())) + self.error(msg, winners, args, kwargs.keys()) + # return the result of calling the object + return winners[0](*args, **kwargs) + + # these are overridden by set_log_methods below + # only defining here to prevent pylint from complaining + info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None + + +class RegistryStore(dict): + """This class is responsible for loading implementations and storing them + in their registry which are created on the fly as needed. + + It handles dynamic registration of objects and provides a convenient api to + access them. To be recognized as an object that should be stored into one of + the store's registry (:class:`Registry`), an object (usually a class) has + the following attributes, used control how they interact with the registry: + + :attr:`__registry__` or `__registries__` + name of the registry for this object (string like 'views', 'templates'...) + or list of registry names if you want your object to be added to multiple + registries + + :attr:`__regid__` + implementation's identifier in the registry (string like 'main', + 'primary', 'folder_box') + + :attr:`__select__` + the implementation's selector + + Moreover, the :attr:`__abstract__` attribute may be set to `True` to + indicate that a class is abstract and should not be registered (inherited + attributes not considered). + + .. Note:: + + When using the store to load objects dynamically, you *always* have + to use **super()** to get the methods and attributes of the + superclasses, and not use the class identifier. Else, you'll get into + trouble when reloading comes into the place. + + For example, instead of writing:: + + class Thing(Parent): + __regid__ = 'athing' + __select__ = yes() + def f(self, arg1): + Parent.f(self, arg1) + + You must write:: + + class Thing(Parent): + __regid__ = 'athing' + __select__ = yes() + def f(self, arg1): + super(Parent, self).f(arg1) + + Controlling objects registration + -------------------------------- + + Dynamic loading is triggered by calling the :meth:`register_objects` method, + given a list of directory to inspect for python modules. + + .. automethod: register_objects + + For each module, by default, all compatible objects are registered + automatically, though if some objects have to replace other objects, or have + to be included only if some condition is met, you'll have to define a + `registration_callback(vreg)` function in your module and explicitly + register **all objects** in this module, using the api defined below. + + + .. automethod:: RegistryStore.register_all + .. automethod:: RegistryStore.register_and_replace + .. automethod:: RegistryStore.register + .. automethod:: RegistryStore.unregister + + .. Note:: + Once the function `registration_callback(vreg)` is implemented in a + module, all the objects from this module have to be explicitly + registered as it disables the automatic objects registration. + + + Examples: + + .. sourcecode:: python + + # cubicweb/web/views/basecomponents.py + def registration_callback(store): + # register everything in the module except SeeAlsoComponent + store.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) + # conditionally register SeeAlsoVComponent + if 'see_also' in store.schema: + store.register(SeeAlsoVComponent) + + In this example, we register all application object classes defined in the module + except `SeeAlsoVComponent`. This class is then registered only if the 'see_also' + relation type is defined in the instance'schema. + + .. sourcecode:: python + + # goa/appobjects/sessions.py + def registration_callback(store): + store.register(SessionsCleaner) + # replace AuthenticationManager by GAEAuthenticationManager + store.register_and_replace(GAEAuthenticationManager, AuthenticationManager) + # replace PersistentSessionManager by GAEPersistentSessionManager + store.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager) + + In this example, we explicitly register classes one by one: + + * the `SessionCleaner` class + * the `GAEAuthenticationManager` to replace the `AuthenticationManager` + * the `GAEPersistentSessionManager` to replace the `PersistentSessionManager` + + If at some point we register a new appobject class in this module, it won't be + registered at all without modification to the `registration_callback` + implementation. The previous example will register it though, thanks to the call + to the `register_all` method. + + Controlling registry instantation + --------------------------------- + The `REGISTRY_FACTORY` class dictionary allows to specify which class should + be instantiated for a given registry name. The class associated to `None` in + it will be the class used when there is no specific class for a name. + """ + + def __init__(self, debugmode=False): + super(RegistryStore, self).__init__() + self.debugmode = debugmode + + def reset(self): + """clear all registries managed by this store""" + # don't use self.clear, we want to keep existing subdictionaries + for subdict in self.itervalues(): + subdict.clear() + self._lastmodifs = {} + + def __getitem__(self, name): + """return the registry (dictionary of class objects) associated to + this name + """ + try: + return super(RegistryStore, self).__getitem__(name) + except KeyError: + raise RegistryNotFound(name), None, sys.exc_info()[-1] + + # methods for explicit (un)registration ################################### + + # default class, when no specific class set + REGISTRY_FACTORY = {None: Registry} + + def registry_class(self, regid): + """return existing registry named regid or use factory to create one and + return it""" + try: + return self.REGISTRY_FACTORY[regid] + except KeyError: + return self.REGISTRY_FACTORY[None] + + def setdefault(self, regid): + try: + return self[regid] + except KeyError: + self[regid] = self.registry_class(regid)(self.debugmode) + return self[regid] + + def register_all(self, objects, modname, butclasses=()): + """register all `objects` given. Objects which are not from the module + `modname` or which are in `butclasses` won't be registered. + + Typical usage is: + + .. sourcecode:: python + + store.register_all(globals().values(), __name__, (ClassIWantToRegisterExplicitly,)) + + So you get partially automatic registration, keeping manual registration + for some object (to use + :meth:`~logilab.common.registry.RegistryStore.register_and_replace` + for instance) + """ + for obj in objects: + try: + if obj.__module__ != modname or obj in butclasses: + continue + oid = obj.__regid__ + except AttributeError: + continue + if oid and not obj.__dict__.get('__abstract__'): + self.register(obj, oid=oid) + + def register(self, obj, registryname=None, oid=None, clear=False): + """register `obj` implementation into `registryname` or + `obj.__registry__` if not specified, with identifier `oid` or + `obj.__regid__` if not specified. + + If `clear` is true, all objects with the same identifier will be + previously unregistered. + """ + assert not obj.__dict__.get('__abstract__') + try: + vname = obj.__name__ + except AttributeError: + # XXX may occurs? + vname = obj.__class__.__name__ + for registryname in class_registries(obj, registryname): + registry = self.setdefault(registryname) + registry.register(obj, oid=oid, clear=clear) + self.debug('register %s in %s[\'%s\']', + vname, registryname, oid or obj.__regid__) + self._loadedmods.setdefault(obj.__module__, {})[classid(obj)] = obj + + def unregister(self, obj, registryname=None): + """unregister `obj` implementation object from the registry + `registryname` or `obj.__registry__` if not specified. + """ + for registryname in class_registries(obj, registryname): + self[registryname].unregister(obj) + + def register_and_replace(self, obj, replaced, registryname=None): + """register `obj` implementation object into `registryname` or + `obj.__registry__` if not specified. If found, the `replaced` object + will be unregistered first (else a warning will be issued as it's + generally unexpected). + """ + for registryname in class_registries(obj, registryname): + self[registryname].register_and_replace(obj, replaced) + + # initialization methods ################################################### + + def init_registration(self, path, extrapath=None): + """reset registry and walk down path to return list of (path, name) + file modules to be loaded""" + # XXX make this private by renaming it to _init_registration ? + self.reset() + # compute list of all modules that have to be loaded + self._toloadmods, filemods = _toload_info(path, extrapath) + # XXX is _loadedmods still necessary ? It seems like it's useful + # to avoid loading same module twice, especially with the + # _load_ancestors_then_object logic but this needs to be checked + self._loadedmods = {} + return filemods + + def register_objects(self, path, extrapath=None): + """register all objects found walking down """ + # load views from each directory in the instance's path + # XXX inline init_registration ? + filemods = self.init_registration(path, extrapath) + for filepath, modname in filemods: + self.load_file(filepath, modname) + self.initialization_completed() + + def initialization_completed(self): + """call initialization_completed() on all known registries""" + for reg in self.itervalues(): + reg.initialization_completed() + + def _mdate(self, filepath): + try: + return stat(filepath)[-2] + except OSError: + # this typically happens on emacs backup files (.#foo.py) + self.warning('Unable to load %s. It is likely to be a backup file', + filepath) + return None + + def is_reload_needed(self, path): + """return True if something module changed and the registry should be + reloaded + """ + lastmodifs = self._lastmodifs + for fileordir in path: + if isdir(fileordir) and exists(join(fileordir, '__init__.py')): + if self.is_reload_needed([join(fileordir, fname) + for fname in listdir(fileordir)]): + return True + elif fileordir[-3:] == '.py': + mdate = self._mdate(fileordir) + if mdate is None: + continue # backup file, see _mdate implementation + elif "flymake" in fileordir: + # flymake + pylint in use, don't consider these they will corrupt the registry + continue + if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate: + self.info('File %s changed since last visit', fileordir) + return True + return False + + def load_file(self, filepath, modname): + """load app objects from a python file""" + from logilab.common.modutils import load_module_from_name + if modname in self._loadedmods: + return + self._loadedmods[modname] = {} + mdate = self._mdate(filepath) + if mdate is None: + return # backup file, see _mdate implementation + elif "flymake" in filepath: + # flymake + pylint in use, don't consider these they will corrupt the registry + return + # set update time before module loading, else we get some reloading + # weirdness in case of syntax error or other error while importing the + # module + self._lastmodifs[filepath] = mdate + # load the module + module = load_module_from_name(modname) + self.load_module(module) + + def load_module(self, module): + """load objects from a module using registration_callback() when it exists + """ + self.info('loading %s from %s', module.__name__, module.__file__) + if hasattr(module, 'registration_callback'): + module.registration_callback(self) + else: + for objname, obj in vars(module).items(): + if objname.startswith('_'): + continue + self._load_ancestors_then_object(module.__name__, obj) + + def _load_ancestors_then_object(self, modname, objectcls): + """handle automatic object class registration: + + - first ensure parent classes are already registered + + - class with __abstract__ == True in their local dictionary or + with a name starting with an underscore are not registered + + - object class needs to have __registry__ and __regid__ attributes + set to a non empty string to be registered. + """ + # imported classes + objmodname = getattr(objectcls, '__module__', None) + if objmodname != modname: + if objmodname in self._toloadmods: + self.load_file(self._toloadmods[objmodname], objmodname) + return + # skip non registerable object + try: + if not (getattr(objectcls, '__regid__', None) + and getattr(objectcls, '__select__', None)): + return + except TypeError: + return + clsid = classid(objectcls) + if clsid in self._loadedmods[modname]: + return + self._loadedmods[modname][clsid] = objectcls + for parent in objectcls.__bases__: + self._load_ancestors_then_object(modname, parent) + if (objectcls.__dict__.get('__abstract__') + or objectcls.__name__[0] == '_' + or not objectcls.__registries__ + or not objectcls.__regid__): + return + try: + self.register(objectcls) + except Exception, ex: + if self.debugmode: + raise + self.exception('object %s registration failed: %s', + objectcls, ex) + + # these are overridden by set_log_methods below + # only defining here to prevent pylint from complaining + info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None + + +# init logging +set_log_methods(RegistryStore, getLogger('registry.store')) +set_log_methods(Registry, getLogger('registry')) + + +# helpers for debugging selectors +TRACED_OIDS = None + +def _trace_selector(cls, selector, args, ret): + vobj = args[0] + if TRACED_OIDS == 'all' or vobj.__regid__ in TRACED_OIDS: + print '%s -> %s for %s(%s)' % (cls, ret, vobj, vobj.__regid__) + +def _lltrace(selector): + """use this decorator on your predicates so they become traceable with + :class:`traced_selection` + """ + def traced(cls, *args, **kwargs): + ret = selector(cls, *args, **kwargs) + if TRACED_OIDS is not None: + _trace_selector(cls, selector, args, ret) + return ret + traced.__name__ = selector.__name__ + traced.__doc__ = selector.__doc__ + return traced + +class traced_selection(object): # pylint: disable=C0103 + """ + Typical usage is : + + .. sourcecode:: python + + >>> from logilab.common.registry import traced_selection + >>> with traced_selection(): + ... # some code in which you want to debug selectors + ... # for all objects + + Don't forget the 'from __future__ import with_statement' at the module top-level + if you're using python prior to 2.6. + + This will yield lines like this in the logs:: + + selector one_line_rset returned 0 for + + You can also give to :class:`traced_selection` the identifiers of objects on + which you want to debug selection ('oid1' and 'oid2' in the example above). + + .. sourcecode:: python + + >>> with traced_selection( ('regid1', 'regid2') ): + ... # some code in which you want to debug selectors + ... # for objects with __regid__ 'regid1' and 'regid2' + + A potentially useful point to set up such a tracing function is + the `logilab.common.registry.Registry.select` method body. + """ + + def __init__(self, traced='all'): + self.traced = traced + + def __enter__(self): + global TRACED_OIDS + TRACED_OIDS = self.traced + + def __exit__(self, exctype, exc, traceback): + global TRACED_OIDS + TRACED_OIDS = None + return traceback is None + +# selector base classes and operations ######################################## + +def objectify_predicate(selector_func): + """Most of the time, a simple score function is enough to build a selector. + The :func:`objectify_predicate` decorator turn it into a proper selector + class:: + + @objectify_predicate + def one(cls, req, rset=None, **kwargs): + return 1 + + class MyView(View): + __select__ = View.__select__ & one() + + """ + return type(selector_func.__name__, (Predicate,), + {'__doc__': selector_func.__doc__, + '__call__': lambda self, *a, **kw: selector_func(*a, **kw)}) + + +_PREDICATES = {} + +def wrap_predicates(decorator): + for predicate in _PREDICATES.itervalues(): + if not '_decorators' in predicate.__dict__: + predicate._decorators = set() + if decorator in predicate._decorators: + continue + predicate._decorators.add(decorator) + predicate.__call__ = decorator(predicate.__call__) + +class PredicateMetaClass(type): + def __new__(cls, *args, **kwargs): + # use __new__ so subclasses doesn't have to call Predicate.__init__ + inst = type.__new__(cls, *args, **kwargs) + proxy = weakref.proxy(inst, lambda p: _PREDICATES.pop(id(p))) + _PREDICATES[id(proxy)] = proxy + return inst + +class Predicate(object): + """base class for selector classes providing implementation + for operators ``&``, ``|`` and ``~`` + + This class is only here to give access to binary operators, the selector + logic itself should be implemented in the :meth:`__call__` method. Notice it + should usually accept any arbitrary arguments (the context), though that may + vary depending on your usage of the registry. + + a selector is called to help choosing the correct object for a + particular context by returning a score (`int`) telling how well + the implementation given as first argument fit to the given context. + + 0 score means that the class doesn't apply. + """ + __metaclass__ = PredicateMetaClass + + @property + def func_name(self): + # backward compatibility + return self.__class__.__name__ + + def search_selector(self, selector): + """search for the given selector, selector instance or tuple of + selectors in the selectors tree. Return None if not found. + """ + if self is selector: + return self + if (isinstance(selector, type) or isinstance(selector, tuple)) and \ + isinstance(self, selector): + return self + return None + + def __str__(self): + return self.__class__.__name__ + + def __and__(self, other): + return AndPredicate(self, other) + def __rand__(self, other): + return AndPredicate(other, self) + def __iand__(self, other): + return AndPredicate(self, other) + def __or__(self, other): + return OrPredicate(self, other) + def __ror__(self, other): + return OrPredicate(other, self) + def __ior__(self, other): + return OrPredicate(self, other) + + def __invert__(self): + return NotPredicate(self) + + # XXX (function | function) or (function & function) not managed yet + + def __call__(self, cls, *args, **kwargs): + return NotImplementedError("selector %s must implement its logic " + "in its __call__ method" % self.__class__) + + def __repr__(self): + return u'' % (self.__class__.__name__, id(self)) + + +class MultiPredicate(Predicate): + """base class for compound selector classes""" + + def __init__(self, *selectors): + self.selectors = self.merge_selectors(selectors) + + def __str__(self): + return '%s(%s)' % (self.__class__.__name__, + ','.join(str(s) for s in self.selectors)) + + @classmethod + def merge_selectors(cls, selectors): + """deal with selector instanciation when necessary and merge + multi-selectors if possible: + + AndPredicate(AndPredicate(sel1, sel2), AndPredicate(sel3, sel4)) + ==> AndPredicate(sel1, sel2, sel3, sel4) + """ + merged_selectors = [] + for selector in selectors: + # XXX do we really want magic-transformations below? + # if so, wanna warn about them? + if isinstance(selector, types.FunctionType): + selector = objectify_predicate(selector)() + if isinstance(selector, type) and issubclass(selector, Predicate): + selector = selector() + assert isinstance(selector, Predicate), selector + if isinstance(selector, cls): + merged_selectors += selector.selectors + else: + merged_selectors.append(selector) + return merged_selectors + + def search_selector(self, selector): + """search for the given selector or selector instance (or tuple of + selectors) in the selectors tree. Return None if not found + """ + for childselector in self.selectors: + if childselector is selector: + return childselector + found = childselector.search_selector(selector) + if found is not None: + return found + # if not found in children, maybe we are looking for self? + return super(MultiPredicate, self).search_selector(selector) + + +class AndPredicate(MultiPredicate): + """and-chained selectors""" + def __call__(self, cls, *args, **kwargs): + score = 0 + for selector in self.selectors: + partscore = selector(cls, *args, **kwargs) + if not partscore: + return 0 + score += partscore + return score + + +class OrPredicate(MultiPredicate): + """or-chained selectors""" + def __call__(self, cls, *args, **kwargs): + for selector in self.selectors: + partscore = selector(cls, *args, **kwargs) + if partscore: + return partscore + return 0 + +class NotPredicate(Predicate): + """negation selector""" + def __init__(self, selector): + self.selector = selector + + def __call__(self, cls, *args, **kwargs): + score = self.selector(cls, *args, **kwargs) + return int(not score) + + def __str__(self): + return 'NOT(%s)' % self.selector + + +class yes(Predicate): # pylint: disable=C0103 + """Return the score given as parameter, with a default score of 0.5 so any + other selector take precedence. + + Usually used for objects which can be selected whatever the context, or + also sometimes to add arbitrary points to a score. + + Take care, `yes(0)` could be named 'no'... + """ + def __init__(self, score=0.5): + self.score = score + + def __call__(self, *args, **kwargs): + return self.score diff --git a/pylibs/logilab/common/setup.py b/pylibs/logilab/common/setup.py deleted file mode 100644 index da44fe0c..00000000 --- a/pylibs/logilab/common/setup.py +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# pylint: disable=W0404,W0622,W0704,W0613 -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Generic Setup script, takes package info from __pkginfo__.py file. -""" -__docformat__ = "restructuredtext en" - -import os -import sys -import shutil -from os.path import isdir, exists, join - -try: - if os.environ.get('NO_SETUPTOOLS'): - raise ImportError() - from setuptools import setup - from setuptools.command import install_lib - USE_SETUPTOOLS = 1 -except ImportError: - from distutils.core import setup - from distutils.command import install_lib - USE_SETUPTOOLS = 0 - -try: - # python3 - from distutils.command.build_py import build_py_2to3 as build_py -except ImportError: - # python2.x - from distutils.command.build_py import build_py - -sys.modules.pop('__pkginfo__', None) -# import optional features -__pkginfo__ = __import__("__pkginfo__") -# import required features -from __pkginfo__ import modname, version, license, description, \ - web, author, author_email - -distname = getattr(__pkginfo__, 'distname', modname) -scripts = getattr(__pkginfo__, 'scripts', []) -data_files = getattr(__pkginfo__, 'data_files', None) -subpackage_of = getattr(__pkginfo__, 'subpackage_of', None) -include_dirs = getattr(__pkginfo__, 'include_dirs', []) -ext_modules = getattr(__pkginfo__, 'ext_modules', None) -install_requires = getattr(__pkginfo__, 'install_requires', None) -dependency_links = getattr(__pkginfo__, 'dependency_links', []) - -STD_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build') - -IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~') - -if exists('README'): - long_description = open('README').read() -else: - long_description = '' - -def ensure_scripts(linux_scripts): - """Creates the proper script names required for each platform - (taken from 4Suite) - """ - from distutils import util - if util.get_platform()[:3] == 'win': - scripts_ = [script + '.bat' for script in linux_scripts] - else: - scripts_ = linux_scripts - return scripts_ - -def get_packages(directory, prefix): - """return a list of subpackages for the given directory""" - result = [] - for package in os.listdir(directory): - absfile = join(directory, package) - if isdir(absfile): - if exists(join(absfile, '__init__.py')) or \ - package in ('test', 'tests'): - if prefix: - result.append('%s.%s' % (prefix, package)) - else: - result.append(package) - result += get_packages(absfile, result[-1]) - return result - -EMPTY_FILE = '''"""generated file, don't modify or your data will be lost""" -try: - __import__('pkg_resources').declare_namespace(__name__) -except ImportError: - pass -''' - -class MyInstallLib(install_lib.install_lib): - """extend install_lib command to handle package __init__.py and - include_dirs variable if necessary - """ - def run(self): - """overridden from install_lib class""" - install_lib.install_lib.run(self) - # create Products.__init__.py if needed - if subpackage_of: - product_init = join(self.install_dir, subpackage_of, '__init__.py') - if not exists(product_init): - self.announce('creating %s' % product_init) - stream = open(product_init, 'w') - stream.write(EMPTY_FILE) - stream.close() - # manually install included directories if any - if include_dirs: - if subpackage_of: - base = join(subpackage_of, modname) - else: - base = modname - for directory in include_dirs: - dest = join(self.install_dir, base, directory) - shutil.rmtree(dest, ignore_errors=True) - shutil.copytree(directory, dest) - -def install(**kwargs): - """setup entry point""" - if USE_SETUPTOOLS: - if '--force-manifest' in sys.argv: - sys.argv.remove('--force-manifest') - # install-layout option was introduced in 2.5.3-1~exp1 - elif sys.version_info < (2, 5, 4) and '--install-layout=deb' in sys.argv: - sys.argv.remove('--install-layout=deb') - if subpackage_of: - package = subpackage_of + '.' + modname - kwargs['package_dir'] = {package : '.'} - packages = [package] + get_packages(os.getcwd(), package) - if USE_SETUPTOOLS: - kwargs['namespace_packages'] = [subpackage_of] - else: - kwargs['package_dir'] = {modname : '.'} - packages = [modname] + get_packages(os.getcwd(), modname) - if USE_SETUPTOOLS and install_requires: - kwargs['install_requires'] = install_requires - kwargs['dependency_links'] = dependency_links - kwargs['packages'] = packages - return setup(name = distname, - version = version, - license = license, - description = description, - long_description = long_description, - author = author, - author_email = author_email, - url = web, - scripts = ensure_scripts(scripts), - data_files = data_files, - ext_modules = ext_modules, - cmdclass = {'install_lib': MyInstallLib, - 'build_py': build_py}, - **kwargs - ) - -if __name__ == '__main__' : - install() diff --git a/pylibs/logilab/common/shellutils.py b/pylibs/logilab/common/shellutils.py index c7139135..749cbac3 100644 --- a/pylibs/logilab/common/shellutils.py +++ b/pylibs/logilab/common/shellutils.py @@ -315,9 +315,22 @@ def _del_text(self): text = property(_get_text, _set_text, _del_text) - def update(self): - """Update the progression bar.""" - self._current += 1 + def update(self, offset=1, exact=False): + """Move FORWARD to new cursor position (cursor will never go backward). + + :offset: fraction of ``size`` + + :exact: + + - False: offset relative to current cursor position if True + - True: offset as an asbsolute position + + """ + if exact: + self._current = offset + else: + self._current += offset + progress = int((float(self._current)/float(self._total))*self._size) if progress > self._progress: self._progress = progress diff --git a/pylibs/logilab/common/testlib.py b/pylibs/logilab/common/testlib.py index da49387a..a35ad98c 100644 --- a/pylibs/logilab/common/testlib.py +++ b/pylibs/logilab/common/testlib.py @@ -62,7 +62,7 @@ import unittest2 as unittest from unittest2 import SkipTest except ImportError: - sys.exit("You have to install python-unittest2 to use this module") + raise ImportError("You have to install python-unittest2 to use %s" % __name__) else: import unittest from unittest import SkipTest @@ -1214,7 +1214,14 @@ def __call__(self, result=None, runcondition=None, options=None):\ suite = doctest.DocTestSuite(self.module) except AttributeError: suite = SkippedSuite() - return suite.run(result) + # doctest may gork the builtins dictionnary + # This happen to the "_" entry used by gettext + old_builtins = __builtins__.copy() + try: + return suite.run(result) + finally: + __builtins__.clear() + __builtins__.update(old_builtins) run = __call__ def test(self): diff --git a/pylibs/logilab/common/textutils.py b/pylibs/logilab/common/textutils.py index bdeed415..f55c0040 100644 --- a/pylibs/logilab/common/textutils.py +++ b/pylibs/logilab/common/textutils.py @@ -313,6 +313,8 @@ def text_to_dict(text): __VALUE_URE = r'-?(([0-9]+\.[0-9]*)|((0x?)?[0-9]+))' __UNITS_URE = r'[a-zA-Z]+' _VALUE_RE = re.compile(r'(?P%s)(?P%s)?'%(__VALUE_URE, __UNITS_URE)) +_VALIDATION_RE = re.compile(r'^((%s)(%s))*(%s)?$' % (__VALUE_URE, __UNITS_URE, + __VALUE_URE)) BYTE_UNITS = { "b": 1, @@ -352,12 +354,12 @@ def apply_units(string, units, inter=None, final=float, blank_reg=_BLANK_RE, """ if inter is None: inter = final - string = _BLANK_RE.sub('', string) + fstring = _BLANK_RE.sub('', string) + if not (fstring and _VALIDATION_RE.match(fstring)): + raise ValueError("Invalid unit string: %r." % string) values = [] - for match in value_reg.finditer(string): + for match in value_reg.finditer(fstring): dic = match.groupdict() - #import sys - #print >> sys.stderr, dic lit, unit = dic["value"], dic.get("unit") value = inter(lit) if unit is not None: diff --git a/pylibs/pylint/README b/pylibs/pylint/README new file mode 100644 index 00000000..5f89aef8 --- /dev/null +++ b/pylibs/pylint/README @@ -0,0 +1,70 @@ +README for PyLint +================= + +Dependencies +------------ +Pylint requires the logilab-astng (version >= 0.21.0), logilab-common +(version >= 0.53). + +* http://www.logilab.org/projects/astng +* http://www.logilab.org/projects/common + +Install +------- +From the source distribution, extract the tarball and run :: + + python setup.py install + +You'll have to install dependencies in a similar way. For debian and +rpm packages, use your usual tools according to your Linux distribution. + +More information about installation and available distribution format +may be found in the user manual in the *doc* subdirectory. + +Documentation +------------- +Look in the doc/ subdirectory or at the project home page +http://www.logilab.org/project/pylint + +Pylint is shipped with following additional commands: + +* pyreverse: an UML diagram generator +* symilar: an independent similarities checker +* epylint: Emacs and Flymake compatible Pylint +* pylint-gui: a graphical interface + +Comments, support, bug reports +------------------------------ + +Project page and tracker on : +http://www.logilab.org/project/pylint + +Use the python-projects@logilab.org mailing list. +You can subscribe to this mailing list at +http://lists.logilab.org/mailman/listinfo/python-projects + +Archives are available at +http://lists.logilab.org/pipermail/python-projects/ + +Contributors +------------ + +order doesn't matter... + +* Sylvain Thenault: main author / maintainer +* Alexandre Fayolle: TkInter gui, documentation, debian support +* Emile Anclin: used to maintain, py3k support +* Mads Kiilerich: various patches +* Torsten Marek, various patches +* Boris Feld, various patches +* Brian van den Broek: windows installation documentation +* Amaury Forgeot d'Arc: patch to check names imported from a module + exists in the module +* Benjamin Niemann: patch to allow block level enabling/disabling of messages +* Nathaniel Manista: suspicious lambda checking +* Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, + Maarten ter Huurne, Mirko Friedenhagen (among others): + bug reports, feedback, feature requests... +* All the Logilab's team: daily use, bug reports, feature requests +* Other people have contributed by their feedback, if I've forgotten + you, send me a note ! diff --git a/pylibs/pylint/README.Python3 b/pylibs/pylint/README.Python3 new file mode 100644 index 00000000..ccc92972 --- /dev/null +++ b/pylibs/pylint/README.Python3 @@ -0,0 +1,37 @@ +Python3 +======= + +Compatibility +------------- + +Please, consider python3 >= 3.2 only. + + +Approach +-------- + +We maintain a Python 2 base and use 2to3 to generate Python 3 code. + +2to3 is integrated into the distutils installation process and will be run as a +build step when invoked by the python3 interpreter:: + + NO_SETUPTOOLS=1 python3 setup.py install --no-compile + +In order to run pylint locally, you have to install the dependencies:: + + easy_install-3.2 logilab-common + easy_install-3.2 logilab-astng + + +Debian +------ + +For the Debian packaging, you can use the debian.py3k/ content against +the debian/ folder:: + + cp debian.py3k/* debian/ + + +Resources +--------- +http://wiki.python.org/moin/PortingPythonToPy3k diff --git a/pylibs/pylint/__pkginfo__.py b/pylibs/pylint/__pkginfo__.py index 37721f93..3f7847b9 100644 --- a/pylibs/pylint/__pkginfo__.py +++ b/pylibs/pylint/__pkginfo__.py @@ -1,5 +1,5 @@ # pylint: disable=W0622,C0103 -# Copyright (c) 2003-2011 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -18,7 +18,7 @@ modname = distname = 'pylint' -numversion = (0, 25, 0) +numversion = (0, 25, 2) version = '.'.join([str(num) for num in numversion]) install_requires = ['logilab-common >= 0.53.0', 'logilab-astng >= 0.21.1'] diff --git a/pylibs/pylint/checkers/__init__.py b/pylibs/pylint/checkers/__init__.py index d33cacf3..969066b5 100644 --- a/pylibs/pylint/checkers/__init__.py +++ b/pylibs/pylint/checkers/__init__.py @@ -120,7 +120,7 @@ def process_module(self, node): stream must implement the readline method """ stream = node.file_stream - stream.seek(0) + stream.seek(0) # XXX may be removed with astng > 0.23 self.process_tokens(tokenize.generate_tokens(stream.readline)) def process_tokens(self, tokens): diff --git a/pylibs/pylint/checkers/base.py b/pylibs/pylint/checkers/base.py index a0629a28..5de570ff 100644 --- a/pylibs/pylint/checkers/base.py +++ b/pylibs/pylint/checkers/base.py @@ -23,7 +23,7 @@ from pylint.interfaces import IASTNGChecker from pylint.reporters import diff_string from pylint.checkers import BaseChecker, EmptyReport -from pylint.checkers.utils import check_messages +from pylint.checkers.utils import check_messages, clobber_in_except, is_inside_except import re @@ -101,6 +101,22 @@ def report_by_type_stats(sect, stats, old_stats): nice_stats[node_type].get('percent_badname', '0')) sect.append(Table(children=lines, cols=6, rheaders=1)) +def redefined_by_decorator(node): + """return True if the object is a method redefined via decorator. + + For example: + @property + def x(self): return self._x + @x.setter + def x(self, value): self._x = value + """ + if node.decorators: + for decorator in node.decorators.nodes: + if (isinstance(decorator, astng.Getattr) and + getattr(decorator.expr, 'name', None) == node.name): + return True + return False + class _BasicChecker(BaseChecker): __implements__ = IASTNGChecker name = 'basic' @@ -142,7 +158,8 @@ def visit_class(self, node): @check_messages('E0100', 'E0101', 'E0102', 'E0106') def visit_function(self, node): - self._check_redefinition(node.is_method() and 'method' or 'function', node) + if not redefined_by_decorator(node): + self._check_redefinition(node.is_method() and 'method' or 'function', node) # checks for max returns, branch, return in __init__ returns = node.nodes_of_class(astng.Return, skip_klass=(astng.Function, astng.Class)) @@ -411,6 +428,12 @@ def visit_function(self, node): else: msg = '%s (%s)' % (default.as_string(), value.as_string()) self.add_message('W0102', node=node, args=(msg,)) + if value.qname() == '__builtin__.set': + if isinstance(default, astng.CallFunc): + msg = default.as_string() + else: + msg = '%s (%s)' % (default.as_string(), value.qname()) + self.add_message('W0102', node=node, args=(msg,)) @check_messages('W0101', 'W0150') def visit_return(self, node): @@ -649,6 +672,8 @@ def visit_assname(self, node): elif isinstance(frame, astng.Module): if isinstance(ass_type, astng.Assign) and not in_loop(ass_type): self._check_name('const', node.name, node) + elif isinstance(ass_type, astng.ExceptHandler): + self._check_name('variable', node.name, node) elif isinstance(frame, astng.Function): # global introduced variable aren't in the function locals if node.name in frame: @@ -664,6 +689,11 @@ def _recursive_check_names(self, args, node): def _check_name(self, node_type, name, node): """check for a name using the type's regexp""" + if is_inside_except(node): + clobbering, _ = clobber_in_except(node) + if clobbering: + return + if name in self.config.good_names: return if name in self.config.bad_names: @@ -676,7 +706,6 @@ def _check_name(self, node_type, name, node): self.stats['badname_' + node_type] += 1 - class DocStringChecker(_BasicChecker): msgs = { 'C0111': ('Missing docstring', # W0131 diff --git a/pylibs/pylint/checkers/classes.py b/pylibs/pylint/checkers/classes.py index 60d20b6c..716d0a92 100644 --- a/pylibs/pylint/checkers/classes.py +++ b/pylibs/pylint/checkers/classes.py @@ -18,11 +18,12 @@ from __future__ import generators from logilab import astng -from logilab.astng import YES, Instance, are_exclusive +from logilab.astng import YES, Instance, are_exclusive, AssAttr from pylint.interfaces import IASTNGChecker from pylint.checkers import BaseChecker -from pylint.checkers.utils import PYMETHODS, overrides_a_method, check_messages +from pylint.checkers.utils import (PYMETHODS, overrides_a_method, + check_messages, is_attr_private, is_attr_protected, node_frame_class) def class_is_abstract(node): """return true if the given class node should be considered as an abstract @@ -254,7 +255,17 @@ def visit_function(self, node): continue self._check_signature(node, meth_node, 'overridden') break - # check if the method overload an attribute + if node.decorators: + for decorator in node.decorators.nodes: + if isinstance(decorator, astng.Getattr) and \ + decorator.attrname in ('getter', 'setter', 'deleter'): + # attribute affectation will call this method, not hiding it + return + if isinstance(decorator, astng.Name) and decorator.name == 'property': + # attribute affectation will either call a setter or raise + # an attribute error, anyway not hiding the function + return + # check if the method is hidden by an attribute try: overridden = klass.instance_attr(node.name)[0] # XXX args = (overridden.root().name, overridden.fromlineno) @@ -289,32 +300,59 @@ class member from outside its class (but ignore __special__ methods) """ attrname = node.attrname - if self._first_attrs and isinstance(node.expr, astng.Name) and \ - node.expr.name == self._first_attrs[-1]: + # Check self + if self.is_first_attr(node): self._accessed[-1].setdefault(attrname, []).append(node) return if 'W0212' not in self.active_msgs: return - if attrname[0] == '_' and not attrname == '_' and not ( - attrname.startswith('__') and attrname.endswith('__')): - # XXX move this in a reusable function - klass = node.frame() - while klass is not None and not isinstance(klass, astng.Class): - if klass.parent is None: - klass = None - else: - klass = klass.parent.frame() + + self._check_protected_attribute_access(node) + + def visit_assign(self, assign_node): + if 'W0212' not in self.active_msgs: + return + + node = assign_node.targets[0] + if not isinstance(node, AssAttr): + return + + if self.is_first_attr(node): + return + + self._check_protected_attribute_access(node) + + def _check_protected_attribute_access(self, node): + '''Given an attribute access node (set or get), check if attribute + access is legitimate. Call _check_first_attr with node before calling + this method. Valid cases are: + * self._attr in a method or cls._attr in a classmethod. Checked by + _check_first_attr. + * Klass._attr inside "Klass" class. + * Klass2._attr inside "Klass" class when Klass2 is a base class of + Klass. + ''' + attrname = node.attrname + + if is_attr_protected(attrname): + + klass = node_frame_class(node) + # XXX infer to be more safe and less dirty ?? # in classes, check we are not getting a parent method # through the class object or through super callee = node.expr.as_string() - if klass is None or not (callee == klass.name or - callee in klass.basenames - or (isinstance(node.expr, astng.CallFunc) - and isinstance(node.expr.func, astng.Name) - and node.expr.func.name == 'super')): + + # We are not in a class, no remaining valid case + if klass is None: self.add_message('W0212', node=node, args=attrname) + return + + # We are in a class, one remaining valid cases, Klass._attr inside + # Klass + if not (callee == klass.name or callee in klass.basenames): + self.add_message('W0212', node=node, args=attrname) def visit_name(self, node): """check if the name handle an access to a class member @@ -519,11 +557,19 @@ def _check_signature(self, method1, refmethod, class_type): # if we use *args, **kwargs, skip the below checks if method1.args.vararg or method1.args.kwarg: return + if is_attr_private(method1.name): + return if len(method1.args.args) != len(refmethod.args.args): self.add_message('W0221', args=class_type, node=method1) elif len(method1.args.defaults) < len(refmethod.args.defaults): self.add_message('W0222', args=class_type, node=method1) + def is_first_attr(self, node): + """Check that attribute lookup name use first attribute variable name + (self for method, cls for classmethod and mcs for metaclass). + """ + return self._first_attrs and isinstance(node.expr, astng.Name) and \ + node.expr.name == self._first_attrs[-1] def _ancestors_to_call(klass_node, method='__init__'): """return a dictionary where keys are the list of base classes providing diff --git a/pylibs/pylint/checkers/exceptions.py b/pylibs/pylint/checkers/exceptions.py index bf814385..08f4334c 100644 --- a/pylibs/pylint/checkers/exceptions.py +++ b/pylibs/pylint/checkers/exceptions.py @@ -151,7 +151,7 @@ def visit_tryexcept(self, node): self.add_message('W0704', node=handler.type or handler.body[0]) if handler.type is None: if nb_handlers == 1 and not is_raising(handler.body): - self.add_message('W0702', node=handler.body[0]) + self.add_message('W0702', node=handler) # check if a "except:" is followed by some other # except elif index < (nb_handlers - 1): diff --git a/pylibs/pylint/checkers/format.py b/pylibs/pylint/checkers/format.py index 6a2d5aca..0784e6af 100644 --- a/pylibs/pylint/checkers/format.py +++ b/pylibs/pylint/checkers/format.py @@ -183,7 +183,7 @@ def process_module(self, node): international text's length is properly calculated. """ stream = node.file_stream - stream.seek(0) + stream.seek(0) # XXX may be removed with astng > 0.23 readline = stream.readline if sys.version_info < (3, 0): if node.file_encoding is not None: diff --git a/pylibs/pylint/checkers/misc.py b/pylibs/pylint/checkers/misc.py index 8f6ad2dc..7f09d404 100644 --- a/pylibs/pylint/checkers/misc.py +++ b/pylibs/pylint/checkers/misc.py @@ -55,7 +55,7 @@ def process_module(self, node): notes """ stream = node.file_stream - stream.seek(0) + stream.seek(0) # XXX may be removed with astng > 0.23 # warning notes in the code notes = [] for note in self.config.notes: diff --git a/pylibs/pylint/checkers/similar.py b/pylibs/pylint/checkers/similar.py index a647adae..1e38ed61 100644 --- a/pylibs/pylint/checkers/similar.py +++ b/pylibs/pylint/checkers/similar.py @@ -39,7 +39,7 @@ def __init__(self, min_lines=4, ignore_comments=False, def append_stream(self, streamid, stream): """append a file to search for similarities""" - stream.seek(0) + stream.seek(0) # XXX may be removed with astng > 0.23 self.linesets.append(LineSet(streamid, stream.readlines(), self.ignore_comments, diff --git a/pylibs/pylint/checkers/typecheck.py b/pylibs/pylint/checkers/typecheck.py index 8f00ca2b..db2f4937 100644 --- a/pylibs/pylint/checkers/typecheck.py +++ b/pylibs/pylint/checkers/typecheck.py @@ -132,6 +132,7 @@ def visit_getattr(self, node): if isinstance(self.config.generated_members, str): gen = shlex.shlex(self.config.generated_members) gen.whitespace += ',' + gen.wordchars += '[]-+' self.config.generated_members = tuple(tok.strip('"') for tok in gen) for pattern in self.config.generated_members: # attribute is marked as generated, stop here @@ -170,6 +171,8 @@ def visit_getattr(self, node): # XXX method / function continue except NotFoundError: + if isinstance(owner, astng.Function) and owner.decorators: + continue if isinstance(owner, Instance) and owner.has_dynamic_getattr(): continue # explicit skipping of optparse'Values class diff --git a/pylibs/pylint/checkers/utils.py b/pylibs/pylint/checkers/utils.py index 43d619f6..cff12fe3 100644 --- a/pylibs/pylint/checkers/utils.py +++ b/pylibs/pylint/checkers/utils.py @@ -18,13 +18,57 @@ """some functions that may be useful for various checkers """ +import re import string from logilab import astng +from logilab.astng import scoped_nodes from logilab.common.compat import builtins BUILTINS_NAME = builtins.__name__ COMP_NODE_TYPES = astng.ListComp, astng.SetComp, astng.DictComp, astng.GenExpr + +def is_inside_except(node): + """Returns true if node is inside the name of an except handler.""" + current = node + while current and not isinstance(current.parent, astng.ExceptHandler): + current = current.parent + + return current and current is current.parent.name + + +def get_all_elements(node): + """Recursively returns all atoms in nested lists and tuples.""" + if isinstance(node, (astng.Tuple, astng.List)): + for child in node.elts: + for e in get_all_elements(child): + yield e + else: + yield node + + +def clobber_in_except(node): + """Checks if an assignment node in an except handler clobbers an existing + variable. + + Returns (True, args for W0623) if assignment clobbers an existing variable, + (False, None) otherwise. + """ + if isinstance(node, astng.AssAttr): + return (True, (node.attrname, 'object %r' % (node.expr.name,))) + elif isinstance(node, astng.AssName): + name = node.name + if is_builtin(name): + return (True, (name, 'builtins')) + else: + scope, stmts = node.lookup(name) + if (stmts and + not isinstance(stmts[0].ass_type(), + (astng.Assign, astng.AugAssign, astng.ExceptHandler))): + return (True, (name, 'outer scope (line %i)' % (stmts[0].lineno,))) + return (False, None) + + def safe_infer(node): """return the inferred value for the given node. Return None if inference failed or if there is some ambiguity (more than @@ -38,6 +82,8 @@ def safe_infer(node): try: inferit.next() return # None if there is ambiguity on the inferred node + except astng.InferenceError: + return # there is some kind of ambiguity except StopIteration: return value @@ -67,7 +113,7 @@ def is_empty(body): """return true if the given node does nothing but 'pass'""" return len(body) == 1 and isinstance(body[0], astng.Pass) -builtins = __builtins__.copy() +builtins = builtins.__dict__.copy() SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') def is_builtin(name): # was is_native_builtin @@ -140,7 +186,10 @@ def is_func_decorator(node): while parent is not None: if isinstance(parent, astng.Decorators): return True - if parent.is_statement or isinstance(parent, astng.Lambda): + if (parent.is_statement or + isinstance(parent, astng.Lambda) or + isinstance(parent, (scoped_nodes.ComprehensionScope, + scoped_nodes.ListComp))): break parent = parent.parent return False @@ -287,3 +336,38 @@ def next_char(i): num_args += 1 i += 1 return keys, num_args + +def is_attr_protected(attrname): + """return True if attribute name is protected (start with _ and some other + details), False otherwise. + """ + return attrname[0] == '_' and not attrname == '_' and not ( + attrname.startswith('__') and attrname.endswith('__')) + +def node_frame_class(node): + """return klass node for a method node (or a staticmethod or a + classmethod), return null otherwise + """ + klass = node.frame() + + while klass is not None and not isinstance(klass, astng.Class): + if klass.parent is None: + klass = None + else: + klass = klass.parent.frame() + + return klass + +def is_super_call(expr): + """return True if expression node is a function call and if function name + is super. Check before that you're in a method. + """ + return (isinstance(expr, astng.CallFunc) and + isinstance(expr.func, astng.Name) and + expr.func.name == 'super') +def is_attr_private(attrname): + """Check that attribute name is private (at least two leading underscores, + at most one trailing underscore) + """ + regex = re.compile('^_{2,}.*[^_]+_?$') + return regex.match(attrname) diff --git a/pylibs/pylint/checkers/variables.py b/pylibs/pylint/checkers/variables.py index ce98e04b..f0abd1ba 100644 --- a/pylibs/pylint/checkers/variables.py +++ b/pylibs/pylint/checkers/variables.py @@ -26,7 +26,14 @@ from pylint.checkers import BaseChecker from pylint.checkers.utils import (PYMETHODS, is_ancestor_name, is_builtin, is_defined_before, is_error, is_func_default, is_func_decorator, - assign_parent, check_messages) + assign_parent, check_messages, is_inside_except, clobber_in_except, + get_all_elements) + + +def in_for_else_branch(parent, stmt): + """Returns True if stmt in inside the else branch for a parent For stmt.""" + return (isinstance(parent, astng.For) and + any(else_stmt.parent_of(stmt) for else_stmt in parent.orelse)) def overridden_method(klass, name): """get overridden method if any""" @@ -51,7 +58,6 @@ def overridden_method(klass, name): assignment.'), 'E0602': ('Undefined variable %r', 'Used when an undefined variable is accessed.'), - 'E0611': ('No name %r in module %r', 'Used when a name cannot be found in a module.'), @@ -83,6 +89,9 @@ def overridden_method(klass, name): scope.'), 'W0622': ('Redefining built-in %r', 'Used when a variable or function override a built-in.'), + 'W0623': ('Redefining name %r from %s in exception handler', + 'Used when an exception handler assigns the exception \ + to an existing name'), 'W0631': ('Using possibly undefined loop variable %r', 'Used when an loop variable (i.e. defined by a for loop or \ @@ -133,7 +142,7 @@ def visit_module(self, node): self._to_consume = [(copy(node.locals), {}, 'module')] self._vars = [] for name, stmts in node.locals.items(): - if is_builtin(name): + if is_builtin(name) and not is_inside_except(stmts[0]): # do not print Redefining builtin for additional builtins self.add_message('W0622', args=name, node=stmts[0]) @@ -222,6 +231,8 @@ def visit_function(self, node): return globs = node.root().globals for name, stmt in node.items(): + if is_inside_except(stmt): + continue if name in globs and not isinstance(stmt, astng.Global): line = globs[name][0].lineno self.add_message('W0621', args=(name, line), node=stmt) @@ -344,7 +355,8 @@ def _loopvar_name(self, node, name): else: _astmts = astmts[:1] for i, stmt in enumerate(astmts[1:]): - if astmts[i].statement().parent_of(stmt): + if (astmts[i].statement().parent_of(stmt) + and not in_for_else_branch(astmts[i].statement(), stmt)): continue _astmts.append(stmt) astmts = _astmts @@ -354,10 +366,16 @@ def _loopvar_name(self, node, name): and not ass.statement() is node.statement(): self.add_message('W0631', args=name, node=node) + def visit_excepthandler(self, node): + for name in get_all_elements(node.name): + clobbering, args = clobber_in_except(name) + if clobbering: + self.add_message('W0623', args=args, node=name) + def visit_assname(self, node): if isinstance(node.ass_type(), astng.AugAssign): self.visit_name(node) - + def visit_delname(self, node): self.visit_name(node) diff --git a/pylibs/pylint/epylint.py b/pylibs/pylint/epylint.py deleted file mode 100644 index f6b16e7a..00000000 --- a/pylibs/pylint/epylint.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4 -"""Emacs and Flymake compatible Pylint. - -This script is for integration with emacs and is compatible with flymake mode. - -epylint walks out of python packages before invoking pylint. This avoids -reporting import errors that occur when a module within a package uses the -absolute import path to get another module within this package. - -For example: - - Suppose a package is structured as - - a/__init__.py - a/b/x.py - a/c/y.py - - - Then if y.py imports x as "from a.b import x" the following produces pylint errors - - cd a/c; pylint y.py - - - The following obviously doesn't - - pylint a/c/y.py - - - As this script will be invoked by emacs within the directory of the file - we are checking we need to go out of it to avoid these false positives. - - -You may also use py_run to run pylint with desired options and get back (or not) its output. -""" - -import sys, os, re -from subprocess import Popen, PIPE - - -def lint(filename): - """Pylint the given file. - - When run from emacs we will be in the directory of a file, and passed its filename. - If this file is part of a package and is trying to import other modules from within - its own package or another package rooted in a directory below it, pylint will classify - it as a failed import. - - To get around this, we traverse down the directory tree to find the root of the package this - module is in. We then invoke pylint from this directory. - - Finally, we must correct the filenames in the output generated by pylint so Emacs doesn't - become confused (it will expect just the original filename, while pylint may extend it with - extra directories if we've traversed down the tree) - """ - # traverse downwards until we are out of a python package - fullPath = os.path.abspath(filename) - parentPath, childPath = os.path.dirname(fullPath), os.path.basename(fullPath) - - while parentPath != "/" and os.path.exists(os.path.join(parentPath, '__init__.py')): - childPath = os.path.join(os.path.basename(parentPath), childPath) - parentPath = os.path.dirname(parentPath) - - # Start pylint - process = Popen('pylint -f parseable -r n --disable=C,R,I "%s"' % - childPath, shell=True, stdout=PIPE, stderr=PIPE, - cwd=parentPath) - p = process.stdout - - # The parseable line format is '%(path)s:%(line)s: [%(sigle)s%(obj)s] %(msg)s' - # NOTE: This would be cleaner if we added an Emacs reporter to pylint.reporters.text .. - regex = re.compile(r"\[(?P[WE])(?P.*?)\]") - - def _replacement(mObj): - "Alter to include 'Error' or 'Warning'" - if mObj.group("type") == "W": - replacement = "Warning" - else: - replacement = "Error" - # replace as "Warning (W0511, funcName): Warning Text" - return "%s (%s%s):" % (replacement, mObj.group("type"), mObj.group("remainder")) - - for line in p: - # remove pylintrc warning - if line.startswith("No config file found"): - continue - line = regex.sub(_replacement, line, 1) - # modify the file name thats output to reverse the path traversal we made - parts = line.split(":") - if parts and parts[0] == childPath: - line = ":".join([filename] + parts[1:]) - print line, - - p.close() - -def Run(): - lint(sys.argv[1]) - - -def py_run(command_options='', return_std=False, stdout=None, stderr=None, - script='epylint'): - """Run pylint from python (needs Python >= 2.4). - - ``command_options`` is a string containing ``pylint`` command line options; - ``return_std`` (boolean) indicates return of created standart output - and error (see below); - ``stdout`` and ``stderr`` are 'file-like' objects in which standart output - could be written. - - Calling agent is responsible for stdout/err management (creation, close). - Default standart output and error are those from sys, - or standalone ones (``subprocess.PIPE``) are used - if they are not set and ``return_std``. - - If ``return_std`` is set to ``True``, this function returns a 2-uple - containing standart output and error related to created process, - as follows: ``(stdout, stderr)``. - - A trivial usage could be as follows: - >>> py_run( '--version') - No config file found, using default configuration - pylint 0.18.1, - ... - - To silently run Pylint on a module, and get its standart output and error: - >>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True) - """ - # Create command line to call pylint - if os.name == 'nt': - script += '.bat' - command_line = script + ' ' + command_options - # Providing standart output and/or error if not set - if stdout is None: - if return_std: - stdout = PIPE - else: - stdout = sys.stdout - if stderr is None: - if return_std: - stderr = PIPE - else: - stderr = sys.stderr - # Call pylint in a subprocess - p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr) - p.wait() - # Return standart output and error - if return_std: - return (p.stdout, p.stderr) - - -if __name__ == '__main__': - lint(sys.argv[1]) - diff --git a/pylibs/pylint/gui.py b/pylibs/pylint/gui.py deleted file mode 100644 index 2d8e81e7..00000000 --- a/pylibs/pylint/gui.py +++ /dev/null @@ -1,452 +0,0 @@ -"""Tkinker gui for pylint""" - -import os -import sys -import re -import Queue -from threading import Thread -from Tkinter import (Tk, Frame, Listbox, Entry, Label, Button, Scrollbar, - Checkbutton, Radiobutton, IntVar, StringVar) -from Tkinter import (TOP, LEFT, RIGHT, BOTTOM, END, X, Y, BOTH, SUNKEN, W, - HORIZONTAL, DISABLED, NORMAL, W, E) -from tkFileDialog import askopenfilename, askdirectory - -import pylint.lint -from pylint.reporters.guireporter import GUIReporter - -HOME = os.path.expanduser('~/') -HISTORY = '.pylint-gui-history' -COLORS = {'(I)':'lightblue', - '(C)':'blue', '(R)':'darkblue', - '(W)':'black', '(E)':'darkred', - '(F)':'red'} - -class BasicStream: - ''' - used in gui reporter instead of writing to stdout, it is written to - this stream and saved in contents - ''' - def __init__(self, gui): - """init""" - self.curline = "" - self.gui = gui - self.contents = [] - self.outdict = {} - self.currout = None - self.nextTitle = None - - def write(self, text): - """write text to the stream""" - if re.match('^--+$', text.strip()) or re.match('^==+$', text.strip()): - if self.currout: - self.outdict[self.currout].remove(self.nextTitle) - self.outdict[self.currout].pop() - self.currout = self.nextTitle - self.outdict[self.currout] = [''] - - if text.strip(): - self.nextTitle = text.strip() - - if text.startswith('\n'): - self.contents.append('') - if self.currout: self.outdict[self.currout].append('') - self.contents[-1] += text.strip('\n') - if self.currout: self.outdict[self.currout][-1] += text.strip('\n') - if text.endswith('\n') and text.strip(): - self.contents.append('') - if self.currout: self.outdict[self.currout].append('') - - def fix_contents(self): - """finalize what the contents of the dict should look like before output""" - for item in self.outdict: - numEmpty = self.outdict[item].count('') - for i in range(numEmpty): - self.outdict[item].remove('') - if self.outdict[item]: - self.outdict[item].pop(0) - - def output_contents(self): - """output contents of dict to the gui, and set the rating""" - self.fix_contents() - self.gui.tabs = self.outdict - try: - self.gui.rating.set(self.outdict['Global evaluation'][0]) - except: - self.gui.rating.set('Error') - self.gui.refresh_results_window() - - #reset stream variables for next run - self.contents = [] - self.outdict = {} - self.currout = None - self.nextTitle = None - - -class LintGui: - """Build and control a window to interact with pylint""" - - def __init__(self, root=None): - """init""" - self.root = root or Tk() - self.root.title('Pylint') - #reporter - self.reporter = None - #message queue for output from reporter - self.msg_queue = Queue.Queue() - self.msgs = [] - self.filenames = [] - self.rating = StringVar() - self.tabs = {} - self.report_stream = BasicStream(self) - #gui objects - self.lbMessages = None - self.showhistory = None - self.results = None - self.btnRun = None - self.information_box = None - self.convention_box = None - self.refactor_box = None - self.warning_box = None - self.error_box = None - self.fatal_box = None - self.txtModule = None - self.status = None - self.msg_type_dict = None - self.init_gui() - - def init_gui(self): - """init helper""" - #setting up frames - top_frame = Frame(self.root) - mid_frame = Frame(self.root) - radio_frame = Frame(self.root) - res_frame = Frame(self.root) - msg_frame = Frame(self.root) - check_frame = Frame(self.root) - history_frame = Frame(self.root) - btn_frame = Frame(self.root) - rating_frame = Frame(self.root) - top_frame.pack(side=TOP, fill=X) - mid_frame.pack(side=TOP, fill=X) - history_frame.pack(side=TOP, fill=BOTH, expand=True) - radio_frame.pack(side=TOP, fill=BOTH, expand=True) - rating_frame.pack(side=TOP, fill=BOTH, expand=True) - res_frame.pack(side=TOP, fill=BOTH, expand=True) - check_frame.pack(side=TOP, fill=BOTH, expand=True) - msg_frame.pack(side=TOP, fill=BOTH, expand=True) - btn_frame.pack(side=TOP, fill=X) - - #Message ListBox - rightscrollbar = Scrollbar(msg_frame) - rightscrollbar.pack(side=RIGHT, fill=Y) - bottomscrollbar = Scrollbar(msg_frame, orient=HORIZONTAL) - bottomscrollbar.pack(side=BOTTOM, fill=X) - self.lbMessages = Listbox(msg_frame, - yscrollcommand=rightscrollbar.set, - xscrollcommand=bottomscrollbar.set, - bg="white") - self.lbMessages.pack(expand=True, fill=BOTH) - rightscrollbar.config(command=self.lbMessages.yview) - bottomscrollbar.config(command=self.lbMessages.xview) - - #History ListBoxes - rightscrollbar2 = Scrollbar(history_frame) - rightscrollbar2.pack(side=RIGHT, fill=Y) - bottomscrollbar2 = Scrollbar(history_frame, orient=HORIZONTAL) - bottomscrollbar2.pack(side=BOTTOM, fill=X) - self.showhistory = Listbox(history_frame, - yscrollcommand=rightscrollbar2.set, - xscrollcommand=bottomscrollbar2.set, - bg="white") - self.showhistory.pack(expand=True, fill=BOTH) - rightscrollbar2.config(command=self.showhistory.yview) - bottomscrollbar2.config(command=self.showhistory.xview) - self.showhistory.bind('', self.select_recent_file) - self.set_history_window() - - #status bar - self.status = Label(self.root, text="", bd=1, relief=SUNKEN, anchor=W) - self.status.pack(side=BOTTOM, fill=X) - - #labels - self.lblRatingLabel = Label(rating_frame, text='Rating:') - self.lblRatingLabel.pack(side=LEFT) - self.lblRating = Label(rating_frame, textvariable=self.rating) - self.lblRating.pack(side=LEFT) - Label(mid_frame, text='Recently Used:').pack(side=LEFT) - Label(top_frame, text='Module or package').pack(side=LEFT) - - #file textbox - self.txtModule = Entry(top_frame, background='white') - self.txtModule.bind('', self.run_lint) - self.txtModule.pack(side=LEFT, expand=True, fill=X) - - #results box - rightscrollbar = Scrollbar(res_frame) - rightscrollbar.pack(side=RIGHT, fill=Y) - bottomscrollbar = Scrollbar(res_frame, orient=HORIZONTAL) - bottomscrollbar.pack(side=BOTTOM, fill=X) - self.results = Listbox(res_frame, - yscrollcommand=rightscrollbar.set, - xscrollcommand=bottomscrollbar.set, - bg="white", font="Courier") - self.results.pack(expand=True, fill=BOTH, side=BOTTOM) - rightscrollbar.config(command=self.results.yview) - bottomscrollbar.config(command=self.results.xview) - - #buttons - Button(top_frame, text='Open', command=self.file_open).pack(side=LEFT) - Button(top_frame, text='Open Package', - command=(lambda : self.file_open(package=True))).pack(side=LEFT) - - self.btnRun = Button(top_frame, text='Run', command=self.run_lint) - self.btnRun.pack(side=LEFT) - Button(btn_frame, text='Quit', command=self.quit).pack(side=BOTTOM) - - #radio buttons - self.information_box = IntVar() - self.convention_box = IntVar() - self.refactor_box = IntVar() - self.warning_box = IntVar() - self.error_box = IntVar() - self.fatal_box = IntVar() - i = Checkbutton(check_frame, text="Information", fg=COLORS['(I)'], - variable=self.information_box, command=self.refresh_msg_window) - c = Checkbutton(check_frame, text="Convention", fg=COLORS['(C)'], - variable=self.convention_box, command=self.refresh_msg_window) - r = Checkbutton(check_frame, text="Refactor", fg=COLORS['(R)'], - variable=self.refactor_box, command=self.refresh_msg_window) - w = Checkbutton(check_frame, text="Warning", fg=COLORS['(W)'], - variable=self.warning_box, command=self.refresh_msg_window) - e = Checkbutton(check_frame, text="Error", fg=COLORS['(E)'], - variable=self.error_box, command=self.refresh_msg_window) - f = Checkbutton(check_frame, text="Fatal", fg=COLORS['(F)'], - variable=self.fatal_box, command=self.refresh_msg_window) - i.select() - c.select() - r.select() - w.select() - e.select() - f.select() - i.pack(side=LEFT) - c.pack(side=LEFT) - r.pack(side=LEFT) - w.pack(side=LEFT) - e.pack(side=LEFT) - f.pack(side=LEFT) - - #check boxes - self.box = StringVar() - # XXX should be generated - report = Radiobutton(radio_frame, text="Report", variable=self.box, - value="Report", command=self.refresh_results_window) - rawMet = Radiobutton(radio_frame, text="Raw metrics", variable=self.box, - value="Raw metrics", command=self.refresh_results_window) - dup = Radiobutton(radio_frame, text="Duplication", variable=self.box, - value="Duplication", command=self.refresh_results_window) - ext = Radiobutton(radio_frame, text="External dependencies", - variable=self.box, value="External dependencies", - command=self.refresh_results_window) - stat = Radiobutton(radio_frame, text="Statistics by type", - variable=self.box, value="Statistics by type", - command=self.refresh_results_window) - msgCat = Radiobutton(radio_frame, text="Messages by category", - variable=self.box, value="Messages by category", - command=self.refresh_results_window) - msg = Radiobutton(radio_frame, text="Messages", variable=self.box, - value="Messages", command=self.refresh_results_window) - report.select() - report.grid(column=0, row=0, sticky=W) - rawMet.grid(column=1, row=0, sticky=W) - dup.grid(column=2, row=0, sticky=W) - msg.grid(column=3, row=0, sticky=E) - stat.grid(column=0, row=1, sticky=W) - msgCat.grid(column=1, row=1, sticky=W) - ext.grid(column=2, row=1, columnspan=2, sticky=W) - - #dictionary for check boxes and associated error term - self.msg_type_dict = { - 'I' : lambda : self.information_box.get() == 1, - 'C' : lambda : self.convention_box.get() == 1, - 'R' : lambda : self.refactor_box.get() == 1, - 'E' : lambda : self.error_box.get() == 1, - 'W' : lambda : self.warning_box.get() == 1, - 'F' : lambda : self.fatal_box.get() == 1 - } - self.txtModule.focus_set() - - - def select_recent_file(self, event): - """adds the selected file in the history listbox to the Module box""" - if not self.showhistory.size(): - return - - selected = self.showhistory.curselection() - item = self.showhistory.get(selected) - #update module - self.txtModule.delete(0, END) - self.txtModule.insert(0, item) - - def refresh_msg_window(self): - """refresh the message window with current output""" - #clear the window - self.lbMessages.delete(0, END) - for msg in self.msgs: - if (self.msg_type_dict.get(msg[0])()): - msg_str = self.convert_to_string(msg) - self.lbMessages.insert(END, msg_str) - fg_color = COLORS.get(msg_str[:3], 'black') - self.lbMessages.itemconfigure(END, fg=fg_color) - - def refresh_results_window(self): - """refresh the results window with current output""" - #clear the window - self.results.delete(0, END) - try: - for res in self.tabs[self.box.get()]: - self.results.insert(END, res) - except: - pass - - def convert_to_string(self, msg): - """make a string representation of a message""" - if (msg[2] != ""): - return "(" + msg[0] + ") " + msg[1] + "." + msg[2] + " [" + msg[3] + "]: " + msg[4] - else: - return "(" + msg[0] + ") " + msg[1] + " [" + msg[3] + "]: " + msg[4] - - def process_incoming(self): - """process the incoming messages from running pylint""" - while self.msg_queue.qsize(): - try: - msg = self.msg_queue.get(0) - if msg == "DONE": - self.report_stream.output_contents() - return False - - #adding message to list of msgs - self.msgs.append(msg) - - #displaying msg if message type is selected in check box - if (self.msg_type_dict.get(msg[0])()): - msg_str = self.convert_to_string(msg) - self.lbMessages.insert(END, msg_str) - fg_color = COLORS.get(msg_str[:3], 'black') - self.lbMessages.itemconfigure(END, fg=fg_color) - - except Queue.Empty: - pass - return True - - def periodic_call(self): - """determine when to unlock the run button""" - if self.process_incoming(): - self.root.after(100, self.periodic_call) - else: - #enabling button so it can be run again - self.btnRun.config(state=NORMAL) - - def mainloop(self): - """launch the mainloop of the application""" - self.root.mainloop() - - def quit(self, _=None): - """quit the application""" - self.root.quit() - - def halt(self): - """program halt placeholder""" - return - - def file_open(self, package=False, _=None): - """launch a file browser""" - if not package: - filename = askopenfilename(parent=self.root, filetypes=[('pythonfiles', '*.py'), - ('allfiles', '*')], title='Select Module') - else: - filename = askdirectory(title="Select A Folder", mustexist=1) - - if filename == (): - return - - self.txtModule.delete(0, END) - self.txtModule.insert(0, filename) - - def update_filenames(self): - """update the list of recent filenames""" - filename = self.txtModule.get() - if not filename: - filename = os.getcwd() - if filename+'\n' in self.filenames: - index = self.filenames.index(filename+'\n') - self.filenames.pop(index) - - #ensure only 10 most recent are stored - if len(self.filenames) == 10: - self.filenames.pop() - self.filenames.insert(0, filename+'\n') - - def set_history_window(self): - """update the history window with info from the history file""" - #clear the window - self.showhistory.delete(0, END) - # keep the last 10 most recent files - try: - view_history = open(HOME+HISTORY, 'r') - for hist in view_history.readlines(): - if not hist in self.filenames: - self.filenames.append(hist) - self.showhistory.insert(END, hist.split('\n')[0]) - view_history.close() - except IOError: - # do nothing since history file will be created later - return - - def run_lint(self, _=None): - """launches pylint""" - self.update_filenames() - self.root.configure(cursor='watch') - self.reporter = GUIReporter(self, output=self.report_stream) - module = self.txtModule.get() - if not module: - module = os.getcwd() - - #cleaning up msgs and windows - self.msgs = [] - self.lbMessages.delete(0, END) - self.tabs = {} - self.results.delete(0, END) - self.btnRun.config(state=DISABLED) - - #setting up a worker thread to run pylint - worker = Thread(target=lint_thread, args=(module, self.reporter, self,)) - self.periodic_call() - worker.start() - - # Overwrite the .pylint-gui-history file with all the new recently added files - # in order from filenames but only save last 10 files - write_history = open(HOME+HISTORY, 'w') - write_history.writelines(self.filenames) - write_history.close() - self.set_history_window() - - self.root.configure(cursor='') - - -def lint_thread(module, reporter, gui): - """thread for pylint""" - gui.status.text = "processing module(s)" - lint_obj = pylint.lint.Run(args=[module], reporter=reporter, exit=False) - gui.msg_queue.put("DONE") - - -def Run(args): - """launch pylint gui from args""" - if args: - print 'USAGE: pylint-gui\n launch a simple pylint gui using Tk' - return - gui = LintGui() - gui.mainloop() - -if __name__ == '__main__': - Run(sys.argv[1:]) diff --git a/pylibs/pylint/lint.py b/pylibs/pylint/lint.py index d3bb00ea..e39a880f 100644 --- a/pylibs/pylint/lint.py +++ b/pylibs/pylint/lint.py @@ -1,5 +1,5 @@ # Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). -# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -68,6 +68,18 @@ 'html': HTMLReporter,} +def _get_python_path(filepath): + dirname = os.path.dirname(os.path.realpath( + os.path.expanduser(filepath))) + while True: + if not os.path.exists(os.path.join(dirname, "__init__.py")): + return dirname + old_dirname = dirname + dirname = os.path.dirname(dirname) + if old_dirname == dirname: + return os.getcwd() + + # Python Linter class ######################################################### MSGS = { @@ -140,84 +152,86 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, msgs = MSGS may_be_disabled = False - options = (('ignore', - {'type' : 'csv', 'metavar' : '[,...]', - 'dest' : 'black_list', 'default' : ('CVS',), - 'help' : 'Add files or directories to the blacklist. \ + @staticmethod + def make_options(): + return (('ignore', + {'type' : 'csv', 'metavar' : '[,...]', + 'dest' : 'black_list', 'default' : ('CVS',), + 'help' : 'Add files or directories to the blacklist. \ They should be base names, not paths.'}), - ('persistent', - {'default': True, 'type' : 'yn', 'metavar' : '', - 'level': 1, - 'help' : 'Pickle collected data for later comparisons.'}), - - ('load-plugins', - {'type' : 'csv', 'metavar' : '', 'default' : (), - 'level': 1, - 'help' : 'List of plugins (as comma separated values of \ + ('persistent', + {'default': True, 'type' : 'yn', 'metavar' : '', + 'level': 1, + 'help' : 'Pickle collected data for later comparisons.'}), + + ('load-plugins', + {'type' : 'csv', 'metavar' : '', 'default' : (), + 'level': 1, + 'help' : 'List of plugins (as comma separated values of \ python modules names) to load, usually to register additional checkers.'}), - ('output-format', - {'default': 'text', 'type': 'choice', 'metavar' : '', - 'choices': ('text', 'parseable', 'msvs', 'colorized', 'html'), - 'short': 'f', - 'group': 'Reports', - 'help' : 'Set the output format. Available formats are text,\ + ('output-format', + {'default': 'text', 'type': 'choice', 'metavar' : '', + 'choices': REPORTER_OPT_MAP.keys(), + 'short': 'f', + 'group': 'Reports', + 'help' : 'Set the output format. Available formats are text,\ parseable, colorized, msvs (visual studio) and html'}), - ('include-ids', - {'type' : 'yn', 'metavar' : '', 'default' : 0, - 'short': 'i', - 'group': 'Reports', - 'help' : 'Include message\'s id in output'}), + ('include-ids', + {'type' : 'yn', 'metavar' : '', 'default' : 0, + 'short': 'i', + 'group': 'Reports', + 'help' : 'Include message\'s id in output'}), - ('files-output', - {'default': 0, 'type' : 'yn', 'metavar' : '', - 'group': 'Reports', 'level': 1, - 'help' : 'Put messages in a separate file for each module / \ + ('files-output', + {'default': 0, 'type' : 'yn', 'metavar' : '', + 'group': 'Reports', 'level': 1, + 'help' : 'Put messages in a separate file for each module / \ package specified on the command line instead of printing them on stdout. \ Reports (if any) will be written in a file name "pylint_global.[txt|html]".'}), - ('reports', - {'default': 1, 'type' : 'yn', 'metavar' : '', - 'short': 'r', - 'group': 'Reports', - 'help' : 'Tells whether to display a full report or only the\ + ('reports', + {'default': 1, 'type' : 'yn', 'metavar' : '', + 'short': 'r', + 'group': 'Reports', + 'help' : 'Tells whether to display a full report or only the\ messages'}), - ('evaluation', - {'type' : 'string', 'metavar' : '', - 'group': 'Reports', 'level': 1, - 'default': '10.0 - ((float(5 * error + warning + refactor + \ + ('evaluation', + {'type' : 'string', 'metavar' : '', + 'group': 'Reports', 'level': 1, + 'default': '10.0 - ((float(5 * error + warning + refactor + \ convention) / statement) * 10)', - 'help' : 'Python expression which should return a note less \ + 'help' : 'Python expression which should return a note less \ than 10 (10 is the highest note). You have access to the variables errors \ warning, statement which respectively contain the number of errors / warnings\ messages and the total number of statements analyzed. This is used by the \ global evaluation report (RP0004).'}), - ('comment', - {'default': 0, 'type' : 'yn', 'metavar' : '', - 'group': 'Reports', 'level': 1, - 'help' : 'Add a comment according to your evaluation note. \ + ('comment', + {'default': 0, 'type' : 'yn', 'metavar' : '', + 'group': 'Reports', 'level': 1, + 'help' : 'Add a comment according to your evaluation note. \ This is used by the global evaluation report (RP0004).'}), - ('enable', - {'type' : 'csv', 'metavar': '', - 'short': 'e', - 'group': 'Messages control', - 'help' : 'Enable the message, report, category or checker with the ' - 'given id(s). You can either give multiple identifier ' - 'separated by comma (,) or put this option multiple time.'}), - - ('disable', - {'type' : 'csv', 'metavar': '', - 'short': 'd', - 'group': 'Messages control', - 'help' : 'Disable the message, report, category or checker ' - 'with the given id(s). You can either give multiple identifier' - ' separated by comma (,) or put this option multiple time ' - '(only on the command line, not in the configuration file ' - 'where it should appear only once).'}), + ('enable', + {'type' : 'csv', 'metavar': '', + 'short': 'e', + 'group': 'Messages control', + 'help' : 'Enable the message, report, category or checker with the ' + 'given id(s). You can either give multiple identifier ' + 'separated by comma (,) or put this option multiple time.'}), + + ('disable', + {'type' : 'csv', 'metavar': '', + 'short': 'd', + 'group': 'Messages control', + 'help' : 'Disable the message, report, category or checker ' + 'with the given id(s). You can either give multiple identifier' + ' separated by comma (,) or put this option multiple time ' + '(only on the command line, not in the configuration file ' + 'where it should appear only once).'}), ) option_groups = ( @@ -240,7 +254,7 @@ def __init__(self, options=(), reporter=None, option_groups=(), self.current_file = None self.stats = None # init options - self.options = options + PyLinter.options + self.options = options + PyLinter.make_options() self.option_groups = option_groups + PyLinter.option_groups self._options_methods = { 'enable': self.enable, @@ -270,6 +284,10 @@ def __init__(self, options=(), reporter=None, option_groups=(), self.load_provider_defaults() self.set_reporter(reporter or TextReporter(sys.stdout)) + def load_default_plugins(self): + from pylint import checkers + checkers.initialize(self) + def load_plugin_modules(self, modnames): """take a list of module names which are pylint plugins and load and register them @@ -338,10 +356,10 @@ def disable_noerror_messages(self): self.disable(msgid) def disable_reporters(self): - """disable all reporters""" - for reporters in self._reports.values(): - for report_id, _title, _cb in reporters: - self.disable_report(report_id) + """disable all reporters""" + for reporters in self._reports.values(): + for report_id, _title, _cb in reporters: + self.disable_report(report_id) def error_mode(self): """error mode: enable only errors; no reports, no persistent""" @@ -494,10 +512,6 @@ def check(self, files_or_modules): # if it's actually a c extension) self.current_file = astng.file self.check_astng_module(astng, walker, rawcheckers) - - # Close file for windows users - astng.file_stream.close() - # notify global end self.set_current_module('') self.stats['statement'] = walker.nbstatements @@ -524,6 +538,7 @@ def set_current_module(self, modname, filepath=None): """ if not modname and filepath is None: return + self.reporter.on_set_current_module(modname, filepath) self.current_name = modname self.current_file = filepath or modname self.stats['by_module'][modname] = {} @@ -588,19 +603,26 @@ def close(self): if persistent run, pickle results for later comparison """ if self.base_name is not None: - # load old results if any - old_stats = config.load_results(self.base_name) + # load previous results if any + previous_stats = config.load_results(self.base_name) + # XXX code below needs refactoring to be more reporter agnostic + self.reporter.on_close(self.stats, previous_stats) if self.config.reports: - self.make_reports(self.stats, old_stats) - elif self.config.output_format == 'html': - self.reporter.display_results(Section()) + sect = self.make_reports(self.stats, previous_stats) + if self.config.files_output: + filename = 'pylint_global.' + self.reporter.extension + self.reporter.set_output(open(filename, 'w')) + else: + sect = Section() + if self.config.reports or self.config.output_format == 'html': + self.reporter.display_results(sect) # save results if persistent run if self.config.persistent: config.save_results(self.stats, self.base_name) # specific reports ######################################################## - def report_evaluation(self, sect, stats, old_stats): + def report_evaluation(self, sect, stats, previous_stats): """make the global evaluation report""" # check with at least check 1 statements (usually 0 when there is a # syntax error preventing pylint from further processing) @@ -615,18 +637,18 @@ def report_evaluation(self, sect, stats, old_stats): else: stats['global_note'] = note msg = 'Your code has been rated at %.2f/10' % note - if 'global_note' in old_stats: - msg += ' (previous run: %.2f/10)' % old_stats['global_note'] + if 'global_note' in previous_stats: + msg += ' (previous run: %.2f/10)' % previous_stats['global_note'] if self.config.comment: msg = '%s\n%s' % (msg, config.get_note_message(note)) sect.append(Text(msg)) # some reporting functions #################################################### -def report_total_messages_stats(sect, stats, old_stats): +def report_total_messages_stats(sect, stats, previous_stats): """make total errors / warnings report""" lines = ['type', 'number', 'previous', 'difference'] - lines += table_lines_from_stats(stats, old_stats, + lines += table_lines_from_stats(stats, previous_stats, ('convention', 'refactor', 'warning', 'error')) sect.append(Table(children=lines, cols=4, rheaders=1)) @@ -806,35 +828,34 @@ def __init__(self, args, reporter=None, exit=True): ), option_groups=self.option_groups, reporter=reporter, pylintrc=self._rcfile) # register standard checkers - from pylint import checkers - checkers.initialize(linter) + linter.load_default_plugins() # load command line plugins linter.load_plugin_modules(self._plugins) # add some help section linter.add_help_section('Environment variables', config.ENV_HELP, level=1) linter.add_help_section('Output', ''' -Using the default text output, the message format is : - - MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE - -There are 5 kind of message types : - * (C) convention, for programming standard violation - * (R) refactor, for bad code smell - * (W) warning, for python specific problems - * (E) error, for probable bugs in the code +Using the default text output, the message format is : + + MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE + +There are 5 kind of message types : + * (C) convention, for programming standard violation + * (R) refactor, for bad code smell + * (W) warning, for python specific problems + * (E) error, for probable bugs in the code * (F) fatal, if an error occurred which prevented pylint from doing further processing. ''', level=1) linter.add_help_section('Output status code', ''' -Pylint should leave with following status code: - * 0 if everything went fine - * 1 if a fatal message was issued - * 2 if an error message was issued - * 4 if a warning message was issued - * 8 if a refactor message was issued - * 16 if a convention message was issued - * 32 on usage error - +Pylint should leave with following status code: + * 0 if everything went fine + * 1 if a fatal message was issued + * 2 if an error message was issued + * 4 if a warning message was issued + * 8 if a refactor message was issued + * 16 if a convention message was issued + * 32 on usage error + status 1 to 16 will be bit-ORed so you can know which different categories has been issued by analysing pylint output status code ''', level=1) @@ -865,7 +886,10 @@ def __init__(self, args, reporter=None, exit=True): sys.exit(32) # insert current working directory to the python path to have a correct # behaviour - sys.path.insert(0, os.getcwd()) + if len(args) == 1: + sys.path.insert(0, _get_python_path(args[0])) + else: + sys.path.insert(0, os.getcwd()) if self.linter.config.profile: print >> sys.stderr, '** profiled run' import cProfile, pstats diff --git a/pylibs/pylint/pyreverse/__init__.py b/pylibs/pylint/pyreverse/__init__.py deleted file mode 100644 index 8c32ad96..00000000 --- a/pylibs/pylint/pyreverse/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -pyreverse.extensions -""" - -__revision__ = "$Id $" diff --git a/pylibs/pylint/pyreverse/diadefslib.py b/pylibs/pylint/pyreverse/diadefslib.py deleted file mode 100644 index 68ca68ca..00000000 --- a/pylibs/pylint/pyreverse/diadefslib.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright (c) 2000-2010 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# 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., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -"""handle diagram generation options for class diagram or default diagrams -""" - -from logilab.common.compat import builtins -BUILTINS_NAME = builtins.__name__ -from logilab import astng -from logilab.astng.utils import LocalsVisitor - -from pylint.pyreverse.diagrams import PackageDiagram, ClassDiagram - -# diagram generators ########################################################## - -class DiaDefGenerator: - """handle diagram generation options - """ - def __init__(self, linker, handler): - """common Diagram Handler initialization""" - self.config = handler.config - self._set_default_options() - self.linker = linker - self.classdiagram = None # defined by subclasses - - def get_title(self, node): - """get title for objects""" - title = node.name - if self.module_names: - title = '%s.%s' % (node.root().name, title) - return title - - def _set_option(self, option): - """activate some options if not explicitly deactivated""" - # if we have a class diagram, we want more information by default; - # so if the option is None, we return True - if option is None: - if self.config.classes: - return True - else: - return False - return option - - def _set_default_options(self): - """set different default options with _default dictionary""" - self.module_names = self._set_option(self.config.module_names) - all_ancestors = self._set_option(self.config.all_ancestors) - all_associated = self._set_option(self.config.all_associated) - anc_level, ass_level = (0, 0) - if all_ancestors: - anc_level = -1 - if all_associated: - ass_level = -1 - if self.config.show_ancestors is not None: - anc_level = self.config.show_ancestors - if self.config.show_associated is not None: - ass_level = self.config.show_associated - self.anc_level, self.ass_level = anc_level, ass_level - - def _get_levels(self): - """help function for search levels""" - return self.anc_level, self.ass_level - - def show_node(self, node): - """true if builtins and not show_builtins""" - if self.config.show_builtin: - return True - return node.root().name != BUILTINS_NAME - - def add_class(self, node): - """visit one class and add it to diagram""" - self.linker.visit(node) - self.classdiagram.add_object(self.get_title(node), node) - - def get_ancestors(self, node, level): - """return ancestor nodes of a class node""" - if level == 0: - return - for ancestor in node.ancestors(recurs=False): - if not self.show_node(ancestor): - continue - yield ancestor - - def get_associated(self, klass_node, level): - """return associated nodes of a class node""" - if level == 0: - return - for ass_nodes in klass_node.instance_attrs_type.values() + \ - klass_node.locals_type.values(): - for ass_node in ass_nodes: - if isinstance(ass_node, astng.Instance): - ass_node = ass_node._proxied - if not (isinstance(ass_node, astng.Class) - and self.show_node(ass_node)): - continue - yield ass_node - - def extract_classes(self, klass_node, anc_level, ass_level): - """extract recursively classes related to klass_node""" - if self.classdiagram.has_node(klass_node) or not self.show_node(klass_node): - return - self.add_class(klass_node) - - for ancestor in self.get_ancestors(klass_node, anc_level): - self.extract_classes(ancestor, anc_level-1, ass_level) - - for ass_node in self.get_associated(klass_node, ass_level): - self.extract_classes(ass_node, anc_level, ass_level-1) - - -class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): - """generate minimum diagram definition for the project : - - * a package diagram including project's modules - * a class diagram including project's classes - """ - - def __init__(self, linker, handler): - DiaDefGenerator.__init__(self, linker, handler) - LocalsVisitor.__init__(self) - - def visit_project(self, node): - """visit an astng.Project node - - create a diagram definition for packages - """ - mode = self.config.mode - if len(node.modules) > 1: - self.pkgdiagram = PackageDiagram('packages %s' % node.name, mode) - else: - self.pkgdiagram = None - self.classdiagram = ClassDiagram('classes %s' % node.name, mode) - - def leave_project(self, node): - """leave the astng.Project node - - return the generated diagram definition - """ - if self.pkgdiagram: - return self.pkgdiagram, self.classdiagram - return self.classdiagram, - - def visit_module(self, node): - """visit an astng.Module node - - add this class to the package diagram definition - """ - if self.pkgdiagram: - self.linker.visit(node) - self.pkgdiagram.add_object(node.name, node) - - def visit_class(self, node): - """visit an astng.Class node - - add this class to the class diagram definition - """ - anc_level, ass_level = self._get_levels() - self.extract_classes(node, anc_level, ass_level) - - def visit_from(self, node): - """visit astng.From and catch modules for package diagram - """ - if self.pkgdiagram: - self.pkgdiagram.add_from_depend(node, node.modname) - - -class ClassDiadefGenerator(DiaDefGenerator): - """generate a class diagram definition including all classes related to a - given class - """ - - def __init__(self, linker, handler): - DiaDefGenerator.__init__(self, linker, handler) - - def class_diagram(self, project, klass): - """return a class diagram definition for the given klass and its - related klasses - """ - - self.classdiagram = ClassDiagram(klass, self.config.mode) - if len(project.modules) > 1: - module, klass = klass.rsplit('.', 1) - module = project.get_module(module) - else: - module = project.modules[0] - klass = klass.split('.')[-1] - klass = module.ilookup(klass).next() - - anc_level, ass_level = self._get_levels() - self.extract_classes(klass, anc_level, ass_level) - return self.classdiagram - -# diagram handler ############################################################# - -class DiadefsHandler: - """handle diagram definitions : - - get it from user (i.e. xml files) or generate them - """ - - def __init__(self, config): - self.config = config - - def get_diadefs(self, project, linker): - """get the diagrams configuration data - :param linker: astng.inspector.Linker(IdGeneratorMixIn, LocalsVisitor) - :param project: astng.manager.Project - """ - - # read and interpret diagram definitions (Diadefs) - diagrams = [] - generator = ClassDiadefGenerator(linker, self) - for klass in self.config.classes: - diagrams.append(generator.class_diagram(project, klass)) - if not diagrams: - diagrams = DefaultDiadefGenerator(linker, self).visit(project) - for diagram in diagrams: - diagram.extract_relationships() - return diagrams diff --git a/pylibs/pylint/pyreverse/diagrams.py b/pylibs/pylint/pyreverse/diagrams.py deleted file mode 100644 index 23d23eff..00000000 --- a/pylibs/pylint/pyreverse/diagrams.py +++ /dev/null @@ -1,247 +0,0 @@ -# Copyright (c) 2004-2010 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# 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., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -"""diagram objects -""" - -from logilab import astng -from pylint.pyreverse.utils import is_interface, FilterMixIn - -def set_counter(value): - """Figure counter (re)set""" - Figure._UID_COUNT = value - -class Figure: - """base class for counter handling""" - _UID_COUNT = 0 - def __init__(self): - Figure._UID_COUNT += 1 - self.fig_id = Figure._UID_COUNT - -class Relationship(Figure): - """a relation ship from an object in the diagram to another - """ - def __init__(self, from_object, to_object, relation_type, name=None): - Figure.__init__(self) - self.from_object = from_object - self.to_object = to_object - self.type = relation_type - self.name = name - - -class DiagramEntity(Figure): - """a diagram object, i.e. a label associated to an astng node - """ - def __init__(self, title='No name', node=None): - Figure.__init__(self) - self.title = title - self.node = node - -class ClassDiagram(Figure, FilterMixIn): - """main class diagram handling - """ - TYPE = 'class' - def __init__(self, title, mode): - FilterMixIn.__init__(self, mode) - Figure.__init__(self) - self.title = title - self.objects = [] - self.relationships = {} - self._nodes = {} - self.depends = [] - - def add_relationship(self, from_object, to_object, - relation_type, name=None): - """create a relation ship - """ - rel = Relationship(from_object, to_object, relation_type, name) - self.relationships.setdefault(relation_type, []).append(rel) - - def get_relationship(self, from_object, relation_type): - """return a relation ship or None - """ - for rel in self.relationships.get(relation_type, ()): - if rel.from_object is from_object: - return rel - raise KeyError(relation_type) - - def get_attrs(self, node): - """return visible attributes, possibly with class name""" - attrs = [] - for node_name, ass_nodes in node.instance_attrs_type.items() + \ - node.locals_type.items(): - if not self.show_attr(node_name): - continue - names = self.class_names(ass_nodes) - if names: - node_name = "%s : %s" % (node_name, ", ".join(names)) - attrs.append(node_name) - return attrs - - def get_methods(self, node): - """return visible methods""" - return [m for m in node.values() - if isinstance(m, astng.Function) and self.show_attr(m.name)] - - def add_object(self, title, node): - """create a diagram object - """ - assert node not in self._nodes - ent = DiagramEntity(title, node) - self._nodes[node] = ent - self.objects.append(ent) - - def class_names(self, nodes): - """return class names if needed in diagram""" - names = [] - for ass_node in nodes: - if isinstance(ass_node, astng.Instance): - ass_node = ass_node._proxied - if isinstance(ass_node, astng.Class) \ - and hasattr(ass_node, "name") and not self.has_node(ass_node): - if ass_node.name not in names: - ass_name = ass_node.name - names.append(ass_name) - return names - - def nodes(self): - """return the list of underlying nodes - """ - return self._nodes.keys() - - def has_node(self, node): - """return true if the given node is included in the diagram - """ - return node in self._nodes - - def object_from_node(self, node): - """return the diagram object mapped to node - """ - return self._nodes[node] - - def classes(self): - """return all class nodes in the diagram""" - return [o for o in self.objects if isinstance(o.node, astng.Class)] - - def classe(self, name): - """return a class by its name, raise KeyError if not found - """ - for klass in self.classes(): - if klass.node.name == name: - return klass - raise KeyError(name) - - def extract_relationships(self): - """extract relation ships between nodes in the diagram - """ - for obj in self.classes(): - node = obj.node - obj.attrs = self.get_attrs(node) - obj.methods = self.get_methods(node) - # shape - if is_interface(node): - obj.shape = 'interface' - else: - obj.shape = 'class' - # inheritance link - for par_node in node.ancestors(recurs=False): - try: - par_obj = self.object_from_node(par_node) - self.add_relationship(obj, par_obj, 'specialization') - except KeyError: - continue - # implements link - for impl_node in node.implements: - try: - impl_obj = self.object_from_node(impl_node) - self.add_relationship(obj, impl_obj, 'implements') - except KeyError: - continue - # associations link - for name, values in node.instance_attrs_type.items() + \ - node.locals_type.items(): - for value in values: - if value is astng.YES: - continue - if isinstance( value, astng.Instance): - value = value._proxied - try: - ass_obj = self.object_from_node(value) - self.add_relationship(ass_obj, obj, 'association', name) - except KeyError: - continue - - -class PackageDiagram(ClassDiagram): - """package diagram handling - """ - TYPE = 'package' - - def modules(self): - """return all module nodes in the diagram""" - return [o for o in self.objects if isinstance(o.node, astng.Module)] - - def module(self, name): - """return a module by its name, raise KeyError if not found - """ - for mod in self.modules(): - if mod.node.name == name: - return mod - raise KeyError(name) - - def get_module(self, name, node): - """return a module by its name, looking also for relative imports; - raise KeyError if not found - """ - for mod in self.modules(): - mod_name = mod.node.name - if mod_name == name: - return mod - #search for fullname of relative import modules - package = node.root().name - if mod_name == "%s.%s" % (package, name): - return mod - if mod_name == "%s.%s" % (package.rsplit('.', 1)[0], name): - return mod - raise KeyError(name) - - def add_from_depend(self, node, from_module): - """add dependencies created by from-imports - """ - mod_name = node.root().name - obj = self.module( mod_name ) - if from_module not in obj.node.depends: - obj.node.depends.append(from_module) - - def extract_relationships(self): - """extract relation ships between nodes in the diagram - """ - ClassDiagram.extract_relationships(self) - for obj in self.classes(): - # ownership - try: - mod = self.object_from_node(obj.node.root()) - self.add_relationship(obj, mod, 'ownership') - except KeyError: - continue - for obj in self.modules(): - obj.shape = 'package' - # dependencies - for dep_name in obj.node.depends: - try: - dep = self.get_module(dep_name, obj.node) - except KeyError: - continue - self.add_relationship(obj, dep, 'depends') diff --git a/pylibs/pylint/pyreverse/main.py b/pylibs/pylint/pyreverse/main.py deleted file mode 100644 index da80bd63..00000000 --- a/pylibs/pylint/pyreverse/main.py +++ /dev/null @@ -1,129 +0,0 @@ -# # Copyright (c) 2000-2010 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# 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., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -""" - %prog [options] - - create UML diagrams for classes and modules in -""" - -import sys, os -from logilab.common.configuration import ConfigurationMixIn -from logilab.astng.manager import ASTNGManager -from logilab.astng.inspector import Linker - -from pylint.pyreverse.diadefslib import DiadefsHandler -from pylint.pyreverse import writer -from pylint.pyreverse.utils import insert_default_options - -OPTIONS = ( -("filter-mode", - dict(short='f', default='PUB_ONLY', dest='mode', type='string', - action='store', metavar='', - help="""filter attributes and functions according to - . Correct modes are : - 'PUB_ONLY' filter all non public attributes - [DEFAULT], equivalent to PRIVATE+SPECIAL_A - 'ALL' no filter - 'SPECIAL' filter Python special functions - except constructor - 'OTHER' filter protected and private - attributes""")), - -("class", -dict(short='c', action="append", metavar="", dest="classes", default=[], - help="create a class diagram with all classes related to ;\ - this uses by default the options -ASmy")), - -("show-ancestors", -dict(short="a", action="store", metavar='', type='int', - help='show generations of ancestor classes not in ')), -("all-ancestors", -dict(short="A", default=None, - help="show all ancestors off all classes in ") ), -("show-associated", -dict(short='s', action="store", metavar='', type='int', - help='show levels of associated classes not in ')), -("all-associated", -dict(short='S', default=None, - help='show recursively all associated off all associated classes')), - -("show-builtin", -dict(short="b", action="store_true", default=False, - help='include builtin objects in representation of classes')), - -("module-names", -dict(short="m", default=None, type='yn', metavar='[yn]', - help='include module name in representation of classes')), -# TODO : generate dependencies like in pylint -#("package-dependencies", -#dict(short="M", action="store", metavar='', type='int', - #help='show module dependencies beyond modules in \ -# (for the package diagram)')), -("only-classnames", -dict(short='k', action="store_true", default=False, - help="don't show attributes and methods in the class boxes; \ -this disables -f values")), -("output", dict(short="o", dest="output_format", action="store", - default="dot", metavar="", - help="create a *. output file if format available.")), -) -# FIXME : quiet mode -#( ('quiet', - #dict(help='run quietly', action='store_true', short='q')), ) - -class PyreverseCommand(ConfigurationMixIn): - """base class providing common behaviour for pyreverse commands""" - - options = OPTIONS - - def __init__(self, args): - ConfigurationMixIn.__init__(self, usage=__doc__) - insert_default_options() - self.manager = ASTNGManager() - self.register_options_provider(self.manager) - args = self.load_command_line_configuration() - self.run(args) - - def run(self, args): - """checking arguments and run project""" - if not args: - print self.help() - return - # insert current working directory to the python path to recognize - # dependencies to local modules even if cwd is not in the PYTHONPATH - sys.path.insert(0, os.getcwd()) - try: - project = self.manager.project_from_files(args) - linker = Linker(project, tag=True) - handler = DiadefsHandler(self.config) - diadefs = handler.get_diadefs(project, linker) - finally: - sys.path.pop(0) - - if self.config.output_format == "vcg": - writer.VCGWriter(self.config).write(diadefs) - else: - writer.DotWriter(self.config).write(diadefs) - - -class Run: - """pyreverse main class""" - def __init__(self, args): - """run pyreverse""" - PyreverseCommand(args) - -if __name__ == '__main__': - Run(sys.argv[1:]) diff --git a/pylibs/pylint/pyreverse/utils.py b/pylibs/pylint/pyreverse/utils.py deleted file mode 100644 index ea8b67cc..00000000 --- a/pylibs/pylint/pyreverse/utils.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright (c) 2002-2010 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# 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., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -""" -generic classes/functions for pyreverse core/extensions -""" - -import sys -import re -import os - -########### pyreverse option utils ############################## - - -RCFILE = '.pyreverserc' - -def get_default_options(): - """ - Read config file and return list of options - """ - options = [] - home = os.environ.get('HOME', '') - if home: - rcfile = os.path.join(home, RCFILE) - try: - options = open(rcfile).read().split() - except IOError: - pass # ignore if no config file found - return options - -def insert_default_options(): - """insert default options to sys.argv - """ - options = get_default_options() - options.reverse() - for arg in options: - sys.argv.insert(1, arg) - - - -# astng utilities ########################################################### - -SPECIAL = re.compile('^__[A-Za-z0-9]+[A-Za-z0-9_]*__$') -PRIVATE = re.compile('^__[_A-Za-z0-9]*[A-Za-z0-9]+_?$') -PROTECTED = re.compile('^_[_A-Za-z0-9]*$') - -def get_visibility(name): - """return the visibility from a name: public, protected, private or special - """ - if SPECIAL.match(name): - visibility = 'special' - elif PRIVATE.match(name): - visibility = 'private' - elif PROTECTED.match(name): - visibility = 'protected' - - else: - visibility = 'public' - return visibility - -ABSTRACT = re.compile('^.*Abstract.*') -FINAL = re.compile('^[A-Z_]*$') - -def is_abstract(node): - """return true if the given class node correspond to an abstract class - definition - """ - return ABSTRACT.match(node.name) - -def is_final(node): - """return true if the given class/function node correspond to final - definition - """ - return FINAL.match(node.name) - -def is_interface(node): - # bw compat - return node.type == 'interface' - -def is_exception(node): - # bw compat - return node.type == 'exception' - - -# Helpers ##################################################################### - -_CONSTRUCTOR = 1 -_SPECIAL = 2 -_PROTECTED = 4 -_PRIVATE = 8 -MODES = { - 'ALL' : 0, - 'PUB_ONLY' : _SPECIAL + _PROTECTED + _PRIVATE, - 'SPECIAL' : _SPECIAL, - 'OTHER' : _PROTECTED + _PRIVATE, -} -VIS_MOD = {'special': _SPECIAL, 'protected': _PROTECTED, \ - 'private': _PRIVATE, 'public': 0 } - -class FilterMixIn: - """filter nodes according to a mode and nodes' visibility - """ - def __init__(self, mode): - "init filter modes" - __mode = 0 - for nummod in mode.split('+'): - try: - __mode += MODES[nummod] - except KeyError, ex: - print >> sys.stderr, 'Unknown filter mode %s' % ex - self.__mode = __mode - - - def show_attr(self, node): - """return true if the node should be treated - """ - visibility = get_visibility(getattr(node, 'name', node)) - return not (self.__mode & VIS_MOD[visibility] ) - diff --git a/pylibs/pylint/pyreverse/writer.py b/pylibs/pylint/pyreverse/writer.py deleted file mode 100644 index 6dbfc265..00000000 --- a/pylibs/pylint/pyreverse/writer.py +++ /dev/null @@ -1,196 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2008-2010 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# 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., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -""" -Utilities for creating VCG and Dot diagrams. -""" - -from logilab.common.vcgutils import VCGPrinter -from logilab.common.graph import DotBackend - -from pylint.pyreverse.utils import is_exception - -class DiagramWriter: - """base class for writing project diagrams - """ - def __init__(self, config, styles): - self.config = config - self.pkg_edges, self.inh_edges, self.imp_edges, self.ass_edges = styles - self.printer = None # defined in set_printer - - def write(self, diadefs): - """write files for according to - """ - for diagram in diadefs: - basename = diagram.title.strip().replace(' ', '_') - file_name = '%s.%s' % (basename, self.config.output_format) - self.set_printer(file_name, basename) - if diagram.TYPE == 'class': - self.write_classes(diagram) - else: - self.write_packages(diagram) - self.close_graph() - - def write_packages(self, diagram): - """write a package diagram""" - for obj in diagram.modules(): - label = self.get_title(obj) - self.printer.emit_node(obj.fig_id, label=label, shape='box') - # package dependencies - for rel in diagram.relationships.get('depends', ()): - self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, - **self.pkg_edges) - - def write_classes(self, diagram): - """write a class diagram""" - for obj in diagram.objects: - self.printer.emit_node(obj.fig_id, **self.get_values(obj) ) - # inheritance links - for rel in diagram.relationships.get('specialization', ()): - self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, - **self.inh_edges) - # implementation links - for rel in diagram.relationships.get('implements', ()): - self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, - **self.imp_edges) - # generate associations - for rel in diagram.relationships.get('association', ()): - self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, - label=rel.name, **self.ass_edges) - - def set_printer(self, file_name, basename): - """set printer""" - raise NotImplementedError - - def get_title(self, obj): - """get project title""" - raise NotImplementedError - - def get_values(self, obj): - """get label and shape for classes.""" - raise NotImplementedError - - def close_graph(self): - """finalize the graph""" - raise NotImplementedError - - -class DotWriter(DiagramWriter): - """write dot graphs from a diagram definition and a project - """ - - def __init__(self, config): - styles = [dict(arrowtail='none', arrowhead="open"), - dict(arrowtail = "none", arrowhead='empty'), - dict(arrowtail="node", arrowhead='empty', style='dashed'), - dict(fontcolor='green', arrowtail='none', - arrowhead='diamond', style='solid') ] - DiagramWriter.__init__(self, config, styles) - - def set_printer(self, file_name, basename): - """initialize DotWriter and add options for layout. - """ - layout = dict(rankdir="BT") - self.printer = DotBackend(basename, additionnal_param=layout) - self.file_name = file_name - - def get_title(self, obj): - """get project title""" - return obj.title - - def get_values(self, obj): - """get label and shape for classes. - - The label contains all attributes and methods - """ - label = obj.title - if obj.shape == 'interface': - label = "«interface»\\n%s" % label - if not self.config.only_classnames: - label = "%s|%s\l|" % (label, r"\l".join(obj.attrs) ) - for func in obj.methods: - label = r'%s%s()\l' % (label, func.name) - label = '{%s}' % label - if is_exception(obj.node): - return dict(fontcolor="red", label=label, shape="record") - return dict(label=label, shape="record") - - def close_graph(self): - """print the dot graph into """ - self.printer.generate(self.file_name) - - -class VCGWriter(DiagramWriter): - """write vcg graphs from a diagram definition and a project - """ - def __init__(self, config): - styles = [dict(arrowstyle='solid', backarrowstyle='none', - backarrowsize=0), - dict(arrowstyle='solid', backarrowstyle='none', - backarrowsize=10), - dict(arrowstyle='solid', backarrowstyle='none', - linestyle='dotted', backarrowsize=10), - dict(arrowstyle='solid', backarrowstyle='none', - textcolor='green') ] - DiagramWriter.__init__(self, config, styles) - - def set_printer(self, file_name, basename): - """initialize VCGWriter for a UML graph""" - self.graph_file = open(file_name, 'w+') - self.printer = VCGPrinter(self.graph_file) - self.printer.open_graph(title=basename, layoutalgorithm='dfs', - late_edge_labels='yes', port_sharing='no', - manhattan_edges='yes') - self.printer.emit_node = self.printer.node - self.printer.emit_edge = self.printer.edge - - def get_title(self, obj): - """get project title in vcg format""" - return r'\fb%s\fn' % obj.title - - def get_values(self, obj): - """get label and shape for classes. - - The label contains all attributes and methods - """ - if is_exception(obj.node): - label = r'\fb\f09%s\fn' % obj.title - else: - label = r'\fb%s\fn' % obj.title - if obj.shape == 'interface': - shape = 'ellipse' - else: - shape = 'box' - if not self.config.only_classnames: - attrs = obj.attrs - methods = [func.name for func in obj.methods] - # box width for UML like diagram - maxlen = max(len(name) for name in [obj.title] + methods + attrs) - line = "_" * (maxlen + 2) - label = r'%s\n\f%s' % (label, line) - for attr in attrs: - label = r'%s\n\f08%s' % (label, attr) - if attrs: - label = r'%s\n\f%s' % (label, line) - for func in methods: - label = r'%s\n\f10%s()' % (label, func) - return dict(label=label, shape=shape) - - def close_graph(self): - """close graph and file""" - self.printer.close_graph() - self.graph_file.close() - diff --git a/pylibs/pylint/reporters/__init__.py b/pylibs/pylint/reporters/__init__.py index 0582a6f0..8f54820f 100644 --- a/pylibs/pylint/reporters/__init__.py +++ b/pylibs/pylint/reporters/__init__.py @@ -1,3 +1,5 @@ +# Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). # 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 @@ -10,11 +12,7 @@ # 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., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -"""utilities methods and classes for reporters - -Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE). -http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" +"""utilities methods and classes for reporters""" import sys, locale @@ -77,3 +75,14 @@ def _display(self, layout): """display the layout""" raise NotImplementedError() + # Event callbacks + + def on_set_current_module(self, module, filepath): + """starting analyzis of a module""" + pass + + def on_close(self, stats, previous_stats): + """global end of analyzis""" + pass + + diff --git a/pylibs/pylint/reporters/guireporter.py b/pylibs/pylint/reporters/guireporter.py index 13914ba8..4e98fefe 100644 --- a/pylibs/pylint/reporters/guireporter.py +++ b/pylibs/pylint/reporters/guireporter.py @@ -21,7 +21,7 @@ def __init__(self, gui, output=sys.stdout): def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" - module, obj, line = location[1:] + module, obj, line, col_offset = location[1:] if self.include_ids: sigle = msg_id else: diff --git a/pylibs/pylint/reporters/text.py b/pylibs/pylint/reporters/text.py index 269a519f..dd4d536b 100644 --- a/pylibs/pylint/reporters/text.py +++ b/pylibs/pylint/reporters/text.py @@ -1,5 +1,5 @@ # Copyright (c) 2003-2007 Sylvain Thenault (thenault@gmail.com). -# Copyright (c) 2003-2011 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). # 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 diff --git a/pylibs/pylint/utils.py b/pylibs/pylint/utils.py index 9c8e8f4e..b09b3e91 100644 --- a/pylibs/pylint/utils.py +++ b/pylibs/pylint/utils.py @@ -1,5 +1,5 @@ # Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). -# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -394,9 +394,6 @@ def report_is_enabled(self, reportid): def make_reports(self, stats, old_stats): """render registered reports""" - if self.config.files_output: - filename = 'pylint_global.' + self.reporter.extension - self.reporter.set_output(open(filename, 'w')) sect = Section('Report', '%s statements analysed.'% (self.stats['statement'])) for checker in self._reports: @@ -410,7 +407,7 @@ def make_reports(self, stats, old_stats): continue report_sect.report_id = reportid sect.append(report_sect) - self.reporter.display_results(sect) + return sect def add_stats(self, **kwargs): """add some stats entries to the statistic dictionary diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index 633a5b8e..de2f853f 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -56,7 +56,7 @@ def run_checkers(task=None, checkers=None, ignore=None, buffer=None, select=None break except Exception, e: - print e + assert True if task: task.done += part @@ -125,11 +125,13 @@ def pyflakes(filename): def _init_pylint(): - from pylint import lint, checkers + from pylint import lint, checkers, reporters import re - class VimReporter(object): + class VimReporter(reporters.BaseReporter): + def __init__(self): + reporters.BaseReporter.__init__(self) self.errors = [] def add_message(self, msg_id, location, msg): From 5cb4de1ba11bc8597e6c5ce14b5125cf852ce0d8 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sat, 28 Jul 2012 04:14:13 +0400 Subject: [PATCH 102/513] Fix queue --- autoload/pymode/queue.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/pymode/queue.vim b/autoload/pymode/queue.vim index 15301db3..1c796567 100644 --- a/autoload/pymode/queue.vim +++ b/autoload/pymode/queue.vim @@ -1,4 +1,4 @@ fun! pymode#queue#Poll() "{{{ py queue.check_task() - call feedkeys("\\", 't') + call feedkeys("\\", 't') endfunction "}}} From 412b5c5a8a64c0171bb082e844694841c7237507 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 31 Jul 2012 16:25:00 +0400 Subject: [PATCH 103/513] Added autopep8 (PyLintAuto), some bugs fix in async linter --- Changelog.rst | 5 +- README.rst | 6 + autoload/pymode.vim | 13 +- autoload/pymode/lint.vim | 17 +- doc/pymode.txt | 9 +- ftplugin/python/pymode.vim | 1 + plugin/pymode.vim | 2 +- pylibs/autopep8.py | 1583 ++++++++++++++++++++++++++++++++++++ pylibs/pymode/auto.py | 16 + pylibs/pymode/interface.py | 6 +- pylibs/pymode/queue.py | 1 + 11 files changed, 1649 insertions(+), 10 deletions(-) create mode 100644 pylibs/autopep8.py create mode 100644 pylibs/pymode/auto.py diff --git a/Changelog.rst b/Changelog.rst index ccbf72ba..ba453eed 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,14 +1,15 @@ Changelog ========= -## 2012-XX-XX 0.6.5 +## 2012-08-XX 0.6.5 ------------------- * Updated Pep8 to version 1.3.3 * Updated Pylint to version 0.25.2 * Fixed virtualenv support for windows users * Added pymode modeline ':help PythonModeModeline' * Added diagnostic tool ':call pymode#troubleshooting#Test()' -* Async code checking +* Added `PyLintAuto` command ':help PyLintAuto' +* Now Code checking is async operation * Improve speed of pymode folding ## 2012-05-24 0.6.4 diff --git a/README.rst b/README.rst index 9bf03296..03dc2d4e 100644 --- a/README.rst +++ b/README.rst @@ -395,6 +395,8 @@ PyLintCheckerToggle Toggle code checker (pylint, pyflakes) -------------------- ------------- PyLint Check current buffer -------------------- ------------- +PyLintAuto Automatic fix PEP8 errors +-------------------- ------------- Pyrun Run current buffer in python ==================== ============= @@ -466,6 +468,10 @@ Copyright (C) 2012 Kirill Klenov (klen_) Copyright (C) 2006 Johann C. Rocholl http://github.com/jcrocholl/pep8 + **autopep8**: + Copyright (c) 2012 hhatto + https://github.com/hhatto/autopep8 + **Python syntax for vim** Copyright (c) 2010 Dmitry Vasiliev http://www.hlabs.spb.ru/vim/python.vim diff --git a/autoload/pymode.vim b/autoload/pymode.vim index 53db997d..37a9182a 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -13,11 +13,15 @@ endfunction "}}} fun! pymode#Option(name) "{{{ - if exists('b:pymode_'.a:name) - return {'b:pymode_'.a:name} - else - return {'g:pymode_'.a:name} + + let name = 'b:pymode_' . a:name + if exists(name) + return eval(name) endif + + let name = 'g:pymode_' . a:name + return eval(name) + endfunction "}}} @@ -166,6 +170,7 @@ fun! pymode#Modeline() "{{{ let {'b:pymode_'.name} = value endfor endif + au BufRead call pymode#Modeline() endfunction "}}} diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 581a2f50..0ea56fcf 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -10,7 +10,7 @@ fun! pymode#lint#Check() "{{{ echohl Error | echo "File modified and I can't save it. Cancel code checking." | echohl None return 0 endtry - endif + endif let g:pymode_lint_buffer = bufnr('%') @@ -88,3 +88,18 @@ fun! pymode#lint#show_errormessage() "{{{ endif endfor endfunction " }}} + + +fun! pymode#lint#Auto() "{{{ + if &modifiable && &modified + try + write + catch /E212/ + echohl Error | echo "File modified and I can't save it. Cancel operation." | echohl None + return 0 + endtry + endif + py auto.fix_current_file() + cclose + edit +endfunction "}}} diff --git a/doc/pymode.txt b/doc/pymode.txt index 5fb42a93..b2be35c8 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -447,6 +447,9 @@ iM Operation with inner function or method. *:PyLint* *PyLint* Check current buffer + +*:PyLintAuto* *PyLintAuto* + Automatic fix PEP8 errors *:Pyrun* *Pyrun* Run current buffer @@ -509,9 +512,13 @@ You may set |exrc| and |secure| in your |vimrc| for auto set custom settings fro http://www.divmod.com/ PEP8: - Copyright (C) 2006 Johann C. Rocholl + Copyright (c) 2006 Johann C. Rocholl http://github.com/jcrocholl/pep8 + autopep8: + Copyright (c) 2012 hhatto + https://github.com/hhatto/autopep8 + Python syntax for vim: Copyright (c) 2010 Dmitry Vasiliev http://www.hlabs.spb.ru/vim/python.vim diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 933a7986..29726560 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -68,6 +68,7 @@ if pymode#Option('lint') command! -buffer -nargs=0 PyLintWindowToggle :call pymode#lint#ToggleWindow() command! -buffer -nargs=0 PyLintCheckerToggle :call pymode#lint#ToggleChecker() command! -buffer -nargs=0 PyLint :call pymode#lint#Check() + command! -buffer -nargs=0 PyLintAuto :call pymode#lint#Auto() " DESC: Set autocommands if pymode#Option('lint_write') diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 051c9aa5..da16e62e 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -116,7 +116,7 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint let g:pymode_lint_config = expand(":p:h:h") . "/pylint.ini" endif - py from pymode import lint, queue + py from pymode import lint, queue, auto au VimLeavePre * py queue.stop_queue() diff --git a/pylibs/autopep8.py b/pylibs/autopep8.py new file mode 100644 index 00000000..2b50197c --- /dev/null +++ b/pylibs/autopep8.py @@ -0,0 +1,1583 @@ +#!/usr/bin/env python +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" +A tool that automatically formats Python code to conform to the PEP 8 style +guide. +""" +import copy +import os +import sys +import inspect +try: + from cStringIO import StringIO +except ImportError: + try: + from StringIO import StringIO + except ImportError: + from io import StringIO +import token +import tokenize +from optparse import OptionParser +from subprocess import Popen, PIPE +from difflib import unified_diff +import tempfile + +from distutils.version import StrictVersion +try: + import pep8 + if StrictVersion(pep8.__version__) < StrictVersion('1.3a2'): + pep8 = None +except ImportError: + pep8 = None + + +__version__ = '0.7.3' + + +PEP8_BIN = 'pep8' +PEP8_PASSES_MAX = 100 +CR = '\r' +LF = '\n' +CRLF = '\r\n' +MAX_LINE_WIDTH = 79 + + +def open_with_encoding(filename, encoding, mode='r'): + """Return opened file with a specific encoding.""" + try: + # Python 3 + return open(filename, mode=mode, encoding=encoding) + except TypeError: + return open(filename, mode=mode) + + +def detect_encoding(filename): + """Return file encoding.""" + try: + # Python 3 + try: + with open(filename, 'rb') as input_file: + encoding = tokenize.detect_encoding(input_file.readline)[0] + + # Check for correctness of encoding + import io + with io.TextIOWrapper(input_file, encoding) as wrapper: + wrapper.read() + + return encoding + except (SyntaxError, LookupError, UnicodeDecodeError): + return 'latin-1' + except AttributeError: + return 'utf-8' + + +def read_from_filename(filename, readlines=False): + """Return contents of file.""" + with open_with_encoding(filename, + encoding=detect_encoding(filename)) as input_file: + return input_file.readlines() if readlines else input_file.read() + + +class FixPEP8(object): + + """Fix invalid code. + + Fixer methods are prefixed "fix_". The _fix_source() method looks for these + automatically. + + The fixer method can take either one or two arguments (in addition to + self). The first argument is "result", which is the error information from + pep8. The second argument, "logical", is required only for logical-line + fixes. + + The fixer method can return the list of modified lines or None. An empty + list would mean that no changes were made. None would mean that only the + line reported in the pep8 error was modified. Note that the modified line + numbers that are returned are indexed at 1. This typically would correspond + with the line number reported in the pep8 error information. + + [fixed method list] + - e111 + - e121,e122,e123,e124,e125,e126,e127,e128 + - e201,e202,e203 + - e211 + - e221,e222,e223,e224,e225 + - e231 + - e251 + - e261,e262 + - e271,e272,e273,e274 + - e301,e302,e303 + - e401 + - e502 + - e701,e702 + - e711 + - e721 + - w291,w293 + - w391 + - w602,w603,w604 + + """ + + def __init__(self, filename, options, contents=None): + self.filename = filename + if contents is None: + self.source = read_from_filename(filename, readlines=True) + else: + sio = StringIO(contents) + self.source = sio.readlines() + self.original_source = copy.copy(self.source) + self.newline = find_newline(self.source) + self.options = options + self.indent_word = _get_indentword("".join(self.source)) + self.logical_start = None + self.logical_end = None + # method definition + self.fix_e111 = self.fix_e101 + self.fix_e128 = self.fix_e127 + self.fix_e202 = self.fix_e201 + self.fix_e203 = self.fix_e201 + self.fix_e211 = self.fix_e201 + self.fix_e221 = self.fix_e271 + self.fix_e222 = self.fix_e271 + self.fix_e223 = self.fix_e271 + self.fix_e241 = self.fix_e271 + self.fix_e242 = self.fix_e224 + self.fix_e261 = self.fix_e262 + self.fix_e272 = self.fix_e271 + self.fix_e273 = self.fix_e271 + self.fix_e274 = self.fix_e271 + self.fix_w191 = self.fix_e101 + + def _fix_source(self, results): + completed_lines = set() + for result in sorted(results, key=_priority_key): + if result['line'] in completed_lines: + continue + + fixed_methodname = "fix_%s" % result['id'].lower() + if hasattr(self, fixed_methodname): + fix = getattr(self, fixed_methodname) + + is_logical_fix = len(inspect.getargspec(fix).args) > 2 + if is_logical_fix: + # Do not run logical fix if any lines have been modified. + if completed_lines: + continue + + logical = self._get_logical(result) + if not logical: + continue + + modified_lines = fix(result, logical) + else: + modified_lines = fix(result) + + if modified_lines: + completed_lines.update(modified_lines) + elif modified_lines == []: # Empty list means no fix + if self.options.verbose: + sys.stderr.write('Not fixing {f} on line {l}\n'.format( + f=result['id'], l=result['line'])) + else: # We assume one-line fix when None + completed_lines.add(result['line']) + else: + if self.options.verbose: + sys.stderr.write("'%s' is not defined.\n" % + fixed_methodname) + info = result['info'].strip() + sys.stderr.write("%s:%s:%s:%s\n" % (self.filename, + result['line'], + result['column'], + info)) + + def fix(self): + """Return a version of the source code with PEP 8 violations fixed.""" + if pep8: + pep8_options = { + 'ignore': + self.options.ignore and self.options.ignore.split(','), + 'select': + self.options.select and self.options.select.split(','), + } + results = _execute_pep8(pep8_options, self.source) + else: + if self.options.verbose: + sys.stderr.write('Running in compatibility mode. Consider ' + 'upgrading to the latest pep8.\n') + results = _spawn_pep8((["--ignore=" + self.options.ignore] + if self.options.ignore else []) + + (["--select=" + self.options.select] + if self.options.select else []) + + [self.filename]) + self._fix_source(results) + return "".join(self.source) + + def fix_e101(self, _): + """Reindent all lines.""" + reindenter = Reindenter(self.source) + if reindenter.run(): + original_length = len(self.source) + self.source = reindenter.fixed_lines() + return range(1, 1 + original_length) + else: + return [] + + def find_logical(self, force=False): + # make a variable which is the index of all the starts of lines + if not force and self.logical_start is not None: + return + logical_start = [] + logical_end = [] + last_newline = True + sio = StringIO("".join(self.source)) + parens = 0 + for t in tokenize.generate_tokens(sio.readline): + if t[0] in [tokenize.COMMENT, tokenize.DEDENT, + tokenize.INDENT, tokenize.NL, + tokenize.ENDMARKER]: + continue + if not parens and t[0] in [ + tokenize.NEWLINE, tokenize.SEMI + ]: + last_newline = True + logical_end.append((t[3][0] - 1, t[2][1])) + continue + if last_newline and not parens: + logical_start.append((t[2][0] - 1, t[2][1])) + last_newline = False + if t[0] == tokenize.OP: + if t[1] in '([{': + parens += 1 + elif t[1] in '}])': + parens -= 1 + self.logical_start = logical_start + self.logical_end = logical_end + + def _get_logical(self, result): + """Return the logical line corresponding to the result. + + Assumes input is already E702-clean. + + """ + try: + self.find_logical() + except (IndentationError, tokenize.TokenError): + return None + + row = result['line'] - 1 + col = result['column'] - 1 + ls = None + le = None + for i in range(0, len(self.logical_start), 1): + x = self.logical_end[i] + if x[0] > row or (x[0] == row and x[1] > col): + le = x + ls = self.logical_start[i] + break + if ls is None: + return None + original = self.source[ls[0]:le[0] + 1] + return ls, le, original + + def _fix_reindent(self, result, logical, fix_distinct=False): + """Fix a badly indented line. + + This is done by adding or removing from its initial indent only. + + """ + if not logical: + return [] + ls, _, original = logical + try: + rewrapper = Wrapper(original, hard_wrap=MAX_LINE_WIDTH) + except (tokenize.TokenError, IndentationError): + return [] + valid_indents = rewrapper.pep8_expected() + if not rewrapper.rel_indent: + return [] + if result["line"] > ls[0]: + # got a valid continuation line number from pep8 + row = result["line"] - ls[0] - 1 + # always pick the first option for this + valid = valid_indents[row] + got = rewrapper.rel_indent[row] + else: + # Line number from pep8 isn't a continuation line. Instead, + # compare our own function's result, look for the first mismatch, + # and just hope that we take fewer than 100 iterations to finish. + for row in range(0, len(original), 1): + valid = valid_indents[row] + got = rewrapper.rel_indent[row] + if valid != got: + break + line = ls[0] + row + # always pick the expected indent, for now. + indent_to = valid[0] + if fix_distinct and indent_to == 4: + if len(valid) == 1: + return [] + else: + indent_to = valid[1] + + if got != indent_to: + orig_line = self.source[line] + new_line = ' ' * (indent_to) + orig_line.lstrip() + if new_line == orig_line: + return [] + else: + self.source[line] = new_line + return [line + 1] # Line indexed at 1 + else: + return [] + + def fix_e121(self, result, logical): + """The 'peculiar indent' error for hanging indents.""" + # fix by adjusting initial indent level + return self._fix_reindent(result, logical) + + def fix_e122(self, result, logical): + """The 'absent indent' error for hanging indents.""" + # fix by adding an initial indent + return self._fix_reindent(result, logical) + + def fix_e123(self, result, logical): + """The 'loose fingernails' indentation level error for hanging + indents.""" + # fix by deleting whitespace to the correct level + modified_lines = self._fix_reindent(result, logical) + if modified_lines: + return modified_lines + else: + # Fallback + if not logical: + return [] + logical_lines = logical[2] + line_index = result['line'] - 1 + original_line = self.source[line_index] + + fixed_line = (_get_indentation(logical_lines[0]) + + original_line.lstrip()) + if fixed_line == original_line: + return [] + else: + self.source[line_index] = fixed_line + + def fix_e124(self, result, logical): + """The 'loose fingernails' indentation level error for visual + indents.""" + # fix by inserting whitespace before the closing bracket + return self._fix_reindent(result, logical) + + def fix_e125(self, result, logical): + """The 'often not visually distinct' error.""" + # fix by indenting the line in error to the next stop. + modified_lines = self._fix_reindent(result, logical, fix_distinct=True) + if modified_lines: + return modified_lines + else: + # Fallback + line_index = result['line'] - 1 + original_line = self.source[line_index] + self.source[line_index] = self.indent_word + original_line + + def fix_e126(self, result, logical): + """The 'spectacular indent' error for hanging indents.""" + # fix by deleting whitespace to the left + modified_lines = self._fix_reindent(result, logical) + if modified_lines: + return modified_lines + else: + # Fallback + if not logical: + return [] + logical_lines = logical[2] + line_index = result['line'] - 1 + original = self.source[line_index] + + fixed = (_get_indentation(logical_lines[0]) + + self.indent_word + original.lstrip()) + if fixed == original: + return [] + else: + self.source[line_index] = fixed + + def fix_e127(self, result, logical): + """The 'interpretive dance' indentation error.""" + # Fix by inserting/deleting whitespace to the correct level. + modified_lines = self._fix_reindent(result, logical) + if modified_lines: + return modified_lines + else: + # Fallback + return self._align_visual_indent(result, logical) + + def _align_visual_indent(self, result, logical): + """Correct visual indent. + + This includes over (E127) and under (E128) indented lines. + + """ + if not logical: + return [] + logical_lines = logical[2] + line_index = result['line'] - 1 + original = self.source[line_index] + fixed = original + + if '(' in logical_lines[0]: + fixed = logical_lines[0].find('(') * ' ' + original.lstrip() + elif logical_lines[0].rstrip().endswith('\\'): + fixed = (_get_indentation(logical_lines[0]) + + self.indent_word + original.lstrip()) + else: + return [] + + if fixed == original: + return [] + else: + self.source[line_index] = fixed + + def fix_e201(self, result): + line_index = result['line'] - 1 + target = self.source[line_index] + offset = result['column'] - 1 + + # When multiline strings are involved, pep8 reports the error as + # being at the start of the multiline string, which doesn't work + # for us. + if '"""' in target or "'''" in target: + return [] + + fixed = fix_whitespace(target, + offset=offset, + replacement='') + + if fixed == target: + return [] + else: + self.source[line_index] = fixed + + def fix_e224(self, result): + target = self.source[result['line'] - 1] + offset = result['column'] - 1 + fixed = target[:offset] + target[offset:].replace('\t', ' ') + self.source[result['line'] - 1] = fixed + + def fix_e225(self, result): + """Fix whitespace around operator.""" + target = self.source[result['line'] - 1] + offset = result['column'] - 1 + fixed = target[:offset] + ' ' + target[offset:] + + # Only proceed if non-whitespace characters match. + # And make sure we don't break the indentation. + if (fixed.replace(' ', '') == target.replace(' ', '') and + _get_indentation(fixed) == _get_indentation(target)): + self.source[result['line'] - 1] = fixed + else: + return [] + + def fix_e231(self, result): + """Add missing whitespace.""" + line_index = result['line'] - 1 + target = self.source[line_index] + offset = result['column'] + fixed = target[:offset] + ' ' + target[offset:] + self.source[line_index] = fixed + + def fix_e251(self, result): + line_index = result['line'] - 1 + target = self.source[line_index] + + # This is necessary since pep8 sometimes reports columns that goes + # past the end of the physical line. This happens in cases like, + # foo(bar\n=None) + c = min(result['column'] - 1, + len(target) - 1) + + if target[c].strip(): + fixed = target + else: + fixed = target[:c].rstrip() + target[c:].lstrip() + + # There could be an escaped newline + # + # def foo(a=\ + # 1) + if (fixed.endswith('=\\\n') or + fixed.endswith('=\\\r\n') or + fixed.endswith('=\\\r')): + self.source[line_index] = fixed.rstrip('\n\r \t\\') + self.source[line_index + 1] = \ + self.source[line_index + 1].lstrip() + return [line_index + 1, line_index + 2] # Line indexed at 1 + + self.source[result['line'] - 1] = fixed + + def fix_e262(self, result): + """Fix spacing after comment hash.""" + target = self.source[result['line'] - 1] + offset = result['column'] + + code = target[:offset].rstrip(' \t#') + comment = target[offset:].lstrip(' \t#') + + fixed = code + (' # ' + comment if comment.strip() + else self.newline) + + self.source[result['line'] - 1] = fixed + + def fix_e271(self, result): + """Fix extraneous whitespace around keywords.""" + line_index = result['line'] - 1 + target = self.source[line_index] + offset = result['column'] - 1 + + fixed = fix_whitespace(target, + offset=offset, + replacement=' ') + + if fixed == target: + return [] + else: + self.source[line_index] = fixed + + def fix_e301(self, result): + cr = self.newline + self.source[result['line'] - 1] = cr + self.source[result['line'] - 1] + + def fix_e302(self, result): + add_linenum = 2 - int(result['info'].split()[-1]) + cr = self.newline * add_linenum + self.source[result['line'] - 1] = cr + self.source[result['line'] - 1] + + def fix_e304(self, result): + line = result['line'] - 2 + if not self.source[line].strip(): + self.source[line] = '' + + def fix_e303(self, result): + delete_linenum = int(result['info'].split("(")[1].split(")")[0]) - 2 + delete_linenum = max(1, delete_linenum) + + # We need to count because pep8 reports an offset line number if there + # are comments. + cnt = 0 + line = result['line'] - 2 + modified_lines = [] + while cnt < delete_linenum: + if line < 0: + break + if not self.source[line].strip(): + self.source[line] = '' + modified_lines.append(1 + line) # Line indexed at 1 + cnt += 1 + line -= 1 + + return modified_lines + + def fix_e401(self, result): + line_index = result['line'] - 1 + target = self.source[line_index] + offset = result['column'] - 1 + + if not target.lstrip().startswith('import'): + return [] + + # pep8 (1.3.1) reports false positive if there is an import statement + # followed by a semicolon and some unrelated statement with commas in + # it. + if ';' in target: + return [] + + indentation = target.split("import ")[0] + fixed = (target[:offset].rstrip('\t ,') + self.newline + + indentation + 'import ' + target[offset:].lstrip('\t ,')) + self.source[line_index] = fixed + + def fix_e501(self, result): + line_index = result['line'] - 1 + target = self.source[line_index] + + indent = _get_indentation(target) + source = target[len(indent):] + sio = StringIO(target) + + # Check for multiline string. + try: + tokens = list(tokenize.generate_tokens(sio.readline)) + except (tokenize.TokenError, IndentationError): + multi_line_candidate = break_multi_line( + target, newline=self.newline, indent_word=self.indent_word) + + if multi_line_candidate: + self.source[line_index] = multi_line_candidate + return + else: + return [] + + # Prefer + # my_long_function_name( + # x, y, z, ...) + # + # over + # my_long_function_name(x, y, + # z, ...) + candidate0 = _shorten_line(tokens, source, target, indent, + self.indent_word, newline=self.newline, + reverse=False) + candidate1 = _shorten_line(tokens, source, target, indent, + self.indent_word, newline=self.newline, + reverse=True) + if candidate0 and candidate1: + if candidate0.split(self.newline)[0].endswith('('): + self.source[line_index] = candidate0 + else: + self.source[line_index] = candidate1 + elif candidate0: + self.source[line_index] = candidate0 + elif candidate1: + self.source[line_index] = candidate1 + else: + # Otherwise both don't work + return [] + + def fix_e502(self, result): + """Remove extraneous escape of newline.""" + line_index = result['line'] - 1 + target = self.source[line_index] + self.source[line_index] = target.rstrip('\n\r \t\\') + self.newline + + def fix_e701(self, result): + line_index = result['line'] - 1 + target = self.source[line_index] + c = result['column'] + + fixed_source = (target[:c] + self.newline + + _get_indentation(target) + self.indent_word + + target[c:].lstrip('\n\r \t\\')) + self.source[result['line'] - 1] = fixed_source + + def fix_e702(self, result, logical): + """Fix multiple statements on one line.""" + logical_lines = logical[2] + + line_index = result['line'] - 1 + target = self.source[line_index] + + if target.rstrip().endswith('\\'): + # Normalize '1; \\\n2' into '1; 2'. + self.source[line_index] = target.rstrip('\n \r\t\\') + self.source[line_index + 1] = self.source[line_index + 1].lstrip() + return [line_index + 1, line_index + 2] + + if target.rstrip().endswith(';'): + self.source[line_index] = target.rstrip('\n \r\t;') + self.newline + return + + offset = result['column'] - 1 + first = target[:offset].rstrip(';') + second = (_get_indentation(logical_lines[0]) + + target[offset:].lstrip(';').lstrip()) + + self.source[line_index] = first + self.newline + second + + def fix_e711(self, result): + """Fix comparison.""" + line_index = result['line'] - 1 + target = self.source[line_index] + offset = result['column'] - 1 + + right_offset = offset + 2 + if right_offset >= len(target): + return [] + + left = target[:offset].rstrip() + center = target[offset:right_offset] + right = target[right_offset:].lstrip() + + if not right.startswith('None'): + return [] + + if center.strip() == '==': + new_center = 'is' + elif center.strip() == '!=': + new_center = 'is not' + else: + return [] + + self.source[line_index] = ' '.join([left, new_center, right]) + + def fix_e721(self, _): + return self.refactor('idioms') + + def fix_w291(self, result): + fixed_line = self.source[result['line'] - 1].rstrip() + self.source[result['line'] - 1] = "%s%s" % (fixed_line, self.newline) + + def fix_w293(self, result): + assert not self.source[result['line'] - 1].strip() + self.source[result['line'] - 1] = self.newline + + def fix_w391(self, _): + source = copy.copy(self.source) + source.reverse() + blank_count = 0 + for line in source: + line = line.rstrip() + if line: + break + else: + blank_count += 1 + source = source[blank_count:] + source.reverse() + + original_length = len(self.source) + self.source = source + return range(1, 1 + original_length) + + def refactor(self, fixer_name, ignore=None): + """Return refactored code using lib2to3. + + Skip if ignore string is produced in the refactored code. + + """ + from lib2to3 import pgen2 + try: + new_text = refactor_with_2to3(''.join(self.source), + fixer_name=fixer_name) + except pgen2.parse.ParseError: + return [] + + if ''.join(self.source).strip() == new_text.strip(): + return [] + else: + if ignore: + if ignore in new_text and ignore not in ''.join(self.source): + return [] + original_length = len(self.source) + self.source = [new_text] + return range(1, 1 + original_length) + + def fix_w601(self, _): + return self.refactor('has_key') + + def fix_w602(self, _): + """Fix deprecated form of raising exception.""" + return self.refactor('raise', + ignore='with_traceback') + + def fix_w603(self, _): + return self.refactor('ne') + + def fix_w604(self, _): + return self.refactor('repr') + + +def find_newline(source): + """Return type of newline used in source.""" + cr, lf, crlf = 0, 0, 0 + for s in source: + if CRLF in s: + crlf += 1 + elif CR in s: + cr += 1 + elif LF in s: + lf += 1 + _max = max(cr, crlf, lf) + if _max == lf: + return LF + elif _max == crlf: + return CRLF + elif _max == cr: + return CR + else: + return LF + + +def _get_indentword(source): + """Return indentation type.""" + sio = StringIO(source) + indent_word = " " # Default in case source has no indentation + try: + for t in tokenize.generate_tokens(sio.readline): + if t[0] == token.INDENT: + indent_word = t[1] + break + except (tokenize.TokenError, IndentationError): + pass + return indent_word + + +def _get_indentation(line): + """Return leading whitespace.""" + non_whitespace_index = len(line) - len(line.lstrip()) + return line[:non_whitespace_index] + + +def _split_indentation(line): + """Return line split into tuple (indentation, rest).""" + non_whitespace_index = len(line) - len(line.lstrip()) + return (line[:non_whitespace_index], line[non_whitespace_index:]) + + +def _analyze_pep8result(result): + tmp = result.split(":") + filename = tmp[0] + line = int(tmp[1]) + column = int(tmp[2]) + info = " ".join(result.split()[1:]) + pep8id = info.lstrip().split()[0] + return dict(id=pep8id, filename=filename, line=line, + column=column, info=info) + + +def _get_difftext(old, new, filename): + diff = unified_diff(old, new, 'original/' + filename, 'fixed/' + filename) + return "".join(diff) + + +def _priority_key(pep8_result): + """Key for sorting PEP8 results. + + Global fixes should be done first. This is important for things + like indentation. + + """ + priority = ['e101', 'e111', 'w191', # Global fixes + 'e701', # Fix multiline colon-based before semicolon based + 'e702', # Break multiline statements early + 'e225', 'e231', # things that make lines longer + 'e201', # Remove extraneous whitespace before breaking lines + 'e501', # before we break lines + ] + key = pep8_result['id'].lower() + if key in priority: + return priority.index(key) + else: + # Lowest priority + return len(priority) + + +def _shorten_line(tokens, source, target, indentation, indent_word, newline, + reverse=False): + """Separate line at OPERATOR.""" + max_line_width_minus_indentation = MAX_LINE_WIDTH - len(indentation) + if reverse: + tokens.reverse() + for tkn in tokens: + # Don't break on '=' after keyword as this violates PEP 8. + if token.OP == tkn[0] and tkn[1] != '=': + offset = tkn[2][1] + 1 + if reverse: + if offset > (max_line_width_minus_indentation - + len(indent_word)): + continue + else: + if (len(target.rstrip()) - offset > + (max_line_width_minus_indentation - + len(indent_word))): + continue + first = source[:offset - len(indentation)] + + second_indent = indentation + if first.rstrip().endswith('('): + second_indent += indent_word + elif '(' in first: + second_indent += ' ' * (1 + first.find('(')) + else: + second_indent += indent_word + + second = (second_indent + + source[offset - len(indentation):].lstrip()) + if not second.strip(): + continue + + # Don't modify if lines are not short enough + if len(first) > max_line_width_minus_indentation: + continue + if len(second) > MAX_LINE_WIDTH: # Already includes indentation + continue + # Do not begin a line with a comma + if second.lstrip().startswith(','): + continue + # Do end a line with a dot + if first.rstrip().endswith('.'): + continue + if tkn[1] in '+-*/': + fixed = first + ' \\' + newline + second + else: + fixed = first + newline + second + if check_syntax(fixed): + return indentation + fixed + return None + + +def fix_whitespace(line, offset, replacement): + """Replace whitespace at offset and return fixed line.""" + # Replace escaped newlines too + left = line[:offset].rstrip('\n\r \t\\') + right = line[offset:].lstrip('\n\r \t\\') + if right.startswith('#'): + return line + else: + return left + replacement + right + + +def _spawn_pep8(pep8_options): + """Execute pep8 via subprocess.Popen.""" + paths = os.environ['PATH'].split(':') + for path in paths: + if os.path.exists(os.path.join(path, PEP8_BIN)): + cmd = ([os.path.join(path, PEP8_BIN)] + + pep8_options) + p = Popen(cmd, stdout=PIPE) + output = p.communicate()[0].decode('utf-8') + return [_analyze_pep8result(l) + for l in output.splitlines()] + raise Exception("'%s' is not found." % PEP8_BIN) + + +def _execute_pep8(pep8_options, source): + """Execute pep8 via python method calls.""" + class QuietReport(pep8.BaseReport): + + """Version of checker that does not print.""" + + def __init__(self, options): + super(QuietReport, self).__init__(options) + self.__full_error_results = [] + + def error(self, line_number, offset, text, _): + """Collect errors.""" + code = super(QuietReport, self).error(line_number, offset, text, _) + if code: + self.__full_error_results.append( + dict(id=code, line=line_number, + column=offset + 1, info=text)) + + def full_error_results(self): + """Return error results in detail. + + Results are in the form of a list of dictionaries. Each dictionary + contains 'id', 'line', 'column', and 'info'. + + """ + return self.__full_error_results + + checker = pep8.Checker('', lines=source, + reporter=QuietReport, **pep8_options) + checker.check_all() + return checker.report.full_error_results() + + +class Reindenter(object): + + """Reindents badly-indented code to uniformly use four-space indentation. + + Released to the public domain, by Tim Peters, 03 October 2000. + + """ + + def __init__(self, input_text): + self.find_stmt = 1 # next token begins a fresh stmt? + self.level = 0 # current indent level + + # Raw file lines. + self.raw = input_text + self.after = None + + # File lines, rstripped & tab-expanded. Dummy at start is so + # that we can use tokenize's 1-based line numbering easily. + # Note that a line is all-blank iff it's "\n". + self.lines = [line.rstrip('\n \t').expandtabs() + "\n" + for line in self.raw] + self.lines.insert(0, None) + self.index = 1 # index into self.lines of next line + + # List of (lineno, indentlevel) pairs, one for each stmt and + # comment line. indentlevel is -1 for comment lines, as a + # signal that tokenize doesn't know what to do about them; + # indeed, they're our headache! + self.stats = [] + + def run(self): + tokens = tokenize.generate_tokens(self.getline) + try: + for t in tokens: + self.tokeneater(*t) + except (tokenize.TokenError, IndentationError): + return False + # Remove trailing empty lines. + lines = self.lines + while lines and lines[-1] == "\n": + lines.pop() + # Sentinel. + stats = self.stats + stats.append((len(lines), 0)) + # Map count of leading spaces to # we want. + have2want = {} + # Program after transformation. + after = self.after = [] + # Copy over initial empty lines -- there's nothing to do until + # we see a line with *something* on it. + i = stats[0][0] + after.extend(lines[1:i]) + for i in range(len(stats) - 1): + thisstmt, thislevel = stats[i] + nextstmt = stats[i + 1][0] + have = _leading_space_count(lines[thisstmt]) + want = thislevel * 4 + if want < 0: + # A comment line. + if have: + # An indented comment line. If we saw the same + # indentation before, reuse what it most recently + # mapped to. + want = have2want.get(have, - 1) + if want < 0: + # Then it probably belongs to the next real stmt. + for j in range(i + 1, len(stats) - 1): + jline, jlevel = stats[j] + if jlevel >= 0: + if have == _leading_space_count(lines[jline]): + want = jlevel * 4 + break + if want < 0: # Maybe it's a hanging + # comment like this one, + # in which case we should shift it like its base + # line got shifted. + for j in range(i - 1, -1, -1): + jline, jlevel = stats[j] + if jlevel >= 0: + want = (have + _leading_space_count( + after[jline - 1]) - + _leading_space_count(lines[jline])) + break + if want < 0: + # Still no luck -- leave it alone. + want = have + else: + want = 0 + assert want >= 0 + have2want[have] = want + diff = want - have + if diff == 0 or have == 0: + after.extend(lines[thisstmt:nextstmt]) + else: + for line in lines[thisstmt:nextstmt]: + if diff > 0: + if line == "\n": + after.append(line) + else: + after.append(" " * diff + line) + else: + remove = min(_leading_space_count(line), -diff) + after.append(line[remove:]) + return self.raw != self.after + + def fixed_lines(self): + return self.after + + def getline(self): + """Line-getter for tokenize.""" + if self.index >= len(self.lines): + line = "" + else: + line = self.lines[self.index] + self.index += 1 + return line + + def tokeneater(self, token_type, _, start, __, line): + """Line-eater for tokenize.""" + sline = start[0] + if token_type == tokenize.NEWLINE: + # A program statement, or ENDMARKER, will eventually follow, + # after some (possibly empty) run of tokens of the form + # (NL | COMMENT)* (INDENT | DEDENT+)? + self.find_stmt = 1 + + elif token_type == tokenize.INDENT: + self.find_stmt = 1 + self.level += 1 + + elif token_type == tokenize.DEDENT: + self.find_stmt = 1 + self.level -= 1 + + elif token_type == tokenize.COMMENT: + if self.find_stmt: + self.stats.append((sline, -1)) + # but we're still looking for a new stmt, so leave + # find_stmt alone + + elif token_type == tokenize.NL: + pass + + elif self.find_stmt: + # This is the first "real token" following a NEWLINE, so it + # must be the first token of the next program statement, or an + # ENDMARKER. + self.find_stmt = 0 + if line: # not endmarker + self.stats.append((sline, self.level)) + + +class Wrapper(object): + + """Class for functions relating to continuation lines and line folding. + + Each instance operates on a single logical line. + + """ + + SKIP_TOKENS = frozenset([ + tokenize.COMMENT, tokenize.NL, tokenize.INDENT, + tokenize.DEDENT, tokenize.NEWLINE, tokenize.ENDMARKER + ]) + + def __init__(self, physical_lines, hard_wrap=79, soft_wrap=72): + if type(physical_lines) != list: + physical_lines = physical_lines.splitlines(keepends=True) + self.lines = physical_lines + self.index = 0 + self.hard_wrap = hard_wrap + self.soft_wrap = soft_wrap + self.tokens = list() + self.rel_indent = None + sio = StringIO("".join(physical_lines)) + for t in tokenize.generate_tokens(sio.readline): + if not len(self.tokens) and t[0] in self.SKIP_TOKENS: + continue + if t[0] != tokenize.ENDMARKER: + #if t[2][0] > max_seen: + #max_seen = t[2][0] + #print ">>" + repr(t[4]) + "<<" + self.tokens.append(t) + self.logical_line, self.mapping = self.build_tokens_logical( + self.tokens + ) + + def build_tokens_logical(self, tokens): + """Build a logical line from a list of tokens. + + Returns the logical line and a list of (offset, token) tuples. Does + not mute strings like the version in pep8.py. + + """ + # from pep8.py with minor modifications + mapping = [] + logical = [] + length = 0 + previous = None + for t in tokens: + token_type, text = t[0:2] + if token_type in self.SKIP_TOKENS: + continue + if previous: + end_line, end = previous[3] + start_line, start = t[2] + if end_line != start_line: # different row + prev_text = self.lines[end_line - 1][end - 1] + if prev_text == ',' or (prev_text not in '{[(' + and text not in '}])'): + logical.append(' ') + length += 1 + elif end != start: # different column + fill = self.lines[end_line - 1][end:start] + logical.append(fill) + length += len(fill) + mapping.append((length, t)) + logical.append(text) + length += len(text) + previous = t + logical_line = ''.join(logical) + assert logical_line.lstrip() == logical_line + assert logical_line.rstrip() == logical_line + return logical_line, mapping + + def pep8_expected(self): + """Replicates logic in pep8.py, to know what level to indent things to. + + Returns a list of lists; each list represents valid indent levels for + the line in question, relative from the initial indent. However, the + first entry is the indent level which was expected. + + """ + + # What follows is an adjusted version of + # pep8.py:continuation_line_indentation. All of the comments have been + # stripped and the 'yield' statements replaced with 'pass'. + tokens = self.tokens + if not tokens: + return + + first_row = tokens[0][2][0] + nrows = 1 + tokens[-1][2][0] - first_row + + # here are the return values + valid_indents = [list()] * nrows + indent_level = tokens[0][2][1] + valid_indents[0].append(indent_level) + + if nrows == 1: + # bug, really. + return valid_indents + + indent_next = self.logical_line.endswith(':') + + row = depth = 0 + parens = [0] * nrows + self.rel_indent = rel_indent = [0] * nrows + indent = [indent_level] + indent_chances = {} + last_indent = (0, 0) + last_token_multiline = None + + for token_type, text, start, end, _ in self.tokens: + newline = row < start[0] - first_row + if newline: + row = start[0] - first_row + newline = (not last_token_multiline and + token_type not in (tokenize.NL, tokenize.NEWLINE)) + + if newline: + # This is where the differences start. Instead of looking at + # the line and determining whether the observed indent matches + # our expectations, we decide which type of indentation is in + # use at the given indent level, and return the offset. This + # algorithm is susceptible to "carried errors", but should + # through repeated runs eventually solve indentation for + # multi-line expressions less than PEP8_PASSES_MAX lines long. + + if depth: + for open_row in range(row - 1, -1, -1): + if parens[open_row]: + break + else: + open_row = 0 + + # That's all we get to work with. This code attempts to + # "reverse" the below logic, and place into the valid indents + # list + vi = [] + add_second_chances = False + if token_type == tokenize.OP and text in ']})': + # this line starts with a closing bracket, so it needs to + # be closed at the same indent as the opening one. + if indent[depth]: + # hanging indent + vi.append(indent[depth]) + else: + # visual indent + vi.append(indent_level + rel_indent[open_row]) + elif depth and indent[depth]: + # visual indent was previously confirmed. + vi.append(indent[depth]) + add_second_chances = True + elif depth and True in indent_chances.values(): + # visual indent happened before, so stick to + # visual indent this time. + if depth > 1 and indent[depth - 1]: + vi.append(indent[depth - 1]) + else: + # stupid fallback + vi.append(indent_level + 4) + add_second_chances = True + elif not depth: + vi.append(indent_level + 4) + else: + # must be in hanging indent + hang = rel_indent[open_row] + 4 + vi.append(indent_level + hang) + + # about the best we can do without look-ahead + if indent_next and vi[0] == indent_level + 4 and \ + nrows == row + 1: + vi[0] += 4 + + if add_second_chances: + # visual indenters like to line things up. + min_indent = vi[0] + for col, what in indent_chances.items(): + if col > min_indent and ( + what is True or + (what == str and token_type == tokenize.STRING) or + (what == text and token_type == tokenize.OP) + ): + vi.append(col) + vi = sorted(vi) + + valid_indents[row] = vi + + # ...returning to original continuation_line_identation func... + visual_indent = indent_chances.get(start[1]) + last_indent = start + rel_indent[row] = start[1] - indent_level + hang = rel_indent[row] - rel_indent[open_row] + + if token_type == tokenize.OP and text in ']})': + if indent[depth]: + if start[1] != indent[depth]: + pass # E124 + elif hang: + pass # E123 + elif visual_indent is True: + if not indent[depth]: + indent[depth] = start[1] + elif visual_indent in (text, str): + pass + elif indent[depth] and start[1] < indent[depth]: + pass # E128 + elif hang == 4 or (indent_next and rel_indent[row] == 8): + pass + else: + if hang <= 0: + pass # E122 + elif indent[depth]: + pass # E127 + elif hang % 4: + pass # E121 + else: + pass # E126 + + # line altered: comments shouldn't define a visual indent + if parens[row] and not indent[depth] and token_type not in ( + tokenize.NL, tokenize.COMMENT + ): + indent[depth] = start[1] + indent_chances[start[1]] = True + elif token_type == tokenize.STRING or text in ( + 'u', 'ur', 'b', 'br' + ): + indent_chances[start[1]] = str + + if token_type == tokenize.OP: + if text in '([{': + depth += 1 + indent.append(0) + parens[row] += 1 + elif text in ')]}' and depth > 0: + prev_indent = indent.pop() or last_indent[1] + for d in range(depth): + if indent[d] > prev_indent: + indent[d] = 0 + for ind in list(indent_chances): + if ind >= prev_indent: + del indent_chances[ind] + depth -= 1 + if depth and indent[depth]: # modified + indent_chances[indent[depth]] = True + for idx in range(row, -1, -1): + if parens[idx]: + parens[idx] -= 1 + break + assert len(indent) == depth + 1 + if start[1] not in indent_chances: + indent_chances[start[1]] = text + + last_token_multiline = (start[0] != end[0]) + + if indent_next and rel_indent[-1] == 4: + pass # E125 + + return valid_indents + + +def _leading_space_count(line): + """Return number of leading spaces in line.""" + i = 0 + while i < len(line) and line[i] == ' ': + i += 1 + return i + + +def refactor_with_2to3(source_text, fixer_name): + """Use lib2to3 to refactor the source. + + Return the refactored source code. + + """ + from lib2to3 import refactor + fixers = ['lib2to3.fixes.fix_' + fixer_name] + tool = refactor.RefactoringTool( + fixer_names=fixers, + explicit=fixers) + return str(tool.refactor_string(source_text, name='')) + + +def break_multi_line(source_text, newline, indent_word): + """Break first line of multi-line code. + + Return None if a break is not possible. + + """ + # Handle special case only. + if ('(' in source_text and source_text.rstrip().endswith(',')): + index = 1 + source_text.find('(') + if index >= MAX_LINE_WIDTH: + return None + + # Make sure we are not in a string. + for quote in ['"', "'"]: + if quote in source_text: + if source_text.find(quote) < index: + return None + + # Make sure we are not in a comment. + if '#' in source_text: + if source_text.find('#') < index: + return None + + assert index < len(source_text) + return ( + source_text[:index].rstrip() + newline + + _get_indentation(source_text) + indent_word + + source_text[index:].lstrip()) + else: + return None + + +def check_syntax(code): + """Return True if syntax is okay.""" + try: + return compile(code, '', 'exec') + except (SyntaxError, TypeError, UnicodeDecodeError): + return False + + +def fix_file(filename, opts, output=sys.stdout): + tmp_source = read_from_filename(filename) + + # Add missing newline (important for diff) + tmp_newline = find_newline(tmp_source) + if tmp_source == tmp_source.rstrip(tmp_newline): + tmp_source += tmp_newline + + fix = FixPEP8(filename, opts, contents=tmp_source) + fixed_source = fix.fix() + original_source = copy.copy(fix.original_source) + tmp_filename = filename + if not pep8 or opts.in_place: + encoding = detect_encoding(filename) + for _ in range(opts.pep8_passes): + if fixed_source == tmp_source: + break + tmp_source = copy.copy(fixed_source) + if not pep8: + tmp_filename = tempfile.mkstemp()[1] + fp = open_with_encoding(tmp_filename, encoding=encoding, mode='w') + fp.write(fixed_source) + fp.close() + fix = FixPEP8(tmp_filename, opts, contents=tmp_source) + fixed_source = fix.fix() + if not pep8: + os.remove(tmp_filename) + del tmp_filename + del tmp_source + + if opts.diff: + new = StringIO(''.join(fix.source)) + new = new.readlines() + output.write(_get_difftext(original_source, new, filename)) + elif opts.in_place: + fp = open_with_encoding(filename, encoding=encoding, + mode='w') + fp.write(fixed_source) + fp.close() + else: + output.write(fixed_source) + + +def parse_args(args): + """Parse command-line options.""" + parser = OptionParser(usage='Usage: autopep8 [options] ' + '[filename [filename ...]]', + version="autopep8: %s" % __version__, + description=__doc__, + prog='autopep8') + parser.add_option('-v', '--verbose', action='store_true', dest='verbose', + help='print verbose messages') + parser.add_option('-d', '--diff', action='store_true', dest='diff', + help='print the diff for the fixed source') + parser.add_option('-i', '--in-place', action='store_true', + help='make changes to files in place') + parser.add_option('-r', '--recursive', action='store_true', + help='run recursively; must be used with --in-place or ' + '--diff') + parser.add_option('-p', '--pep8-passes', + default=PEP8_PASSES_MAX, type='int', + help='maximum number of additional pep8 passes' + ' (default: %default)') + parser.add_option('--ignore', default='', + help='do not fix these errors/warnings (e.g. E4,W)') + parser.add_option('--select', default='', + help='select errors/warnings (e.g. E4,W)') + opts, args = parser.parse_args(args) + + if not len(args): + parser.error('incorrect number of arguments') + + if len(args) > 1 and not (opts.in_place or opts.diff): + parser.error('autopep8 only takes one filename as argument ' + 'unless the "--in-place" or "--diff" options are ' + 'used') + + if opts.recursive and not (opts.in_place or opts.diff): + parser.error('--recursive must be used with --in-place or --diff') + + if opts.in_place and opts.diff: + parser.error('--in-place and --diff are mutually exclusive') + + return opts, args + + +def main(): + """Tool main.""" + opts, args = parse_args(sys.argv[1:]) + if opts.in_place or opts.diff: + filenames = list(set(args)) + else: + assert len(args) == 1 + assert not opts.recursive + filenames = args[:1] + + while filenames: + name = filenames.pop(0) + if opts.recursive and os.path.isdir(name): + for root, directories, children in os.walk(name): + filenames += [os.path.join(root, f) for f in children + if f.endswith('.py') and + not f.startswith('.')] + for d in directories: + if d.startswith('.'): + directories.remove(d) + else: + if opts.verbose: + sys.stderr.write('[file:%s]\n' % name) + try: + fix_file(name, opts) + except (UnicodeDecodeError, UnicodeEncodeError, IOError) as error: + sys.stderr.write(str(error) + '\n') + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/pylibs/pymode/auto.py b/pylibs/pymode/auto.py new file mode 100644 index 00000000..e535bd45 --- /dev/null +++ b/pylibs/pymode/auto.py @@ -0,0 +1,16 @@ +import vim +from autopep8 import fix_file, PEP8_PASSES_MAX + + +class Options(): + verbose = False + diff = False + in_place = True + recursive = False + pep8_passes = PEP8_PASSES_MAX + ignore = '' + select = '' + + +def fix_current_file(): + fix_file(vim.current.buffer.name, Options) diff --git a/pylibs/pymode/interface.py b/pylibs/pymode/interface.py index 7f7cd20b..a7863bd0 100644 --- a/pylibs/pymode/interface.py +++ b/pylibs/pymode/interface.py @@ -2,13 +2,17 @@ def get_option(name): - return vim.eval("pymode#Option('%s')" % name) + return get_bvar(name) or get_var(name) def get_var(name): return vim.eval("g:pymode_%s" % name) +def get_bvar(name): + return (int(vim.eval("exists('b:pymode_%s')" % name)) and vim.eval("b:pymode_%s" % name)) or None + + def get_current_buffer(): return vim.current.buffer diff --git a/pylibs/pymode/queue.py b/pylibs/pymode/queue.py index 841e251f..220d8397 100644 --- a/pylibs/pymode/queue.py +++ b/pylibs/pymode/queue.py @@ -43,6 +43,7 @@ def add_task(target, callback=None, buffer=None, title=None, *args, **kwargs): " Add all tasks. " task = Task(buffer, title=title, target=target, callback=callback, args=args, kwargs=kwargs) + task.daemon = True task.start() show_message('%s started.' % task.title) From a2b752fbcb9040297b02385af60360c914af6582 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 2 Aug 2012 11:25:03 +0400 Subject: [PATCH 104/513] Fix code run --- Changelog.rst | 5 +++-- autoload/pymode.vim | 3 --- autoload/pymode/run.vim | 19 ++++++++++++++----- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index ba453eed..8b332799 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -9,8 +9,9 @@ Changelog * Added pymode modeline ':help PythonModeModeline' * Added diagnostic tool ':call pymode#troubleshooting#Test()' * Added `PyLintAuto` command ':help PyLintAuto' -* Now Code checking is async operation -* Improve speed of pymode folding +* Code checking is async operation now +* More, more fast the pymode folding +* Repair python code run ## 2012-05-24 0.6.4 ------------------- diff --git a/autoload/pymode.vim b/autoload/pymode.vim index 37a9182a..a7bb8af3 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -175,6 +175,3 @@ endfunction "}}} " vim: fdm=marker:fdl=0 - - - diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index ad8ba582..bd92b1ac 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -3,9 +3,18 @@ fun! pymode#run#Run(line1, line2) "{{{ if &modifiable && &modified | write | endif py import StringIO py sys.stdout, _ = StringIO.StringIO(), sys.stdout - py execfile(vim.eval('expand("%s:p")'), {}, {}) - py sys.stdout, out = _, sys.stdout.getvalue() - call pymode#TempBuffer() - py vim.current.buffer.append(out.split('\n'), 0) - wincmd p + call pymode#WideMessage("Code running.") + try + py execfile(vim.eval('expand("%s:p")')) + py sys.stdout, out = _, sys.stdout.getvalue() + call pymode#TempBuffer() + py vim.current.buffer.append(out.split('\n'), 0) + wincmd p + call pymode#WideMessage("") + + catch /.*/ + + echohl Error | echo "Run-time error." | echohl none + + endtry endfunction "}}} From e7678bce35fead1dcfe90784854abd946d61eec6 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 2 Aug 2012 22:59:26 +0400 Subject: [PATCH 105/513] Move space key mapping to var --- ftplugin/python/pymode.vim | 8 +++++--- plugin/pymode.vim | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 29726560..df442d3c 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -105,9 +105,11 @@ if pymode#Option('rope') exe "noremap " . g:pymode_rope_short_prefix . "m :emenu Rope . " inoremap =RopeLuckyAssistInsertMode() - let s:prascm = g:pymode_rope_always_show_complete_menu ? "" : "" - exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm - exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm + if g:pymode_rope_map_space + let s:prascm = g:pymode_rope_always_show_complete_menu ? "" : "" + exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm + exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm + endif endif diff --git a/plugin/pymode.vim b/plugin/pymode.vim index da16e62e..e27d99a4 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -211,6 +211,9 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope " OPTION: g:pymode_rope_short_prefix -- string. call pymode#Default("g:pymode_rope_short_prefix", "") + " OPTION: g:pymode_rope_map_space -- string. + call pymode#Default("g:pymode_rope_map_space", 1) + " OPTION: g:pymode_rope_vim_completion -- bool. call pymode#Default("g:pymode_rope_vim_completion", 1) From ccd7dd5f62a0e0b0195a4a50165fc7d5e827a8fc Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 2 Aug 2012 23:01:41 +0400 Subject: [PATCH 106/513] Update changelog --- Changelog.rst | 4 ++-- README.rst | 18 ++++++++++-------- doc/pymode.txt | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 8b332799..afdb9144 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -## 2012-08-XX 0.6.5 +## 2012-08-02 0.6.5 ------------------- * Updated Pep8 to version 1.3.3 * Updated Pylint to version 0.25.2 @@ -11,7 +11,7 @@ Changelog * Added `PyLintAuto` command ':help PyLintAuto' * Code checking is async operation now * More, more fast the pymode folding -* Repair python code run +* Repaired execution of python code ## 2012-05-24 0.6.4 ------------------- diff --git a/README.rst b/README.rst index 03dc2d4e..11595a6a 100644 --- a/README.rst +++ b/README.rst @@ -30,15 +30,17 @@ See (very old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is Changelog ========= -## 2012-04-10 0.6.2 +## 2012-08-02 0.6.5 ------------------- -* Fix pymode_run for "unnamed" clipboard -* Add 'pymode_lint_mccabe_complexity' option -* Update Pep8 to version 1.0.1 -* Warning! Change 'pymode_rope_goto_def_newwin' option - for open "goto definition" in new window, set it to 'new' or 'vnew' - for horizontally or vertically split - If you use default behaviour (in the same buffer), not changes needed. +* Updated Pep8 to version 1.3.3 +* Updated Pylint to version 0.25.2 +* Fixed virtualenv support for windows users +* Added pymode modeline ':help PythonModeModeline' +* Added diagnostic tool ':call pymode#troubleshooting#Test()' +* Added `PyLintAuto` command ':help PyLintAuto' +* Code checking is async operation now +* More, more fast the pymode folding +* Repaired execution of python code Requirements diff --git a/doc/pymode.txt b/doc/pymode.txt index b2be35c8..aa641400 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -449,7 +449,7 @@ iM Operation with inner function or method. Check current buffer *:PyLintAuto* *PyLintAuto* - Automatic fix PEP8 errors + Automatic fix PEP8 errors in current buffer *:Pyrun* *Pyrun* Run current buffer From a03476a84e3dc7390f76bcfe7a32caa920efd774 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 2 Aug 2012 23:47:17 +0400 Subject: [PATCH 107/513] Fix pymode paths --- plugin/pymode.vim | 2 +- pylibs/__init__.py | 0 pylibs/pyflakes/checker.py | 2 +- pylibs/pymode/auto.py | 2 +- pylibs/pymode/lint.py | 10 +++++----- pylibs/ropevim.py | 38 ++++++++++++++++++++------------------ 6 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 pylibs/__init__.py diff --git a/plugin/pymode.vim b/plugin/pymode.vim index e27d99a4..42b13198 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -46,7 +46,7 @@ curpath = vim.eval("getcwd()") libpath = os.path.join(os.path.dirname(os.path.dirname( vim.eval("expand(':p')"))), 'pylibs') -sys.path = [libpath, curpath] + vim.eval("g:pymode_paths") + sys.path +sys.path = [os.path.dirname(libpath), libpath, curpath] + vim.eval("g:pymode_paths") + sys.path EOF endif diff --git a/pylibs/__init__.py b/pylibs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pylibs/pyflakes/checker.py b/pylibs/pyflakes/checker.py index fa2494e0..643603cd 100644 --- a/pylibs/pyflakes/checker.py +++ b/pylibs/pyflakes/checker.py @@ -6,7 +6,7 @@ import os.path import _ast -from pyflakes import messages +from . import messages # utility function to iterate over an AST node's children, adapted diff --git a/pylibs/pymode/auto.py b/pylibs/pymode/auto.py index e535bd45..cf43d0e7 100644 --- a/pylibs/pymode/auto.py +++ b/pylibs/pymode/auto.py @@ -1,5 +1,5 @@ import vim -from autopep8 import fix_file, PEP8_PASSES_MAX +from pylibs.autopep8 import fix_file, PEP8_PASSES_MAX class Options(): diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index de2f853f..8d1eaddb 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -76,7 +76,7 @@ def parse_result(result): def mccabe(filename): - import mccabe as mc + from pylibs.mccabe import get_code_complexity complexity = int(get_option('lint_mccabe_complexity')) return mc.get_module_complexity(filename, min=complexity) @@ -89,7 +89,7 @@ def pep8(filename): def pylint(filename): - from logilab.astng.builder import MANAGER + from pylibs.logilab.astng.builder import MANAGER PYLINT or _init_pylint() linter = PYLINT['lint'] @@ -102,7 +102,7 @@ def pylint(filename): def pyflakes(filename): - from pyflakes import checker + from pylibs.pyflakes import checker import _ast codeString = file(filename, 'U').read() + '\n' @@ -125,7 +125,7 @@ def pyflakes(filename): def _init_pylint(): - from pylint import lint, checkers, reporters + from pylibs.pylint import lint, checkers, reporters import re class VimReporter(reporters.BaseReporter): @@ -160,7 +160,7 @@ def add_message(self, msg_id, location, msg): def _init_pep8(): - import pep8 as p8 + from pylibs import pep8 as p8 class _PEP8Report(p8.BaseReport): diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 5c45b24e..b54cd83e 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -3,14 +3,14 @@ import tempfile import re -import ropemode.decorators -import ropemode.environment -import ropemode.interface +from pylibs.ropemode import decorators +from pylibs.ropemode import environment +from pylibs.ropemode import interface import vim -class VimUtils(ropemode.environment.Environment): +class VimUtils(environment.Environment): def __init__(self, *args, **kwargs): super(VimUtils, self).__init__(*args, **kwargs) @@ -62,10 +62,10 @@ def _update_proposals(self, values): if not self.get('extended_complete'): return u','.join(u"'%s'" % self._completion_text(proposal) - for proposal in values) + for proposal in values) return u','.join(self._extended_completion(proposal) - for proposal in values) + for proposal in values) def _command(self, command, encode=False): if encode: @@ -79,7 +79,7 @@ def ask_completion(self, prompt, values, starting=None): if starting: col -= len(starting) self._command(u'call complete(%s, [%s])' % (col, proposals), - encode=True) + encode=True) return None return self.ask_values(prompt, values, starting=starting, @@ -118,8 +118,8 @@ def _decode_line(self, line): return line.decode(self._get_encoding()) def _position_to_offset(self, lineno, colno): - result = min(colno, len(self.buffer[lineno -1]) + 1) - for line in self.buffer[:lineno-1]: + result = min(colno, len(self.buffer[lineno - 1]) + 1) + for line in self.buffer[:lineno - 1]: line = self._decode_line(line) result += len(line) + 1 return result @@ -303,15 +303,17 @@ def _completion_data(self, proposal): return proposal _docstring_re = re.compile('^[\s\t\n]*([^\n]*)') + def _extended_completion(self, proposal): # we are using extended complete and return dicts instead of strings. # `ci` means "completion item". see `:help complete-items` word, _, menu = map(lambda x: x.strip(), proposal.name.partition(':')) ci = dict( - word=word, - info='', - kind=''.join(s if s not in 'aeyuo' else '' for s in proposal.type)[:3], - menu=menu or '') + word=word, + info='', + kind=''.join( + s if s not in 'aeyuo' else '' for s in proposal.type)[:3], + menu=menu or '') if proposal.scope == 'parameter_keyword': default = proposal.get_default() @@ -388,18 +390,18 @@ def __call__(self, arg_lead, cmd_line, cursor_pos): # don't know if self.values can be empty but better safe then sorry if self.values: if not isinstance(self.values[0], basestring): - result = [proposal.name for proposal in self.values \ + result = [proposal.name for proposal in self.values if proposal.name.startswith(arg_lead)] else: - result = [proposal for proposal in self.values \ + result = [proposal for proposal in self.values if proposal.startswith(arg_lead)] vim.command('let s:completions = %s' % result) -ropemode.decorators.logger.message = echo -ropemode.decorators.logger.only_short = True +decorators.logger.message = echo +decorators.logger.only_short = True _completer = _ValueCompleter() _env = VimUtils() -_interface = ropemode.interface.RopeMode(env=_env) +_interface = interface.RopeMode(env=_env) From 01ea184061d0bb6595b4e350ea6f7a251480c3c4 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 2 Aug 2012 23:48:03 +0400 Subject: [PATCH 108/513] increment version --- Changelog.rst | 2 +- doc/pymode.txt | 2 +- plugin/pymode.vim | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index afdb9144..93e7d784 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -## 2012-08-02 0.6.5 +## 2012-08-02 0.6.6 ------------------- * Updated Pep8 to version 1.3.3 * Updated Pylint to version 0.25.2 diff --git a/doc/pymode.txt b/doc/pymode.txt index aa641400..7b7fc0fc 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.4 + Version: 0.6.6 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 42b13198..e8a3488d 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.6.5" +let g:pymode_version = "0.6.6" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 9d51afc8e91064c6844a65bc341807f653eadfe1 Mon Sep 17 00:00:00 2001 From: Robert David Grant Date: Sun, 5 Aug 2012 02:33:54 -0500 Subject: [PATCH 109/513] Edit the help file. --- doc/pymode.txt | 140 +++++++++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 64 deletions(-) diff --git a/doc/pymode.txt b/doc/pymode.txt index 7b7fc0fc..c7578048 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -25,12 +25,12 @@ CONTENTS *Python-mode-contents* 1. Intro ~ *PythonMode* -Python-mode is a vim plugin that allows you to use the pylint, rope, pydoc -library in vim to provide features like python code looking for bugs, -refactoring and some other useful things. +Python-mode is a vim plugin that allows you to use the pylint, rope, and pydoc +libraries in vim to provide features like python code bug checking, +refactoring, and some other useful things. This plugin allow you create python code in vim very easily. There is no need -to install the pylint or rope library on your system. +to install the pylint or rope libraries on your system. ============================================================================== @@ -41,7 +41,7 @@ to install the pylint or rope library on your system. Pylint options (ex. disable messages) may be defined in '$HOME/pylint.rc' See pylint documentation. -The script provides the following options that can customise the behaviour the +This script provides the following options that can customise the behaviour of PythonMode. These options should be set in your vimrc. |'pymode_paths'| Additional python paths for pymode @@ -74,7 +74,7 @@ PythonMode. These options should be set in your vimrc. |'pymode_lint_signs'| Place signs -|'pymode_lint_jump'| Auto jump on first error +|'pymode_lint_jump'| Auto jump to first error |'pymode_lint_hold'| Hold cursor in current window @@ -116,7 +116,7 @@ PythonMode. These options should be set in your vimrc. 2.1. Customisation details ~ *PythonModeOptionsDetails* -To enable any of the below options you should put the given line in your +To enable any of the options below you should put the given line in your '$HOME/.vimrc'. See |vimrc-intro|. ------------------------------------------------------------------------------ @@ -157,7 +157,7 @@ This option set additional python import paths Values: 0 or 1. Default: 1. -If this option is set to 0 then docs script is disabled. +If this option is set to 0 then the doc script is disabled. ------------------------------------------------------------------------------ *'pymode_doc_key'* @@ -183,7 +183,7 @@ Set key for run python code. Values: 0 or 1. Default: 1. -If this option is set to 0 then pylint script is disabled. +If this option is set to 0 then the pylint script is disabled. ------------------------------------------------------------------------------ *'pymode_lint_checker'* @@ -192,11 +192,11 @@ Values: "pylint", "pyflakes", "pep8", "mccabe" Default: "pyflakes,pep8,mccabe". -This option set code checkers. +This option sets code checkers. ------------------------------------------------------------------------------ *'pymode_lint_ignore'* -Values: IDs of errors, separated by commas or empty string +Values: IDs of errors, separated by commas or empty strings E.g. "E501,W002", "E2,W" (Skip all Warnings and Errors startswith E2) and etc ~ Default: "E501". @@ -206,7 +206,7 @@ See also: |'pymode_lint_select'|, |'pymode_lint_config'| ------------------------------------------------------------------------------ *'pymode_lint_select'* -Values: IDs of errors, separated by commas or empty string +Values: IDs of errors, separated by commas or empty strings E.g. "W002,C" Force W002 and all C-ids ~ Default: "". @@ -219,15 +219,15 @@ See also: |'pymode_lint_ignore'|, |'pymode_lint_config'| Values: 0 or 1 Default: 0 -This option enabled "on the fly" code checking +This option enables "on the fly" code checking ------------------------------------------------------------------------------ *'pymode_lint_config'* Values: 'Path to pylint configuration file' Default: "$HOME/.pylintrc" -If this option is set path to pylint configuration. If configuration not found -uses file 'pylintrc' from python-mode sources. +This option sets the path to the pylint configuration file. If the +file is not found, use the 'pylintrc' file from python-mode sources. See also: |'pymode_lint_ignore'|, |'pymode_lint_select'| @@ -236,91 +236,92 @@ See also: |'pymode_lint_ignore'|, |'pymode_lint_select'| Values: 0 or 1. Default: 1. -If this option is set to 0 then pylint auto check every save is disabled. +If this option is set to 0, then pylint auto-checking on every save is +disabled. ------------------------------------------------------------------------------ *'pymode_lint_cwindow'* Values: 0 or 1. Default: 1. -If this option is set to 0 then pylint not show cwindow. +If this option is set to 0 then pylint will not show cwindow. ------------------------------------------------------------------------------ *'pymode_lint_message'* Values: 0 or 1. Default: 1. -If this option is set to 0 then pylint not show errors in bottom +If this option is set to 0 then pylint will not show errors at bottom. ------------------------------------------------------------------------------ *'pymode_lint_signs'* Values: 0 or 1. Default: 1. -If this option is set to 0 then pylint not place error signs. +If this option is set to 0 then pylint will not place error signs. ------------------------------------------------------------------------------ *'pymode_lint_jump'* Values: 0 or 1. Default: 0. -If this option is set to 0 then pylint not jump on first error. +If this option is set to 0 then pylint will not jump to the first error. ------------------------------------------------------------------------------ *'pymode_lint_hold'* Values: 0 or 1. Default: 0. -If this option is set to 0 then pylint switch on quickfix window when it open -Not working when |'pymode_lint_jump'| enabled. +If this option is set to 0 then pylint will switch on the quickfix window when +it opens. Doesn't work when |'pymode_lint_jump'| enabled. ------------------------------------------------------------------------------ *'pymode_lint_minheight'* Values: int Default: 3. -Set minimal height for pylint cwindow +Set minimal height for the pylint cwindow. ------------------------------------------------------------------------------ *'pymode_lint_mccabe_complexity'* Values: int Default: 8. -Set minimal complexity for mccabe linter. +Set minimal complexity for the mccabe linter. ------------------------------------------------------------------------------ *'pymode_lint_maxheight'* Values: int Default: 6. -Set maximal height for pylint cwindow +Set maximal height for the pylint cwindow. ------------------------------------------------------------------------------ *'pymode_rope'* Values: 0 or 1. Default: 1. -If this option is set to 0 then rope script is disabled. +If this option is set to 0 then the rope script is disabled. ------------------------------------------------------------------------------ *'pymode_breakpoint'* Values: 0 or 1. Default: 1. -If this option is set to 0 then breakpoint script is disabled. +If this option is set to 0 then the breakpoint script is disabled. ------------------------------------------------------------------------------ *'pymode_breakpoint_key'* Default: 'b'. - Key for set/unset breakpoint +Key for setting/unsetting breakpoints. ------------------------------------------------------------------------------ *'pymode_utils'* Values: 0 or 1. Default: 1. -If this option is set to 0 then utils script is disabled. +If this option is set to 0 the then utils script is disabled. ------------------------------------------------------------------------------ *'pymode_virtualenv'* @@ -334,7 +335,7 @@ If this option is set to 0 then virtualenv support is disabled. Values: 0 or 1. Default: 1. -Autoremove unused whitespaces +Auto-remove unused whitespaces. ------------------------------------------------------------------------------ *'pymode_syntax'* @@ -349,7 +350,8 @@ not be used. Values: 0 or 1. Default: 1. -If this option is set to 1, pymode enable next options for python buffers: > +If this option is set to 1, pymode will enable the following options for python +buffers: > setlocal cinwords=if,elif,else,for,while,try,except,finally,def,class setlocal cindent @@ -367,14 +369,15 @@ If this option is set to 1, pymode enable next options for python buffers: > Values: 0 or 1. Default: 1. -If this option is set to 1, pymode enable python-folding. +If this option is set to 1, pymode will enable python-folding. ------------------------------------------------------------------------------ *'pymode_options_other'* Values: 0 or 1. Default: 1. -If this option is set to 1, pymode enable next options for python buffers: > +If this option is set to 1, pymode will enable the following options for python +buffers: > setlocal complete+=t setlocal formatoptions-=t @@ -387,16 +390,16 @@ If this option is set to 1, pymode enable next options for python buffers: > Values: 0 or 1. Default: 1. -If this option is set to 1, pymode enable some python motions. Pymode-motion -is beta. +If this option is set to 1, pymode will enable some python motions. +Pymode-motion is beta. ================ ============================ Key Command ================ ============================ -[[ Jump on previous class or function (normal, visual, operator modes) -]] Jump on next class or function (normal, visual, operator modes) -[M Jump on previous class or method (normal, visual, operator modes) -]M Jump on next class or method (normal, visual, operator modes) +[[ Jump to previous class or function (normal, visual, operator modes) +]] Jump to next class or function (normal, visual, operator modes) +[M Jump to previous class or method (normal, visual, operator modes) +]M Jump to next class or method (normal, visual, operator modes) aC Select a class. Ex: vaC, daC, yaC, caC (normal, operator modes) iC Select inner class. Ex: viC, diC, yiC, ciC (normal, operator modes) aM Select a function or method. Ex: vaM, daM, yaM, caM (normal, operator modes) @@ -408,7 +411,7 @@ iM Select inner function or method. Ex: viM, diM, yiM, ciM (norma 3. Default Keys ~ *PythonModeKeys* -For redefine keys see: |PythonModeOptions| +To redefine keys, see: |PythonModeOptions| ================ ============================ Key Command @@ -417,10 +420,10 @@ K Show python docs for current word under cursor C-Space Rope code assist r Run current buffer b Set breakpoints -[[ Jump on previous class or function (normal, visual, operator modes) -]] Jump on next class or function (normal, visual, operator modes) -[M Jump on previous class or method (normal, visual, operator modes) -]M Jump on next class or method (normal, visual, operator modes) +[[ Jump to previous class or function (normal, visual, operator modes) +]] Jump to next class or function (normal, visual, operator modes) +[M Jump to previous class or method (normal, visual, operator modes) +]M Jump to next class or method (normal, visual, operator modes) aC C Operation with a class. Ex: vaC, daC, dC, yaC, yC, caC, cC (normal, operator modes) iC Operation with inner class. @@ -449,7 +452,7 @@ iM Operation with inner function or method. Check current buffer *:PyLintAuto* *PyLintAuto* - Automatic fix PEP8 errors in current buffer + Automatically fix PEP8 errors in the current buffer *:Pyrun* *Pyrun* Run current buffer @@ -459,36 +462,45 @@ iM Operation with inner function or method. 5. FAQ ~ *PythonModeFAQ* -Python-mode dont work ---------------------- +Python-mode doesn't work +------------------------ -Run ":call pymode#troubleshooting#Test()", fix warning or send me output. +Run ":call pymode#troubleshooting#Test()" and fix the warning or send me the +output. Rope completion is very slow ---------------------------- -To work rope_ creates a service directory: `.ropeproject`. -If |'pymode_rope_guess_project'| set on (by default) and `.ropeproject` in current dir not found, rope scan `.ropeproject` on every dir in parent path. -If rope finded `.ropeproject` in parent dirs, rope set project for all child dir and scan may be slow for many dirs and files. +To work, rope_ creates a service directory: `.ropeproject`. If +|'pymode_rope_guess_project'| is set on (as it is by default) and +`.ropeproject` is not found in the current dir, rope will scan for +`.ropeproject` in every dir in the parent path. If rope finds `.ropeproject` +in parent dirs, rope sets project for all child dir and the scan may be slow +for many dirs and files. Solutions: -- Disable |'pymode_rope_guess_project'| to make rope always create `.ropeproject` in current dir. -- Delete `.ropeproject` from dip parent dir to make rope create `.ropeproject` in current dir. -- Press `po` or `:RopeOpenProject` to make force rope create `.ropeproject` in current dir. - +- Disable |'pymode_rope_guess_project'| to make rope always create + `.ropeproject` in the current dir. +- Delete `.ropeproject` from the parent dir to make rope create `.ropeproject` + in the current dir. +- Press `po` or `:RopeOpenProject` to force rope to create `.ropeproject` + in the current dir. Pylint check is very slow ------------------------- -In some projects pylint_ may check slowly, because it also scan imported modules if possible. -Try use pyflakes, see |'pymode_lint_checker'|. +In some projects pylint_ may check slowly, because it also scan imported +modules if possible. Try using pyflakes: see |'pymode_lint_checker'|. -You may set |exrc| and |secure| in your |vimrc| for auto set custom settings from `.vimrc` from your projects directories. +You may set |exrc| and |secure| in your |vimrc| to auto-set custom settings +from `.vimrc` from your projects directories. > - Example: On Flask projects I automatically set 'g:pymode_lint_checker = "pyflakes"', on Django 'g:pymode_lint_checker = "pylint"' + Example: On Flask projects I automatically set + 'g:pymode_lint_checker = "pyflakes"'. + On Django 'g:pymode_lint_checker = "pylint"' < @@ -528,14 +540,14 @@ You may set |exrc| and |secure| in your |vimrc| for auto set custom settings fro 7. License ~ *PythonModeLicense* -The Python-mode is released under the GNU lesser general public license. +Python-mode is released under the GNU lesser general public license. See: http://www.gnu.org/copyleft/lesser.html -If you like this plugin, you can send me postcard :) -My address is here: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Klenov". -Thanks for support! +If you like this plugin, you can send me a postcard :) +My address is: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Klenov". +Thanks for your support! -Version 0.6.5: I still haven't received any postcard, guys :( +Version 0.6.5: I still haven't received any postcards, guys :( ------------------------------------------------------------------------------ From 851755858d460ed8992b2aa7bd03210adada1641 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 15 Aug 2012 12:51:11 +0400 Subject: [PATCH 110/513] Fix queue --- autoload/pymode/queue.vim | 13 ++++++++++++- ftplugin/python/pymode.vim | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/autoload/pymode/queue.vim b/autoload/pymode/queue.vim index 1c796567..8de52cdb 100644 --- a/autoload/pymode/queue.vim +++ b/autoload/pymode/queue.vim @@ -1,4 +1,15 @@ fun! pymode#queue#Poll() "{{{ + + " Check current tasks py queue.check_task() - call feedkeys("\\", 't') + + " Update interval + if mode() == 'i' + let p = getpos('.') + silent exe 'call feedkeys("\\", "n")' + call setpos('.', p) + else + call feedkeys("f\e", "n") + endif + endfunction "}}} diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index df442d3c..00e7744b 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -73,7 +73,6 @@ if pymode#Option('lint') " DESC: Set autocommands if pymode#Option('lint_write') au BufWritePost PyLint - au BufLeave py queue.stop_queue() endif if pymode#Option('lint_onfly') @@ -86,8 +85,9 @@ if pymode#Option('lint') endif " DESC: Run queue - au CursorHold call pymode#queue#Poll() setlocal updatetime=1000 + au CursorHold call pymode#queue#Poll() + au BufLeave py queue.stop_queue() endif From 857d4d41a3a42a341aaf7752ae65da603b9c14e1 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 15 Aug 2012 13:13:36 +0400 Subject: [PATCH 111/513] Update changelog --- Changelog.rst | 5 +++++ doc/pymode.txt | 2 +- plugin/pymode.vim | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 93e7d784..d1389275 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2012-08-15 0.6.7 +------------------- +* Fix documentation. Thanks (c) bgrant; +* Fix pymode "async queue" support. + ## 2012-08-02 0.6.6 ------------------- * Updated Pep8 to version 1.3.3 diff --git a/doc/pymode.txt b/doc/pymode.txt index 7b7fc0fc..277f15a1 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.6 + Version: 0.6.7 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/plugin/pymode.vim b/plugin/pymode.vim index e8a3488d..7df0dc53 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.6.6" +let g:pymode_version = "0.6.7" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 5d2c85709bee6fdec20f93ac1946bb5b29447afe Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 3 Sep 2012 21:43:05 +0400 Subject: [PATCH 112/513] Update autopep8 to version 0.8 --- pylibs/autopep8.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pylibs/autopep8.py b/pylibs/autopep8.py index 2b50197c..b0114bc5 100644 --- a/pylibs/autopep8.py +++ b/pylibs/autopep8.py @@ -49,7 +49,7 @@ pep8 = None -__version__ = '0.7.3' +__version__ = '0.8' PEP8_BIN = 'pep8' @@ -1580,4 +1580,7 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + try: + sys.exit(main()) + except KeyboardInterrupt: + sys.exit(1) From acb561583e2bbe50302de07ba52defbc160f695c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 6 Sep 2012 13:22:12 +0400 Subject: [PATCH 113/513] Add pymode indentation --- README.rst | 12 ++- after/indent/python.vim | 14 +++ autoload/pymode/indent.vim | 184 +++++++++++++++++++++++++++++++++++++ doc/pymode.txt | 27 ++---- ftplugin/python/pymode.vim | 16 +--- plugin/pymode.vim | 10 +- 6 files changed, 221 insertions(+), 42 deletions(-) create mode 100644 after/indent/python.vim create mode 100644 autoload/pymode/indent.vim diff --git a/README.rst b/README.rst index 11595a6a..a2f3b347 100644 --- a/README.rst +++ b/README.rst @@ -287,11 +287,11 @@ Default values: :: " Autoremove unused whitespaces let g:pymode_utils_whitespaces = 1 - " Set default pymode python indent options - let g:pymode_options_indent = 1 + " Enable pymode indentation + let g:pymode_indent = 1 - " Set default pymode python other options - let g:pymode_options_other = 1 + " Set default pymode python options + let g:pymode_options = 1 Syntax highlight @@ -478,6 +478,10 @@ Copyright (C) 2012 Kirill Klenov (klen_) Copyright (c) 2010 Dmitry Vasiliev http://www.hlabs.spb.ru/vim/python.vim + **PEP8 VIM indentation** + Copyright (c) 2012 Hynek Schlawack + http://github.com/hynek/vim-python-pep8-indent + License ======= diff --git a/after/indent/python.vim b/after/indent/python.vim new file mode 100644 index 00000000..1e324f23 --- /dev/null +++ b/after/indent/python.vim @@ -0,0 +1,14 @@ +if pymode#Default('b:pymode_indent', 1) || !g:pymode_indent + finish +endif + + +setlocal nolisp +setlocal tabstop=4 +setlocal softtabstop=4 +setlocal shiftwidth=4 +setlocal shiftround +setlocal expandtab +setlocal autoindent +setlocal indentexpr=pymode#indent#Indent(v:lnum) +setlocal indentkeys=!^F,o,O,<:>,0),0],0},=elif,=except diff --git a/autoload/pymode/indent.vim b/autoload/pymode/indent.vim new file mode 100644 index 00000000..fb4d085d --- /dev/null +++ b/autoload/pymode/indent.vim @@ -0,0 +1,184 @@ +" PEP8 compatible Python indent file +" Language: Python +" Maintainer: Hynek Schlawack +" Prev Maintainer: Eric Mc Sween (address invalid) +" Original Author: David Bustos (address invalid) +" Last Change: 2012-06-21 +" License: Public Domainlet + + +function! pymode#indent#Indent(lnum) + + " First line has indent 0 + if a:lnum == 1 + return 0 + endif + + " If we can find an open parenthesis/bracket/brace, line up with it. + call cursor(a:lnum, 1) + let parlnum = s:SearchParensPair() + if parlnum > 0 + let parcol = col('.') + let closing_paren = match(getline(a:lnum), '^\s*[])}]') != -1 + if match(getline(parlnum), '[([{]\s*$', parcol - 1) != -1 + if closing_paren + return indent(parlnum) + else + return indent(parlnum) + &shiftwidth + endif + else + return parcol + endif + endif + + " Examine this line + let thisline = getline(a:lnum) + let thisindent = indent(a:lnum) + + " If the line starts with 'elif' or 'else', line up with 'if' or 'elif' + if thisline =~ '^\s*\(elif\|else\)\>' + let bslnum = s:BlockStarter(a:lnum, '^\s*\(if\|elif\)\>') + if bslnum > 0 + return indent(bslnum) + else + return -1 + endif + endif + + " If the line starts with 'except' or 'finally', line up with 'try' + " or 'except' + if thisline =~ '^\s*\(except\|finally\)\>' + let bslnum = s:BlockStarter(a:lnum, '^\s*\(try\|except\)\>') + if bslnum > 0 + return indent(bslnum) + else + return -1 + endif + endif + + " Examine previous line + let plnum = a:lnum - 1 + let pline = getline(plnum) + let sslnum = s:StatementStart(plnum) + + " If the previous line is blank, keep the same indentation + if pline =~ '^\s*$' + return -1 + endif + + " If this line is explicitly joined, try to find an indentation that looks + " good. + if pline =~ '\\$' + let compound_statement = '^\s*\(if\|while\|for\s.*\sin\|except\)\s*' + let maybe_indent = matchend(getline(sslnum), compound_statement) + if maybe_indent != -1 + return maybe_indent + else + return indent(sslnum) + &sw * 2 + endif + endif + + " If the previous line ended with a colon and is not a comment, indent + " relative to statement start. + if pline =~ ':\s*$' && pline !~ '^\s*#' + return indent(sslnum) + &sw + endif + + " If the previous line was a stop-execution statement or a pass + if getline(sslnum) =~ '^\s*\(break\|continue\|raise\|return\|pass\)\>' + " See if the user has already dedented + if indent(a:lnum) > indent(sslnum) - &sw + " If not, recommend one dedent + return indent(sslnum) - &sw + endif + " Otherwise, trust the user + return -1 + endif + + " In all other cases, line up with the start of the previous statement. + return indent(sslnum) +endfunction + + +" Find backwards the closest open parenthesis/bracket/brace. +function! s:SearchParensPair() + let line = line('.') + let col = col('.') + + " Skip strings and comments and don't look too far + let skip = "line('.') < " . (line - 50) . " ? dummy :" . + \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? ' . + \ '"string\\|comment"' + + " Search for parentheses + call cursor(line, col) + let parlnum = searchpair('(', '', ')', 'bW', skip) + let parcol = col('.') + + " Search for brackets + call cursor(line, col) + let par2lnum = searchpair('\[', '', '\]', 'bW', skip) + let par2col = col('.') + + " Search for braces + call cursor(line, col) + let par3lnum = searchpair('{', '', '}', 'bW', skip) + let par3col = col('.') + + " Get the closest match + if par2lnum > parlnum || (par2lnum == parlnum && par2col > parcol) + let parlnum = par2lnum + let parcol = par2col + endif + if par3lnum > parlnum || (par3lnum == parlnum && par3col > parcol) + let parlnum = par3lnum + let parcol = par3col + endif + + " Put the cursor on the match + if parlnum > 0 + call cursor(parlnum, parcol) + endif + return parlnum +endfunction + + +" Find the start of a multi-line statement +function! s:StatementStart(lnum) + let lnum = a:lnum + while 1 + if getline(lnum - 1) =~ '\\$' + let lnum = lnum - 1 + else + call cursor(lnum, 1) + let maybe_lnum = s:SearchParensPair() + if maybe_lnum < 1 + return lnum + else + let lnum = maybe_lnum + endif + endif + endwhile +endfunction + + +" Find the block starter that matches the current line +function! s:BlockStarter(lnum, block_start_re) + let lnum = a:lnum + let maxindent = 10000 " whatever + while lnum > 1 + let lnum = prevnonblank(lnum - 1) + if indent(lnum) < maxindent + if getline(lnum) =~ a:block_start_re + return lnum + else + let maxindent = indent(lnum) + " It's not worth going further if we reached the top level + if maxindent == 0 + return -1 + endif + endif + endif + endwhile + return -1 +endfunction diff --git a/doc/pymode.txt b/doc/pymode.txt index 2704540c..700cebc6 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -100,10 +100,9 @@ PythonMode. These options should be set in your vimrc. |'pymode_syntax'| Turns off the custom syntax highlighting -|'pymode_options_indent'| Set default pymode options for - python indentation +|'pymode_indent'| Enable/Disable pymode PEP8 indentation -|'pymode_options_other'| Set default pymode options for +|'pymode_options'| Set default pymode options for python codding |'pymode_motion'| Enable pymode motion stuff @@ -346,24 +345,12 @@ If this option is set to 0 then the custom syntax highlighting will not be used. ------------------------------------------------------------------------------ - *'pymode_options_indent'* + *'pymode_indent'* Values: 0 or 1. Default: 1. -If this option is set to 1, pymode will enable the following options for python -buffers: > +If this option is set to 1, pymode will enable python indentation support - setlocal cinwords=if,elif,else,for,while,try,except,finally,def,class - setlocal cindent - setlocal tabstop=4 - setlocal softtabstop=4 - setlocal shiftwidth=4 - setlocal shiftround - setlocal smartindent - setlocal smarttab - setlocal expandtab - setlocal autoindent -< ------------------------------------------------------------------------------ *'pymode_folding'* Values: 0 or 1. @@ -372,7 +359,7 @@ Default: 1. If this option is set to 1, pymode will enable python-folding. ------------------------------------------------------------------------------ - *'pymode_options_other'* + *'pymode_options'* Values: 0 or 1. Default: 1. @@ -535,6 +522,10 @@ from `.vimrc` from your projects directories. Copyright (c) 2010 Dmitry Vasiliev http://www.hlabs.spb.ru/vim/python.vim + PEP8 VIM indentation + Copyright (c) 2012 Hynek Schlawack + http://github.com/hynek/vim-python-pep8-indent + ============================================================================== 7. License ~ diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 00e7744b..713bafed 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -15,22 +15,8 @@ endif " Options {{{ -" Python indent options -if pymode#Option('options_indent') - setlocal cinwords=if,elif,else,for,while,try,except,finally,def,class - setlocal cindent - setlocal tabstop=4 - setlocal softtabstop=4 - setlocal shiftwidth=4 - setlocal shiftround - setlocal smartindent - setlocal smarttab - setlocal expandtab - setlocal autoindent -endif - " Python other options -if pymode#Option('options_other') +if pymode#Option('options') setlocal complete+=t setlocal formatoptions-=t if v:version > 702 && !&relativenumber diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 7df0dc53..852d3605 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -281,13 +281,13 @@ call pymode#Default("g:pymode_folding", 1) " OPTION: g:pymode_syntax -- bool. Enable python-mode syntax for pyfiles. call pymode#Default("g:pymode_syntax", 1) +" OPTION: g:pymode_indent -- bool. Enable/Disable pymode PEP8 indentation +call pymode#Default("g:pymode_indent", 1) + " OPTION: g:pymode_utils_whitespaces -- bool. Remove unused whitespaces on save call pymode#Default("g:pymode_utils_whitespaces", 1) -" OPTION: g:pymode_options_indent -- bool. To set indent options. -call pymode#Default("g:pymode_options_indent", 1) - -" OPTION: g:pymode_options_other -- bool. To set other options. -call pymode#Default("g:pymode_options_other", 1) +" OPTION: g:pymode_options -- bool. To set some python options. +call pymode#Default("g:pymode_options", 1) " vim: fdm=marker:fdl=0 From 1c9372c5262c30bfcb96e9ed939ad604dbe672c3 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 6 Sep 2012 13:26:01 +0400 Subject: [PATCH 114/513] Update changelog --- Changelog.rst | 4 ++++ plugin/pymode.vim | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index d1389275..a424087c 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,10 @@ Changelog ========= +## 2012-09-06 0.6.8 +------------------- +* Add PEP8 indentation ":help 'pymode_indent'" + ## 2012-08-15 0.6.7 ------------------- * Fix documentation. Thanks (c) bgrant; diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 852d3605..c8f800be 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.6.7" +let g:pymode_version = "0.6.8" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From c62fe9e473e1d42027d6b16bbd7584aa11730e93 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 6 Sep 2012 13:26:28 +0400 Subject: [PATCH 115/513] Thank you, Robert Grant! --- doc/pymode.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/pymode.txt b/doc/pymode.txt index 700cebc6..776296e8 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.7 + Version: 0.6.8 ============================================================================== CONTENTS *Python-mode-contents* @@ -538,8 +538,6 @@ If you like this plugin, you can send me a postcard :) My address is: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Klenov". Thanks for your support! -Version 0.6.5: I still haven't received any postcards, guys :( - ------------------------------------------------------------------------------ From e1311f5820ecf9967d2ca4e9423683d7b07ccde8 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 6 Sep 2012 13:26:58 +0400 Subject: [PATCH 116/513] Got them! --- README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.rst b/README.rst index a2f3b347..57a150c0 100644 --- a/README.rst +++ b/README.rst @@ -492,8 +492,6 @@ If you like this plugin, you can send me postcard :) My address is here: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Klenov". **Thanks for support!** -Version 0.6.5: I still haven't received any postcard, guys :( - .. _GNU lesser general public license: http://www.gnu.org/copyleft/lesser.html .. _klen: http://klen.github.com/ From da952022a8d8ce97c18c0b32d10ff7b2ba59c171 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 7 Sep 2012 13:32:29 +0400 Subject: [PATCH 117/513] Update diagnostic tool --- autoload/pymode/troubleshooting.vim | 72 ++++++++++++++++++++--------- doc/pymode.txt | 9 ---- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/autoload/pymode/troubleshooting.vim b/autoload/pymode/troubleshooting.vim index b04f2506..02e00a26 100644 --- a/autoload/pymode/troubleshooting.vim +++ b/autoload/pymode/troubleshooting.vim @@ -2,9 +2,17 @@ fun! pymode#troubleshooting#Test() "{{{ new setlocal buftype=nofile bufhidden=delete noswapfile nowrap + + let os = "Unknown" + if has('win16') || has('win32') || has('win64') + let os = "Windows" + else + let os = substitute(system('uname'), "\n", "", "") + endif + call append('0', ['Pymode diagnostic', \ '===================', - \ 'VIM:' . v:version . ' multi_byte:' . has('multi_byte') . ' pymode: ' . g:pymode_version, + \ 'VIM:' . v:version . ', OS: ' . os .', multi_byte:' . has('multi_byte') . ', pymode: ' . g:pymode_version, \ '']) let python = 1 @@ -22,26 +30,48 @@ fun! pymode#troubleshooting#Test() "{{{ call append('$', 'Pymode variables:') call append('$', '-------------------') - call append('$', 'pymode:' . g:pymode) - call append('$', 'pymode_lint:' . g:pymode_lint) - call append('$', 'pymode_rope:' . g:pymode_rope) - call append('$', 'pymode_path:' . g:pymode_path) - call append('$', 'pymode_doc:' . g:pymode_doc) - call append('$', 'pymode_run:' . g:pymode_run) - call append('$', 'pymode_virtualenv:' . g:pymode_virtualenv) - call append('$', 'pymode_breakpoint:' . g:pymode_breakpoint) - call append('$', 'pymode_path:' . g:pymode_path) - call append('$', 'pymode_folding:' . g:pymode_folding) - call append('$', 'pymode_syntax:' . g:pymode_syntax) - call append('$', 'pymode_utils_whitespaces:' . g:pymode_utils_whitespaces) - call append('$', 'pymode_options_indent:' . g:pymode_options_indent) - call append('$', 'pymode_options_other:' . g:pymode_options_other) - - if len(g:pymode_virtualenv_enabled) - call append('$', 'Enabled virtualenv:') - call append('$', '-------------------') - call append('$', g:pymode_virtualenv_enabled) - call append('$', '') + call append('$', 'let pymode = ' . string(g:pymode)) + if g:pymode + call append('$', 'let pymode_path = ' . string(g:pymode_path)) + call append('$', 'let pymode_paths = ' . string(g:pymode_paths)) + + call append('$', 'let pymode_doc = ' . string(g:pymode_doc)) + if g:pymode_doc + call append('$', 'let pymode_doc_key = ' . string(g:pymode_doc_key)) + endif + + call append('$', 'let pymode_run = ' . string(g:pymode_run)) + if g:pymode_run + call append('$', 'let pymode_run_key = ' . string(g:pymode_run_key)) + endif + + call append('$', 'let pymode_lint = ' . string(g:pymode_lint)) + if g:pymode_lint + call append('$', 'let pymode_lint_checker = ' . string(g:pymode_lint_checker)) + call append('$', 'let pymode_lint_ignore = ' . string(g:pymode_lint_ignore)) + call append('$', 'let pymode_lint_select = ' . string(g:pymode_lint_select)) + call append('$', 'let pymode_lint_onfly = ' . string(g:pymode_lint_onfly)) + call append('$', 'let pymode_lint_config = ' . string(g:pymode_lint_config)) + call append('$', 'let pymode_lint_write = ' . string(g:pymode_lint_write)) + call append('$', 'let pymode_lint_cwindow = ' . string(g:pymode_lint_cwindow)) + call append('$', 'let pymode_lint_message = ' . string(g:pymode_lint_message)) + call append('$', 'let pymode_lint_signs = ' . string(g:pymode_lint_signs)) + call append('$', 'let pymode_lint_jump = ' . string(g:pymode_lint_jump)) + call append('$', 'let pymode_lint_hold = ' . string(g:pymode_lint_hold)) + call append('$', 'let pymode_lint_minheight = ' . string(g:pymode_lint_minheight)) + call append('$', 'let pymode_lint_maxheight = ' . string(g:pymode_lint_maxheight)) + endif + + call append('$', 'let pymode_rope = ' . string(g:pymode_rope)) + call append('$', 'let pymode_folding = ' . string(g:pymode_folding)) + call append('$', 'let pymode_breakpoint = ' . string(g:pymode_breakpoint)) + call append('$', 'let pymode_syntax = ' . string(g:pymode_syntax)) + call append('$', 'let pymode_virtualenv = ' . string(g:pymode_virtualenv)) + if g:pymode_virtualenv + call append('$', 'let pymode_virtualenv_enabled = ' . string(g:pymode_virtualenv_enabled)) + endif + call append('$', 'pymode_utils_whitespaces:' . string(g:pymode_utils_whitespaces)) + call append('$', 'pymode_options:' . string(g:pymode_options)) endif if python diff --git a/doc/pymode.txt b/doc/pymode.txt index 776296e8..db62f026 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -92,8 +92,6 @@ PythonMode. These options should be set in your vimrc. |'pymode_breakpoint_key'| Key for breakpoint -|'pymode_utils'| Turns off utils - |'pymode_virtualenv'| Turns off virtualenv |'pymode_utils_whitespaces'| Remove unused whitespaces @@ -315,13 +313,6 @@ Default: 'b'. Key for setting/unsetting breakpoints. ------------------------------------------------------------------------------- - *'pymode_utils'* -Values: 0 or 1. -Default: 1. - -If this option is set to 0 the then utils script is disabled. - ------------------------------------------------------------------------------ *'pymode_virtualenv'* Values: 0 or 1. From 9a04eee292d182313a5201ce9b27897699c6b172 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 7 Sep 2012 13:34:25 +0400 Subject: [PATCH 118/513] Update autopep8 --- pylibs/autopep8.py | 62 ++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/pylibs/autopep8.py b/pylibs/autopep8.py index b0114bc5..dc11d770 100644 --- a/pylibs/autopep8.py +++ b/pylibs/autopep8.py @@ -362,23 +362,19 @@ def fix_e123(self, result, logical): """The 'loose fingernails' indentation level error for hanging indents.""" # fix by deleting whitespace to the correct level - modified_lines = self._fix_reindent(result, logical) - if modified_lines: - return modified_lines - else: - # Fallback - if not logical: - return [] - logical_lines = logical[2] - line_index = result['line'] - 1 - original_line = self.source[line_index] + if not logical: + return [] + logical_lines = logical[2] + line_index = result['line'] - 1 + original_line = self.source[line_index] - fixed_line = (_get_indentation(logical_lines[0]) + - original_line.lstrip()) - if fixed_line == original_line: - return [] - else: - self.source[line_index] = fixed_line + fixed_line = (_get_indentation(logical_lines[0]) + + original_line.lstrip()) + if fixed_line == original_line: + # Fallback to slower method. + return self._fix_reindent(result, logical) + else: + self.source[line_index] = fixed_line def fix_e124(self, result, logical): """The 'loose fingernails' indentation level error for visual @@ -401,33 +397,29 @@ def fix_e125(self, result, logical): def fix_e126(self, result, logical): """The 'spectacular indent' error for hanging indents.""" # fix by deleting whitespace to the left - modified_lines = self._fix_reindent(result, logical) - if modified_lines: - return modified_lines - else: - # Fallback - if not logical: - return [] - logical_lines = logical[2] - line_index = result['line'] - 1 - original = self.source[line_index] + if not logical: + return [] + logical_lines = logical[2] + line_index = result['line'] - 1 + original = self.source[line_index] - fixed = (_get_indentation(logical_lines[0]) + - self.indent_word + original.lstrip()) - if fixed == original: - return [] - else: - self.source[line_index] = fixed + fixed = (_get_indentation(logical_lines[0]) + + self.indent_word + original.lstrip()) + if fixed == original: + # Fallback to slower method. + return self._fix_reindent(result, logical) + else: + self.source[line_index] = fixed def fix_e127(self, result, logical): """The 'interpretive dance' indentation error.""" # Fix by inserting/deleting whitespace to the correct level. - modified_lines = self._fix_reindent(result, logical) + modified_lines = self._align_visual_indent(result, logical) if modified_lines: return modified_lines else: - # Fallback - return self._align_visual_indent(result, logical) + # Fallback to slower method. + return self._fix_reindent(result, logical) def _align_visual_indent(self, result, logical): """Correct visual indent. From b5196d9f569d3dabcc9a0b58128dd829e8754502 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 7 Sep 2012 14:14:21 +0400 Subject: [PATCH 119/513] Update FAQ --- Changelog.rst | 5 +++++ README.rst | 14 ++++++++++++++ doc/pymode.txt | 14 ++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/Changelog.rst b/Changelog.rst index a424087c..f85cbc0e 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2012-**-** 0.6.9 +------------------- +* Update autopep8 +* Improve pymode#troubleshooting#Test() + ## 2012-09-06 0.6.8 ------------------- * Add PEP8 indentation ":help 'pymode_indent'" diff --git a/README.rst b/README.rst index 57a150c0..28bff927 100644 --- a/README.rst +++ b/README.rst @@ -433,6 +433,20 @@ Try use pyflakes_, see ``:h 'pymode_lint_checker'``. Example: On Flask projects I automaticly set ``g:pymode_lint_checker = "pyflakes"``, on django ``g:pymode_lint_cheker = "pylint"`` +OSX cannot import urandom +------------------------- + +See: https://groups.google.com/forum/?fromgroups=#!topic/vim_dev/2NXKF6kDONo + +The sequence of commands that fixed this: :: + + brew unlink python + brew unlink macvim + brew remove macvim + brew install -v --force macvim + brew link macvim + brew link python + Bugtracker =========== diff --git a/doc/pymode.txt b/doc/pymode.txt index db62f026..85027b0a 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -481,6 +481,20 @@ from `.vimrc` from your projects directories. On Django 'g:pymode_lint_checker = "pylint"' < +OSX cannot import urandom +------------------------- + +See: https://groups.google.com/forum/?fromgroups=#!topic/vim_dev/2NXKF6kDONo + +The sequence of commands that fixed this: +> + brew unlink python + brew unlink macvim + brew remove macvim + brew install -v --force macvim + brew link macvim + brew link python +< ============================================================================== 6. Credits ~ From 9e7830f8eeb44d88195cfbb7c825ee7927e0703a Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 7 Sep 2012 14:15:14 +0400 Subject: [PATCH 120/513] Update changelog --- Changelog.rst | 2 +- doc/pymode.txt | 2 +- plugin/pymode.vim | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index f85cbc0e..1d817354 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -## 2012-**-** 0.6.9 +## 2012-09-07 0.6.9 ------------------- * Update autopep8 * Improve pymode#troubleshooting#Test() diff --git a/doc/pymode.txt b/doc/pymode.txt index 85027b0a..d5b59945 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.8 + Version: 0.6.9 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/plugin/pymode.vim b/plugin/pymode.vim index c8f800be..e99fd584 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.6.8" +let g:pymode_version = "0.6.9" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 3db9487abd0417ed0fd91077ee877449705d7a08 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Sat, 8 Sep 2012 18:36:00 +0100 Subject: [PATCH 121/513] Support sourcing `vim` files under `.ropeproject/` --- pylibs/ropevim.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index b54cd83e..f7417dff 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -1,4 +1,5 @@ """ropevim, a vim mode for using rope refactoring library""" +import glob import os import tempfile import re @@ -352,7 +353,7 @@ def update(self, percent): except vim.error: raise KeyboardInterrupt('Task %s was interrupted!' % self.name) if percent > self.last + 4: - status('%s ... %s%%%%' % (self.name, percent)) + status('%s ... %s%%' % (self.name, percent)) self.last = percent def done(self): @@ -398,10 +399,30 @@ def __call__(self, arg_lead, cmd_line, cursor_pos): vim.command('let s:completions = %s' % result) +class RopeMode(interface.RopeMode): + @decorators.global_command('o') + def open_project(self, root=None): + super(RopeMode, self).open_project(root=root) + rope_project_dir = os.path.join(self.project.address, '.ropeproject') + vimfiles = glob.glob(os.path.join(rope_project_dir, '*.vim')) + + if not vimfiles: + return + + txt = 'Sourcing vim files under \'.ropeproject/\'' + progress = self.env.create_progress(txt) + for idx, vimfile in enumerate(vimfiles): + progress.name = txt + ' ({0})'.format(os.path.basename(vimfile)) + vim.command(':so {0}'.format(vimfile)) + progress.update(idx * 100 / len(vimfiles)) + progress.name = txt + progress.done() + + decorators.logger.message = echo decorators.logger.only_short = True _completer = _ValueCompleter() _env = VimUtils() -_interface = interface.RopeMode(env=_env) +_interface = RopeMode(env=_env) From f13502169dc4daf3f9dee19ab5d6d492f809d482 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Sat, 8 Sep 2012 18:38:16 +0100 Subject: [PATCH 122/513] Source `vim` files properly sorted. --- pylibs/ropevim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index f7417dff..67b1b520 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -411,7 +411,7 @@ def open_project(self, root=None): txt = 'Sourcing vim files under \'.ropeproject/\'' progress = self.env.create_progress(txt) - for idx, vimfile in enumerate(vimfiles): + for idx, vimfile in enumerate(sorted(vimfiles)): progress.name = txt + ' ({0})'.format(os.path.basename(vimfile)) vim.command(':so {0}'.format(vimfile)) progress.update(idx * 100 / len(vimfiles)) From 681a18215a8d2ac6cf09d68c170188b205907b40 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Sat, 8 Sep 2012 18:53:46 +0100 Subject: [PATCH 123/513] Use python's string `.format()` instead of string substitution. --- pylibs/ropevim.py | 74 ++++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 67b1b520..0eca06eb 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -22,8 +22,8 @@ def ask(self, prompt, default=None, starting=None): if starting is None: starting = '' if default is not None: - prompt = prompt + ('[%s] ' % default) - result = call('input("%s", "%s")' % (prompt, starting)) + prompt = prompt + '[{0}] '.format(default) + result = call('input("{0}", "{1}")'.format(prompt, starting)) if default is not None and result == '': return default return result @@ -33,11 +33,14 @@ def ask_values(self, prompt, values, default=None, if show_values or (show_values is None and len(values) < 14): self._print_values(values) if default is not None: - prompt = prompt + ('[%s] ' % default) + prompt = prompt + '[{0}] '.format(default) starting = starting or '' _completer.values = values - answer = call('input("%s", "%s", "customlist,RopeValueCompleter")' % - (prompt, starting)) + answer = call( + 'input("{0}", "{1}", "customlist,RopeValueCompleter")'.format( + prompt, starting + ) + ) if answer is None: if 'cancel' in values: return 'cancel' @@ -55,14 +58,14 @@ def _print_values(self, values): echo('\n'.join(numbered) + '\n') def ask_directory(self, prompt, default=None, starting=None): - return call('input("%s", ".", "dir")' % prompt) + return call('input("{0}", ".", "dir")'.format(prompt)) def _update_proposals(self, values): self.completeopt = vim.eval('&completeopt') self.preview = 'preview' in self.completeopt if not self.get('extended_complete'): - return u','.join(u"'%s'" % self._completion_text(proposal) + return u','.join(u"'{0}'".format(self._completion_text(proposal)) for proposal in values) return u','.join(self._extended_completion(proposal) @@ -79,7 +82,7 @@ def ask_completion(self, prompt, values, starting=None): col = int(call('col(".")')) if starting: col -= len(starting) - self._command(u'call complete(%s, [%s])' % (col, proposals), + self._command(u'call complete({0}, [{1}])'.format(col, proposals), encode=True) return None @@ -96,8 +99,8 @@ def y_or_n(self, prompt): return self.yes_or_no(prompt) def get(self, name, default=None): - vimname = 'g:pymode_rope_%s' % name - if str(vim.eval('exists("%s")' % vimname)) == '0': + vimname = 'g:pymode_rope_{0}'.format(name) + if str(vim.eval('exists("{0}")'.format(vimname))) == '0': return default result = vim.eval(vimname) if isinstance(result, str) and result.isdigit(): @@ -262,8 +265,9 @@ def _writedefs(self, locations, filename): def show_doc(self, docs, altview=False): if docs: - cmd = 'call pymode#ShowStr("%s")' % str(docs.replace('"', '\\"')) - vim.command(cmd) + vim.command( + 'call pymode#ShowStr("{0}")'.format(docs.replace('"', '\\"')) + ) def preview_changes(self, diffs): echo(diffs) @@ -282,23 +286,33 @@ def add_hook(self, name, callback, hook): 'after_save': 'FileWritePost,BufWritePost', 'exit': 'VimLeave'} self._add_function(name, callback) - vim.command('autocmd %s *.py call %s()' % - (mapping[hook], _vim_name(name))) + vim.command( + 'autocmd {0} *.py call {1}()'.format( + mapping[hook], _vim_name(name) + ) + ) def _add_command(self, name, callback, key, prefix, prekey): self._add_function(name, callback, prefix) - vim.command('command! -range %s call %s()' % - (_vim_name(name), _vim_name(name))) + vim.command( + 'command! -range {0} call {1}()'.format( + _vim_name(name), _vim_name(name) + ) + ) if key is not None: key = prekey + key.replace(' ', '') - vim.command('noremap %s :call %s()' % (key, _vim_name(name))) + vim.command( + 'noremap {0} :call {1}()'.format(key, _vim_name(name)) + ) def _add_function(self, name, callback, prefix=False): globals()[name] = callback arg = 'None' if prefix else '' - vim.command('function! %s()\n' % _vim_name(name) + - 'python ropevim.%s(%s)\n' % (name, arg) + - 'endfunction\n') + vim.command( + 'function! {0}()\n' + 'python ropevim.{1}({2})\n' + 'endfunction\n'.format(_vim_name(name), name, arg) + ) def _completion_data(self, proposal): return proposal @@ -318,7 +332,7 @@ def _extended_completion(self, proposal): if proposal.scope == 'parameter_keyword': default = proposal.get_default() - ci["menu"] += '*' if default is None else '= %s' % default + ci["menu"] += '*' if default is None else '= {0}'.format(default) if self.preview and not ci['menu']: doc = proposal.get_doc() @@ -329,9 +343,9 @@ def _extended_completion(self, proposal): def _conv(self, obj): if isinstance(obj, dict): return u'{' + u','.join([ - u"%s:%s" % (self._conv(key), self._conv(value)) + u"{0}:{1}".format(self._conv(key), self._conv(value)) for key, value in obj.iteritems()]) + u'}' - return u'"%s"' % str(obj).replace(u'"', u'\\"') + return u'"{0}"'.format(str(obj).replace(u'"', u'\\"')) def _vim_name(name): @@ -345,19 +359,21 @@ class VimProgress(object): def __init__(self, name): self.name = name self.last = 0 - status('%s ... ' % self.name) + status('{0} ... '.format(self.name)) def update(self, percent): try: vim.eval('getchar(0)') except vim.error: - raise KeyboardInterrupt('Task %s was interrupted!' % self.name) + raise KeyboardInterrupt( + 'Task {0} was interrupted!'.format(self.name) + ) if percent > self.last + 4: - status('%s ... %s%%' % (self.name, percent)) + status('{0} ... {1}%'.format(self.name, percent)) self.last = percent def done(self): - status('%s ... done' % self.name) + status('{0} ... done'.format(self.name)) def echo(message): @@ -369,7 +385,7 @@ def echo(message): def status(message): if isinstance(message, unicode): message = message.encode(vim.eval('&encoding')) - vim.command('redraw | echon "%s"' % message) + vim.command('redraw | echon "{0}"'.format(message)) def call(command): @@ -396,7 +412,7 @@ def __call__(self, arg_lead, cmd_line, cursor_pos): else: result = [proposal for proposal in self.values if proposal.startswith(arg_lead)] - vim.command('let s:completions = %s' % result) + vim.command('let s:completions = {0}'.format(result)) class RopeMode(interface.RopeMode): From 5ae1df93178a5fa23918e8b3c8ee25fb00c4bd18 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Sun, 9 Sep 2012 14:09:51 +0100 Subject: [PATCH 124/513] Auto-open existing projects. When Vim loads, if, on the current directory, there's a `.ropeproject` sub-directory and both rope and `g:pymode_rope_auto_project_open` are enabled, the project is automatically opened. --- plugin/pymode.vim | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index e99fd584..7348a09f 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -175,9 +175,14 @@ endif if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope - " OPTION: g:pymode_rope_auto_project -- bool. Auto open ropeproject + " OPTION: g:pymode_rope_auto_project -- bool. Auto create ropeproject call pymode#Default("g:pymode_rope_auto_project", 1) + " OPTION: g:pymode_rope_auto_project_open -- bool. + " Auto open existing projects, ie, if the current directory has a + " `.ropeproject` subdirectory. + call pymode#Default("g:pymode_rope_auto_project_open", 1) + " OPTION: g:pymode_rope_enable_autoimport -- bool. Enable autoimport call pymode#Default("g:pymode_rope_enable_autoimport", 1) @@ -234,6 +239,13 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope return "" endfunction "}}} + fun! RopeOpenExistingProject() "{{{ + if isdirectory('./.ropeproject') + call RopeOpenProject() + return "" + endif + endfunction "}}} + fun! RopeLuckyAssistInsertMode() "{{{ call RopeLuckyAssist() return "" @@ -270,6 +282,12 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope menu Rope.Undo :RopeUndo menu Rope.UseFunction :RopeUseFunction + " Hooks + if !pymode#Default("g:pymode_rope_auto_project_open", 1) || g:pymode_rope_auto_project_open + autocmd VimEnter * call RopeOpenExistingProject() + call windo e + endif + endif " }}} From 7a531aa1bf2839e112b83a411483b79e6fbf233e Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Sun, 9 Sep 2012 17:26:40 +0100 Subject: [PATCH 125/513] Allow always showing the signs vertical ruler. --- autoload/pymode.vim | 6 ++++++ plugin/pymode.vim | 14 +++++++++++++- pylibs/ropevim.py | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index a7bb8af3..230dc53c 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -55,6 +55,12 @@ fun! pymode#PlaceSigns() "{{{ " if has('signs') sign unplace * + + if !pymode#Default("g:pymode_lint_signs_always_visible", 0) || g:pymode_lint_signs_always_visible + " Show the sign's ruller if asked for, even it there's no error to show + execute printf('silent! sign place 1 line=1 name=__dummy__ file=%s', expand("%:p")) + endif + for item in filter(getqflist(), 'v:val.bufnr != ""') execute printf('silent! sign place 1 line=%d name=%s buffer=%d', item.lnum, item.type, item.bufnr) endfor diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 7348a09f..3eb1d2a2 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -99,6 +99,10 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " OPTION: g:pymode_lint_mccabe_complexity -- int. Maximum allowed complexity call pymode#Default("g:pymode_lint_mccabe_complexity", 8) + " OPTION: g:pymode_lint_signs_always_visible -- bool. Always show the + " errors ruller, even if there's no errors. + call pymode#Default("g:pymode_lint_signs_always_visible", 0) + " OPTION: g:pymode_lint_signs -- bool. Place error signs if (!pymode#Default("g:pymode_lint_signs", 1) || g:pymode_lint_signs) && has('signs') @@ -109,6 +113,12 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint sign define E text=EE texthl=Error sign define I text=II texthl=Info + if !pymode#Default("g:pymode_lint_signs_always_visible", 0) || g:pymode_lint_signs_always_visible + " Show the sign's ruller if asked for, even it there's no error to show + sign define __dummy__ + autocmd BufRead,BufNew * execute printf('silent! sign place 1 line=1 name=__dummy__ file=%s', expand("%:p")) + endif + endif " DESC: Set default pylint configuration @@ -242,6 +252,9 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope fun! RopeOpenExistingProject() "{{{ if isdirectory('./.ropeproject') call RopeOpenProject() + " Reload current buffer + "silent edit! + " Does not work, looses syntax!!! argg return "" endif endfunction "}}} @@ -285,7 +298,6 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope " Hooks if !pymode#Default("g:pymode_rope_auto_project_open", 1) || g:pymode_rope_auto_project_open autocmd VimEnter * call RopeOpenExistingProject() - call windo e endif endif diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 0eca06eb..0b526886 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -429,11 +429,11 @@ def open_project(self, root=None): progress = self.env.create_progress(txt) for idx, vimfile in enumerate(sorted(vimfiles)): progress.name = txt + ' ({0})'.format(os.path.basename(vimfile)) - vim.command(':so {0}'.format(vimfile)) + vim.command(':silent source {0}'.format(vimfile)) progress.update(idx * 100 / len(vimfiles)) progress.name = txt progress.done() - + echo('Project opened!') decorators.logger.message = echo decorators.logger.only_short = True From 9d2401b52298aedaaf9578d428a320395805a68f Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Tue, 11 Sep 2012 22:34:18 +0100 Subject: [PATCH 126/513] Load existing projects when the plugin is also being loaded. This will fix the current buffer not having any sourced settings applied. --- plugin/pymode.vim | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 3eb1d2a2..909df931 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -251,10 +251,7 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope fun! RopeOpenExistingProject() "{{{ if isdirectory('./.ropeproject') - call RopeOpenProject() - " Reload current buffer - "silent edit! - " Does not work, looses syntax!!! argg + :silent call RopeOpenProject() return "" endif endfunction "}}} @@ -297,7 +294,7 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope " Hooks if !pymode#Default("g:pymode_rope_auto_project_open", 1) || g:pymode_rope_auto_project_open - autocmd VimEnter * call RopeOpenExistingProject() + call RopeOpenExistingProject() endif endif From 6a9d70fadfe7b599f90aa1cafda3a47f582e977f Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Thu, 13 Sep 2012 00:22:34 +0100 Subject: [PATCH 127/513] Added the ability to actually quiet the project loading process. Added auto session management, which will probably be removed and use `session.vim` if installed. --- plugin/pymode.vim | 36 +++++++++++++++++++++++++++++++++--- pylibs/ropevim.py | 14 +++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 909df931..07c39453 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -193,6 +193,9 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope " `.ropeproject` subdirectory. call pymode#Default("g:pymode_rope_auto_project_open", 1) + " OPTION: g:pymode_rope_auto_session_manage -- bool + call pymode#Default("g:pymode_rope_auto_session_manage", 0) + " OPTION: g:pymode_rope_enable_autoimport -- bool. Enable autoimport call pymode#Default("g:pymode_rope_enable_autoimport", 1) @@ -250,12 +253,33 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope endfunction "}}} fun! RopeOpenExistingProject() "{{{ - if isdirectory('./.ropeproject') - :silent call RopeOpenProject() + if isdirectory(getcwd() . '/.ropeproject') + " In order to pass it the quiet kwarg I need to open the project + " using python and not vim, which should be no major issue + py ropevim._interface.open_project(quiet=True) return "" endif endfunction "}}} + fun! RopeOpenSession() "{{{ + if filereadable(getcwd() . '/.ropeproject/.session.vim') + execute 'source ' . getcwd() . '/.ropeproject/.session.vim' + if bufexists(1) + for l in range(1, bufnr('$')) + if bufwinnr(l) == -1 + execute 'sbuffer ' . l + endif + endfor + endif + endif + endfunction "}}} + + fun! RopeSaveSession() "{{{ + if isdirectory(getcwd() . '/.ropeproject') + execute 'mksession! ' . getcwd() . '/.ropeproject/.session.vim' + endif + endfunction "}}} + fun! RopeLuckyAssistInsertMode() "{{{ call RopeLuckyAssist() return "" @@ -291,12 +315,18 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope menu Rope.Restructure :RopeRestructure menu Rope.Undo :RopeUndo menu Rope.UseFunction :RopeUseFunction + menu Rope.OpenSession :call RopeOpenSession() + menu Rope.SaveSession :call RopeSaveSession() - " Hooks if !pymode#Default("g:pymode_rope_auto_project_open", 1) || g:pymode_rope_auto_project_open call RopeOpenExistingProject() endif + if !pymode#Default("g:pymode_rope_auto_session_manage", 0) || g:pymode_rope_auto_session_manage + autocmd VimLeave * call RopeSaveSession() + autocmd VimEnter * call RopeRestoreSession() + endif + endif " }}} diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 0b526886..b2d150c4 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -10,6 +10,9 @@ import vim +# Gobal var to be able to shutup output +_rope_quiet = False + class VimUtils(environment.Environment): @@ -377,12 +380,17 @@ def done(self): def echo(message): + if _rope_quiet: + return if isinstance(message, unicode): message = message.encode(vim.eval('&encoding')) print message def status(message): + if _rope_quiet: + return + if isinstance(message, unicode): message = message.encode(vim.eval('&encoding')) vim.command('redraw | echon "{0}"'.format(message)) @@ -417,7 +425,10 @@ def __call__(self, arg_lead, cmd_line, cursor_pos): class RopeMode(interface.RopeMode): @decorators.global_command('o') - def open_project(self, root=None): + def open_project(self, root=None, quiet=False): + global _rope_quiet + _rope_quiet = quiet + super(RopeMode, self).open_project(root=root) rope_project_dir = os.path.join(self.project.address, '.ropeproject') vimfiles = glob.glob(os.path.join(rope_project_dir, '*.vim')) @@ -431,6 +442,7 @@ def open_project(self, root=None): progress.name = txt + ' ({0})'.format(os.path.basename(vimfile)) vim.command(':silent source {0}'.format(vimfile)) progress.update(idx * 100 / len(vimfiles)) + progress.name = txt progress.done() echo('Project opened!') From d83b9e0fbe330e0c50d9c901773c9f10399c2096 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Thu, 13 Sep 2012 00:24:13 +0100 Subject: [PATCH 128/513] Removed the auto session support. --- plugin/pymode.vim | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 07c39453..35740400 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -261,25 +261,6 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope endif endfunction "}}} - fun! RopeOpenSession() "{{{ - if filereadable(getcwd() . '/.ropeproject/.session.vim') - execute 'source ' . getcwd() . '/.ropeproject/.session.vim' - if bufexists(1) - for l in range(1, bufnr('$')) - if bufwinnr(l) == -1 - execute 'sbuffer ' . l - endif - endfor - endif - endif - endfunction "}}} - - fun! RopeSaveSession() "{{{ - if isdirectory(getcwd() . '/.ropeproject') - execute 'mksession! ' . getcwd() . '/.ropeproject/.session.vim' - endif - endfunction "}}} - fun! RopeLuckyAssistInsertMode() "{{{ call RopeLuckyAssist() return "" @@ -315,8 +296,6 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope menu Rope.Restructure :RopeRestructure menu Rope.Undo :RopeUndo menu Rope.UseFunction :RopeUseFunction - menu Rope.OpenSession :call RopeOpenSession() - menu Rope.SaveSession :call RopeSaveSession() if !pymode#Default("g:pymode_rope_auto_project_open", 1) || g:pymode_rope_auto_project_open call RopeOpenExistingProject() From 6b609124de60f55a1d4a002e1f0e760367f64d30 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Thu, 13 Sep 2012 17:31:46 +0100 Subject: [PATCH 129/513] The errors even if forced is only shown on python files. --- autoload/pymode.vim | 4 ++-- plugin/pymode.vim | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index 230dc53c..91e975dc 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -57,13 +57,13 @@ fun! pymode#PlaceSigns() "{{{ sign unplace * if !pymode#Default("g:pymode_lint_signs_always_visible", 0) || g:pymode_lint_signs_always_visible - " Show the sign's ruller if asked for, even it there's no error to show - execute printf('silent! sign place 1 line=1 name=__dummy__ file=%s', expand("%:p")) + call RopeShowSignsRulerIfNeeded() endif for item in filter(getqflist(), 'v:val.bufnr != ""') execute printf('silent! sign place 1 line=%d name=%s buffer=%d', item.lnum, item.type, item.bufnr) endfor + endif endfunction "}}} diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 35740400..2f5f968a 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -116,7 +116,7 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint if !pymode#Default("g:pymode_lint_signs_always_visible", 0) || g:pymode_lint_signs_always_visible " Show the sign's ruller if asked for, even it there's no error to show sign define __dummy__ - autocmd BufRead,BufNew * execute printf('silent! sign place 1 line=1 name=__dummy__ file=%s', expand("%:p")) + autocmd BufRead,BufNew * call RopeShowSignsRulerIfNeeded() endif endif @@ -276,6 +276,13 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope endif endfunction "}}} + fun! RopeShowSignsRulerIfNeeded() "{{{ + if &ft == 'python' + execute printf('silent! sign place 1 line=1 name=__dummy__ file=%s', expand("%:p")) + endif + endfunction "}}} + + " Rope menu menu Rope.Autoimport :RopeAutoImport menu Rope.ChangeSignature :RopeChangeSignature From fefcb7826819c53fa8a61791a71bb29a7540c1d7 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Sat, 13 Oct 2012 20:54:00 -0600 Subject: [PATCH 130/513] Improve performance of white space removal On large'ish python files, this was taking 10+ seconds at save time. Now it runs instantly, even on very large python files. Cursor position does not move. --- ftplugin/python/pymode.vim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 713bafed..8ddf16d4 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -133,7 +133,12 @@ endif " Utils {{{ if pymode#Option('utils_whitespaces') - au BufWritePre :call setline(1,map(getline(1,"$"),'substitute(v:val,"\\s\\+$","","")')) + function PyModeTrimEndWhiteSpace() + let cursor_pos = getpos('.') + %s/\s\+$// + call setpos('.', cursor_pos) + endfunction + au BufWritePre call PyModeTrimEndWhiteSpace() endif " }}} From 74a6e653b3b8f39f4e13f7422e624638a7042633 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Sat, 13 Oct 2012 21:55:29 -0600 Subject: [PATCH 131/513] Suppres error output if there is now whitespace to remove --- ftplugin/python/pymode.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 8ddf16d4..ac77f1eb 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -135,7 +135,7 @@ endif if pymode#Option('utils_whitespaces') function PyModeTrimEndWhiteSpace() let cursor_pos = getpos('.') - %s/\s\+$// + :silent! %s/\s\+$// call setpos('.', cursor_pos) endfunction au BufWritePre call PyModeTrimEndWhiteSpace() From 251ce00e357163a9917e215ea99cecc224e7a24a Mon Sep 17 00:00:00 2001 From: nixon Date: Sun, 21 Oct 2012 21:58:01 -0500 Subject: [PATCH 132/513] dont raise an exception when Logger has no message handler --- pylibs/ropemode/decorators.py | 2 +- pylibs/ropemode/tests/__init__.py | 0 pylibs/ropemode/tests/decorators_test.py | 21 +++++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 pylibs/ropemode/tests/__init__.py create mode 100644 pylibs/ropemode/tests/decorators_test.py diff --git a/pylibs/ropemode/decorators.py b/pylibs/ropemode/decorators.py index 21eaaf0c..6a610728 100644 --- a/pylibs/ropemode/decorators.py +++ b/pylibs/ropemode/decorators.py @@ -15,7 +15,7 @@ def __call__(self, message, short=None): self._show(short) def _show(self, message): - if message is None: + if self.message is None: print message else: self.message(message) diff --git a/pylibs/ropemode/tests/__init__.py b/pylibs/ropemode/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pylibs/ropemode/tests/decorators_test.py b/pylibs/ropemode/tests/decorators_test.py new file mode 100644 index 00000000..503de313 --- /dev/null +++ b/pylibs/ropemode/tests/decorators_test.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +import os +import sys +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) + +import unittest + +from ropemode.decorators import Logger + + +class LoggerTests(unittest.TestCase): + def test_Logger_called_with_no_args_doesnt_raise_TypeError(self): + """ + When not initialized with a message display method, Logger + prints the message to stdout without raising an exception. + """ + logger = Logger() + try: + logger("a message") + except TypeError: + self.fail("logger raised TypeError unexpectedly") From 7cd9ed15a0bb6fe4475b9d0012afe60e1148534d Mon Sep 17 00:00:00 2001 From: nixon Date: Sun, 21 Oct 2012 22:42:25 -0500 Subject: [PATCH 133/513] Logger message handler tests --- pylibs/ropemode/tests/decorators_test.py | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pylibs/ropemode/tests/decorators_test.py b/pylibs/ropemode/tests/decorators_test.py index 503de313..40a6be6e 100644 --- a/pylibs/ropemode/tests/decorators_test.py +++ b/pylibs/ropemode/tests/decorators_test.py @@ -19,3 +19,30 @@ def test_Logger_called_with_no_args_doesnt_raise_TypeError(self): logger("a message") except TypeError: self.fail("logger raised TypeError unexpectedly") + + +class LoggerMessageHandlerTests(unittest.TestCase): + def setUp(self): + self.message = "" + self.logger = Logger() + self.logger.message = self._echo + + def _echo(self, message): + self.message += message + + def test_message_handler_with_no_short_message(self): + """Test that message handler is called""" + self.logger("a message") + self.assertEqual(self.message, "a message") + + def test_only_short_True(self): + """Test that only_short=True prints only the short message""" + self.logger.only_short = True + self.logger("a long message", "a short message") + self.assertEqual(self.message, "a short message") + + def test_only_short_False(self): + """Test that only_short=False prints both messages""" + self.logger.only_short = False + self.logger("a long message", "a short message") + self.assertEqual(self.message, "a long messagea short message") From fce8b4f537b275c5364ac43bb73e2b0b7e6c51c5 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 22 Oct 2012 12:39:21 +0400 Subject: [PATCH 134/513] Update changelog. --- Changelog.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changelog.rst b/Changelog.rst index 1d817354..245ad619 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2012-09-07 0.6.10 +-------------------- +* Dont raise an exception when Logger has no message handler (c) nixon +* Improve performance of white space removal (c) Dave Smith + ## 2012-09-07 0.6.9 ------------------- * Update autopep8 From 06eb9e6a642943ce2e5beaa9d419471823f6c980 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 22 Oct 2012 13:01:25 +0400 Subject: [PATCH 135/513] Add g:pymode_updatetime. --- Changelog.rst | 1 + ftplugin/python/pymode.vim | 2 +- plugin/pymode.vim | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index 245ad619..684944cd 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -5,6 +5,7 @@ Changelog -------------------- * Dont raise an exception when Logger has no message handler (c) nixon * Improve performance of white space removal (c) Dave Smith +* Improve ropemode support (c) s0undt3ch ## 2012-09-07 0.6.9 ------------------- diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index ac77f1eb..5c6691d4 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -71,7 +71,7 @@ if pymode#Option('lint') endif " DESC: Run queue - setlocal updatetime=1000 + let &l:updatetime = g:pymode_updatetime au CursorHold call pymode#queue#Poll() au BufLeave py queue.stop_queue() diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 2f5f968a..4c4e5803 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -333,4 +333,7 @@ call pymode#Default("g:pymode_utils_whitespaces", 1) " OPTION: g:pymode_options -- bool. To set some python options. call pymode#Default("g:pymode_options", 1) +" OPTION: g:pymode_updatetime -- int. Set updatetime for async pymode's operation +call pymode#Default("g:pymode_updatetime", 1000) + " vim: fdm=marker:fdl=0 From 9db9857e329d5aa866f6476b5b2224a458e006b7 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 22 Oct 2012 13:53:04 +0400 Subject: [PATCH 136/513] Update autopep8 --- Changelog.rst | 2 + autoload/pymode.vim | 7 + ftplugin/python/pymode.vim | 7 +- pylibs/autopep8.py | 398 +++++++++++++++++++++++++------------ pylibs/pymode/auto.py | 6 +- 5 files changed, 286 insertions(+), 134 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 684944cd..7b2739f1 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -6,6 +6,8 @@ Changelog * Dont raise an exception when Logger has no message handler (c) nixon * Improve performance of white space removal (c) Dave Smith * Improve ropemode support (c) s0undt3ch +* Add `g:pymode_updatetime` option +* Update autopep8 to version 0.8.1 ## 2012-09-07 0.6.9 ------------------- diff --git a/autoload/pymode.vim b/autoload/pymode.vim index 91e975dc..12e2baba 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -180,4 +180,11 @@ fun! pymode#Modeline() "{{{ endfunction "}}} +fun! pymode#TrimWhiteSpace() "{{{ + let cursor_pos = getpos('.') + silent! %s/\s\+$// + call setpos('.', cursor_pos) +endfunction "}}} + + " vim: fdm=marker:fdl=0 diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 5c6691d4..854e51d0 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -133,12 +133,7 @@ endif " Utils {{{ if pymode#Option('utils_whitespaces') - function PyModeTrimEndWhiteSpace() - let cursor_pos = getpos('.') - :silent! %s/\s\+$// - call setpos('.', cursor_pos) - endfunction - au BufWritePre call PyModeTrimEndWhiteSpace() + au BufWritePre call pymode#TrimWhiteSpace() endif " }}} diff --git a/pylibs/autopep8.py b/pylibs/autopep8.py index dc11d770..fe7e75c3 100644 --- a/pylibs/autopep8.py +++ b/pylibs/autopep8.py @@ -22,17 +22,19 @@ A tool that automatically formats Python code to conform to the PEP 8 style guide. """ +from __future__ import print_function + import copy import os +import re import sys import inspect +import codecs +import locale try: - from cStringIO import StringIO + from StringIO import StringIO except ImportError: - try: - from StringIO import StringIO - except ImportError: - from io import StringIO + from io import StringIO import token import tokenize from optparse import OptionParser @@ -49,11 +51,10 @@ pep8 = None -__version__ = '0.8' +__version__ = '0.8.1' PEP8_BIN = 'pep8' -PEP8_PASSES_MAX = 100 CR = '\r' LF = '\n' CRLF = '\r\n' @@ -66,7 +67,8 @@ def open_with_encoding(filename, encoding, mode='r'): # Python 3 return open(filename, mode=mode, encoding=encoding) except TypeError: - return open(filename, mode=mode) + # Python 2 + return codecs.open(filename, mode=mode, encoding=encoding) def detect_encoding(filename): @@ -86,7 +88,16 @@ def detect_encoding(filename): except (SyntaxError, LookupError, UnicodeDecodeError): return 'latin-1' except AttributeError: - return 'utf-8' + # Python 2 + encoding = 'utf-8' + try: + # Check for correctness of encoding + with open_with_encoding(filename, encoding) as input_file: + input_file.read() + except UnicodeDecodeError: + encoding = 'latin-1' + + return encoding def read_from_filename(filename, readlines=False): @@ -146,7 +157,7 @@ def __init__(self, filename, options, contents=None): self.original_source = copy.copy(self.source) self.newline = find_newline(self.source) self.options = options - self.indent_word = _get_indentword("".join(self.source)) + self.indent_word = _get_indentword(''.join(self.source)) self.logical_start = None self.logical_end = None # method definition @@ -172,7 +183,7 @@ def _fix_source(self, results): if result['line'] in completed_lines: continue - fixed_methodname = "fix_%s" % result['id'].lower() + fixed_methodname = 'fix_%s' % result['id'].lower() if hasattr(self, fixed_methodname): fix = getattr(self, fixed_methodname) @@ -193,20 +204,23 @@ def _fix_source(self, results): if modified_lines: completed_lines.update(modified_lines) elif modified_lines == []: # Empty list means no fix - if self.options.verbose: - sys.stderr.write('Not fixing {f} on line {l}\n'.format( - f=result['id'], l=result['line'])) + if self.options.verbose >= 2: + print( + 'Not fixing {f} on line {l}'.format( + f=result['id'], l=result['line']), + file=sys.stderr) else: # We assume one-line fix when None completed_lines.add(result['line']) else: - if self.options.verbose: - sys.stderr.write("'%s' is not defined.\n" % - fixed_methodname) + if self.options.verbose >= 3: + print("'%s' is not defined." % fixed_methodname, + file=sys.stderr) info = result['info'].strip() - sys.stderr.write("%s:%s:%s:%s\n" % (self.filename, - result['line'], - result['column'], - info)) + print('%s:%s:%s:%s' % (self.filename, + result['line'], + result['column'], + info), + file=sys.stderr) def fix(self): """Return a version of the source code with PEP 8 violations fixed.""" @@ -220,23 +234,30 @@ def fix(self): results = _execute_pep8(pep8_options, self.source) else: if self.options.verbose: - sys.stderr.write('Running in compatibility mode. Consider ' - 'upgrading to the latest pep8.\n') - results = _spawn_pep8((["--ignore=" + self.options.ignore] + print('Running in compatibility mode. Consider ' + 'upgrading to the latest pep8.', + file=sys.stderr) + results = _spawn_pep8((['--ignore=' + self.options.ignore] if self.options.ignore else []) + - (["--select=" + self.options.select] + (['--select=' + self.options.select] if self.options.select else []) + [self.filename]) - self._fix_source(results) - return "".join(self.source) + + if self.options.verbose: + print('{n} issues to fix'.format( + n=len(results)), file=sys.stderr) + + self._fix_source(filter_results(source=''.join(self.source), + results=results)) + return ''.join(self.source) def fix_e101(self, _): """Reindent all lines.""" - reindenter = Reindenter(self.source) - if reindenter.run(): - original_length = len(self.source) + reindenter = Reindenter(self.source, self.newline) + modified_line_numbers = reindenter.run() + if modified_line_numbers: self.source = reindenter.fixed_lines() - return range(1, 1 + original_length) + return modified_line_numbers else: return [] @@ -247,7 +268,7 @@ def find_logical(self, force=False): logical_start = [] logical_end = [] last_newline = True - sio = StringIO("".join(self.source)) + sio = StringIO(''.join(self.source)) parens = 0 for t in tokenize.generate_tokens(sio.readline): if t[0] in [tokenize.COMMENT, tokenize.DEDENT, @@ -313,9 +334,9 @@ def _fix_reindent(self, result, logical, fix_distinct=False): valid_indents = rewrapper.pep8_expected() if not rewrapper.rel_indent: return [] - if result["line"] > ls[0]: + if result['line'] > ls[0]: # got a valid continuation line number from pep8 - row = result["line"] - ls[0] - 1 + row = result['line'] - ls[0] - 1 # always pick the first option for this valid = valid_indents[row] got = rewrapper.rel_indent[row] @@ -349,19 +370,18 @@ def _fix_reindent(self, result, logical, fix_distinct=False): return [] def fix_e121(self, result, logical): - """The 'peculiar indent' error for hanging indents.""" - # fix by adjusting initial indent level + """Fix indentation to be a multiple of four.""" + # Fix by adjusting initial indent level. return self._fix_reindent(result, logical) def fix_e122(self, result, logical): - """The 'absent indent' error for hanging indents.""" - # fix by adding an initial indent + """Add absent indentation for hanging indentation.""" + # Fix by adding an initial indent. return self._fix_reindent(result, logical) def fix_e123(self, result, logical): - """The 'loose fingernails' indentation level error for hanging - indents.""" - # fix by deleting whitespace to the correct level + """Align closing bracket to match opening bracket.""" + # Fix by deleting whitespace to the correct level. if not logical: return [] logical_lines = logical[2] @@ -371,20 +391,19 @@ def fix_e123(self, result, logical): fixed_line = (_get_indentation(logical_lines[0]) + original_line.lstrip()) if fixed_line == original_line: - # Fallback to slower method. + # Fall back to slower method. return self._fix_reindent(result, logical) else: self.source[line_index] = fixed_line def fix_e124(self, result, logical): - """The 'loose fingernails' indentation level error for visual - indents.""" - # fix by inserting whitespace before the closing bracket + """Align closing bracket to match visual indentation.""" + # Fix by inserting whitespace before the closing bracket. return self._fix_reindent(result, logical) def fix_e125(self, result, logical): - """The 'often not visually distinct' error.""" - # fix by indenting the line in error to the next stop. + """Indent to distinguish line from next logical line.""" + # Fix by indenting the line in error to the next stop. modified_lines = self._fix_reindent(result, logical, fix_distinct=True) if modified_lines: return modified_lines @@ -395,7 +414,7 @@ def fix_e125(self, result, logical): self.source[line_index] = self.indent_word + original_line def fix_e126(self, result, logical): - """The 'spectacular indent' error for hanging indents.""" + """Fix over-indented hanging indentation.""" # fix by deleting whitespace to the left if not logical: return [] @@ -412,7 +431,7 @@ def fix_e126(self, result, logical): self.source[line_index] = fixed def fix_e127(self, result, logical): - """The 'interpretive dance' indentation error.""" + """Fix visual indentation.""" # Fix by inserting/deleting whitespace to the correct level. modified_lines = self._align_visual_indent(result, logical) if modified_lines: @@ -448,6 +467,7 @@ def _align_visual_indent(self, result, logical): self.source[line_index] = fixed def fix_e201(self, result): + """Remove extraneous whitespace.""" line_index = result['line'] - 1 target = self.source[line_index] offset = result['column'] - 1 @@ -468,13 +488,14 @@ def fix_e201(self, result): self.source[line_index] = fixed def fix_e224(self, result): + """Remove extraneous whitespace around operator.""" target = self.source[result['line'] - 1] offset = result['column'] - 1 fixed = target[:offset] + target[offset:].replace('\t', ' ') self.source[result['line'] - 1] = fixed def fix_e225(self, result): - """Fix whitespace around operator.""" + """Fix missing whitespace around operator.""" target = self.source[result['line'] - 1] offset = result['column'] - 1 fixed = target[:offset] + ' ' + target[offset:] @@ -496,6 +517,7 @@ def fix_e231(self, result): self.source[line_index] = fixed def fix_e251(self, result): + """Remove whitespace around parameter '=' sign.""" line_index = result['line'] - 1 target = self.source[line_index] @@ -553,21 +575,19 @@ def fix_e271(self, result): self.source[line_index] = fixed def fix_e301(self, result): + """Add missing blank line.""" cr = self.newline self.source[result['line'] - 1] = cr + self.source[result['line'] - 1] def fix_e302(self, result): + """Add missing 2 blank lines.""" add_linenum = 2 - int(result['info'].split()[-1]) cr = self.newline * add_linenum self.source[result['line'] - 1] = cr + self.source[result['line'] - 1] - def fix_e304(self, result): - line = result['line'] - 2 - if not self.source[line].strip(): - self.source[line] = '' - def fix_e303(self, result): - delete_linenum = int(result['info'].split("(")[1].split(")")[0]) - 2 + """Remove extra blank lines.""" + delete_linenum = int(result['info'].split('(')[1].split(')')[0]) - 2 delete_linenum = max(1, delete_linenum) # We need to count because pep8 reports an offset line number if there @@ -586,7 +606,14 @@ def fix_e303(self, result): return modified_lines + def fix_e304(self, result): + """Remove blank line following function decorator.""" + line = result['line'] - 2 + if not self.source[line].strip(): + self.source[line] = '' + def fix_e401(self, result): + """Put imports on separate lines.""" line_index = result['line'] - 1 target = self.source[line_index] offset = result['column'] - 1 @@ -600,12 +627,13 @@ def fix_e401(self, result): if ';' in target: return [] - indentation = target.split("import ")[0] + indentation = target.split('import ')[0] fixed = (target[:offset].rstrip('\t ,') + self.newline + indentation + 'import ' + target[offset:].lstrip('\t ,')) self.source[line_index] = fixed def fix_e501(self, result): + """Try to make lines fit within 79 characters.""" line_index = result['line'] - 1 target = self.source[line_index] @@ -659,6 +687,7 @@ def fix_e502(self, result): self.source[line_index] = target.rstrip('\n\r \t\\') + self.newline def fix_e701(self, result): + """Put colon-separated compound statement on separate lines.""" line_index = result['line'] - 1 target = self.source[line_index] c = result['column'] @@ -669,7 +698,7 @@ def fix_e701(self, result): self.source[result['line'] - 1] = fixed_source def fix_e702(self, result, logical): - """Fix multiple statements on one line.""" + """Put semicolon-separated compound statement on separate lines.""" logical_lines = logical[2] line_index = result['line'] - 1 @@ -686,7 +715,7 @@ def fix_e702(self, result, logical): return offset = result['column'] - 1 - first = target[:offset].rstrip(';') + first = target[:offset].rstrip(';').rstrip() second = (_get_indentation(logical_lines[0]) + target[offset:].lstrip(';').lstrip()) @@ -719,31 +748,31 @@ def fix_e711(self, result): self.source[line_index] = ' '.join([left, new_center, right]) def fix_e721(self, _): + """Switch to use isinstance().""" return self.refactor('idioms') def fix_w291(self, result): + """Remove trailing whitespace.""" fixed_line = self.source[result['line'] - 1].rstrip() - self.source[result['line'] - 1] = "%s%s" % (fixed_line, self.newline) + self.source[result['line'] - 1] = '%s%s' % (fixed_line, self.newline) def fix_w293(self, result): + """Remove trailing whitespace on blank line.""" assert not self.source[result['line'] - 1].strip() self.source[result['line'] - 1] = self.newline def fix_w391(self, _): - source = copy.copy(self.source) - source.reverse() + """Remove trailing blank lines.""" blank_count = 0 - for line in source: + for line in reversed(self.source): line = line.rstrip() if line: break else: blank_count += 1 - source = source[blank_count:] - source.reverse() original_length = len(self.source) - self.source = source + self.source = self.source[:original_length - blank_count] return range(1, 1 + original_length) def refactor(self, fixer_name, ignore=None): @@ -756,10 +785,15 @@ def refactor(self, fixer_name, ignore=None): try: new_text = refactor_with_2to3(''.join(self.source), fixer_name=fixer_name) - except pgen2.parse.ParseError: + except (pgen2.parse.ParseError, + UnicodeDecodeError, UnicodeEncodeError): return [] - if ''.join(self.source).strip() == new_text.strip(): + try: + original = unicode(''.join(self.source).strip(), 'utf-8') + except (NameError, TypeError): + original = ''.join(self.source).strip() + if original == new_text.strip(): return [] else: if ignore: @@ -770,6 +804,7 @@ def refactor(self, fixer_name, ignore=None): return range(1, 1 + original_length) def fix_w601(self, _): + """Replace the {}.has_key() form with 'in'.""" return self.refactor('has_key') def fix_w602(self, _): @@ -778,9 +813,11 @@ def fix_w602(self, _): ignore='with_traceback') def fix_w603(self, _): + """Replace <> with !=.""" return self.refactor('ne') def fix_w604(self, _): + """Replace backticks with repr().""" return self.refactor('repr') @@ -808,7 +845,7 @@ def find_newline(source): def _get_indentword(source): """Return indentation type.""" sio = StringIO(source) - indent_word = " " # Default in case source has no indentation + indent_word = ' ' # Default in case source has no indentation try: for t in tokenize.generate_tokens(sio.readline): if t[0] == token.INDENT: @@ -821,22 +858,19 @@ def _get_indentword(source): def _get_indentation(line): """Return leading whitespace.""" - non_whitespace_index = len(line) - len(line.lstrip()) - return line[:non_whitespace_index] - - -def _split_indentation(line): - """Return line split into tuple (indentation, rest).""" - non_whitespace_index = len(line) - len(line.lstrip()) - return (line[:non_whitespace_index], line[non_whitespace_index:]) + if line.strip(): + non_whitespace_index = len(line) - len(line.lstrip()) + return line[:non_whitespace_index] + else: + return '' def _analyze_pep8result(result): - tmp = result.split(":") + tmp = result.split(':') filename = tmp[0] line = int(tmp[1]) column = int(tmp[2]) - info = " ".join(result.split()[1:]) + info = ' '.join(result.split()[1:]) pep8id = info.lstrip().split()[0] return dict(id=pep8id, filename=filename, line=line, column=column, info=info) @@ -844,7 +878,7 @@ def _analyze_pep8result(result): def _get_difftext(old, new, filename): diff = unified_diff(old, new, 'original/' + filename, 'fixed/' + filename) - return "".join(diff) + return ''.join(diff) def _priority_key(pep8_result): @@ -936,8 +970,7 @@ def fix_whitespace(line, offset, replacement): def _spawn_pep8(pep8_options): """Execute pep8 via subprocess.Popen.""" - paths = os.environ['PATH'].split(':') - for path in paths: + for path in os.environ['PATH'].split(':'): if os.path.exists(os.path.join(path, PEP8_BIN)): cmd = ([os.path.join(path, PEP8_BIN)] + pep8_options) @@ -989,7 +1022,9 @@ class Reindenter(object): """ - def __init__(self, input_text): + def __init__(self, input_text, newline): + self.newline = newline + self.find_stmt = 1 # next token begins a fresh stmt? self.level = 0 # current indent level @@ -997,11 +1032,22 @@ def __init__(self, input_text): self.raw = input_text self.after = None + self.string_content_line_numbers = multiline_string_lines( + ''.join(self.raw)) + # File lines, rstripped & tab-expanded. Dummy at start is so # that we can use tokenize's 1-based line numbering easily. - # Note that a line is all-blank iff it's "\n". - self.lines = [line.rstrip('\n \t').expandtabs() + "\n" - for line in self.raw] + # Note that a line is all-blank iff it is a newline. + self.lines = [] + for line_number, line in enumerate(self.raw, start=1): + # Do not modify if inside a multi-line string. + if line_number in self.string_content_line_numbers: + self.lines.append(line) + else: + # Only expand leading tabs. + self.lines.append(_get_indentation(line).expandtabs() + + line.strip() + newline) + self.lines.insert(0, None) self.index = 1 # index into self.lines of next line @@ -1012,15 +1058,20 @@ def __init__(self, input_text): self.stats = [] def run(self): + """Fix indentation and return modified line numbers. + + Line numbers are indexed at 1. + + """ tokens = tokenize.generate_tokens(self.getline) try: for t in tokens: self.tokeneater(*t) except (tokenize.TokenError, IndentationError): - return False + return set() # Remove trailing empty lines. lines = self.lines - while lines and lines[-1] == "\n": + while lines and lines[-1] == self.newline: lines.pop() # Sentinel. stats = self.stats @@ -1075,16 +1126,24 @@ def run(self): if diff == 0 or have == 0: after.extend(lines[thisstmt:nextstmt]) else: - for line in lines[thisstmt:nextstmt]: - if diff > 0: - if line == "\n": + for line_number, line in enumerate(lines[thisstmt:nextstmt], + start=thisstmt): + if line_number in self.string_content_line_numbers: + after.append(line) + elif diff > 0: + if line == self.newline: after.append(line) else: - after.append(" " * diff + line) + after.append(' ' * diff + line) else: remove = min(_leading_space_count(line), -diff) after.append(line[remove:]) - return self.raw != self.after + + if self.raw == self.after: + return set() + else: + return (set(range(1, 1 + len(self.raw))) - + self.string_content_line_numbers) def fixed_lines(self): return self.after @@ -1155,30 +1214,24 @@ def __init__(self, physical_lines, hard_wrap=79, soft_wrap=72): self.soft_wrap = soft_wrap self.tokens = list() self.rel_indent = None - sio = StringIO("".join(physical_lines)) + sio = StringIO(''.join(physical_lines)) for t in tokenize.generate_tokens(sio.readline): if not len(self.tokens) and t[0] in self.SKIP_TOKENS: continue if t[0] != tokenize.ENDMARKER: - #if t[2][0] > max_seen: - #max_seen = t[2][0] - #print ">>" + repr(t[4]) + "<<" self.tokens.append(t) - self.logical_line, self.mapping = self.build_tokens_logical( - self.tokens - ) + + self.logical_line = self.build_tokens_logical(self.tokens) def build_tokens_logical(self, tokens): """Build a logical line from a list of tokens. - Returns the logical line and a list of (offset, token) tuples. Does + Return the logical line and a list of (offset, token) tuples. Does not mute strings like the version in pep8.py. """ # from pep8.py with minor modifications - mapping = [] logical = [] - length = 0 previous = None for t in tokens: token_type, text = t[0:2] @@ -1192,29 +1245,24 @@ def build_tokens_logical(self, tokens): if prev_text == ',' or (prev_text not in '{[(' and text not in '}])'): logical.append(' ') - length += 1 elif end != start: # different column fill = self.lines[end_line - 1][end:start] logical.append(fill) - length += len(fill) - mapping.append((length, t)) logical.append(text) - length += len(text) previous = t logical_line = ''.join(logical) assert logical_line.lstrip() == logical_line assert logical_line.rstrip() == logical_line - return logical_line, mapping + return logical_line def pep8_expected(self): - """Replicates logic in pep8.py, to know what level to indent things to. + """Replicate logic in pep8.py, to know what level to indent things to. - Returns a list of lists; each list represents valid indent levels for + Return a list of lists; each list represents valid indent levels for the line in question, relative from the initial indent. However, the first entry is the indent level which was expected. """ - # What follows is an adjusted version of # pep8.py:continuation_line_indentation. All of the comments have been # stripped and the 'yield' statements replaced with 'pass'. @@ -1413,7 +1461,11 @@ def refactor_with_2to3(source_text, fixer_name): tool = refactor.RefactoringTool( fixer_names=fixers, explicit=fixers) - return str(tool.refactor_string(source_text, name='')) + try: + return unicode(tool.refactor_string( + source_text.decode('utf-8'), name='')) + except NameError: + return str(tool.refactor_string(source_text, name='')) def break_multi_line(source_text, newline, indent_word): @@ -1456,13 +1508,76 @@ def check_syntax(code): return False +def filter_results(source, results): + """Filter out spurious reports from pep8. + + Currently we filter out errors about indentation in multiline strings. + + """ + string_line_numbers = multiline_string_lines(source) + + for r in results: + if r['line'] in string_line_numbers: + if r['id'].lower().startswith('e1'): + continue + elif r['id'].lower() in ['e501', 'w191']: + continue + + # Filter out incorrect E101 reports when there are no tabs. + # pep8 will complain about this even if the tab indentation found + # elsewhere is in a multi-line string. + if r['id'].lower() == 'e101' and '\t' not in source[r['line'] - 1]: + continue + + yield r + + +def multiline_string_lines(source): + """Return line numbers that are within multiline strings. + + The line numbers are indexed at 1. + + Docstrings are ignored. + + """ + sio = StringIO(source) + line_numbers = set() + previous_token_type = '' + try: + for t in tokenize.generate_tokens(sio.readline): + token_type = t[0] + token_string = t[1] + start_row = t[2][0] + end_row = t[3][0] + + if (token_type == tokenize.STRING and + starts_with_triple(token_string) and + previous_token_type != tokenize.INDENT): + # We increment by one since we want the contents of the + # string. + line_numbers |= set(range(1 + start_row, 1 + end_row)) + + previous_token_type = token_type + except (IndentationError, tokenize.TokenError): + pass + + return line_numbers + + +def starts_with_triple(string): + """Return True if the string starts with triple single/double quotes.""" + return (string.strip().startswith('"""') or + string.strip().startswith("'''")) + + def fix_file(filename, opts, output=sys.stdout): tmp_source = read_from_filename(filename) # Add missing newline (important for diff) - tmp_newline = find_newline(tmp_source) - if tmp_source == tmp_source.rstrip(tmp_newline): - tmp_source += tmp_newline + if tmp_source: + tmp_newline = find_newline(tmp_source) + if tmp_source == tmp_source.rstrip(tmp_newline): + tmp_source += tmp_newline fix = FixPEP8(filename, opts, contents=tmp_source) fixed_source = fix.fix() @@ -1470,6 +1585,7 @@ def fix_file(filename, opts, output=sys.stdout): tmp_filename = filename if not pep8 or opts.in_place: encoding = detect_encoding(filename) + for _ in range(opts.pep8_passes): if fixed_source == tmp_source: break @@ -1503,11 +1619,13 @@ def parse_args(args): """Parse command-line options.""" parser = OptionParser(usage='Usage: autopep8 [options] ' '[filename [filename ...]]', - version="autopep8: %s" % __version__, + version='autopep8: %s' % __version__, description=__doc__, prog='autopep8') - parser.add_option('-v', '--verbose', action='store_true', dest='verbose', - help='print verbose messages') + parser.add_option('-v', '--verbose', action='count', dest='verbose', + default=0, + help='print verbose messages; ' + 'multiple -v result in more verbose messages') parser.add_option('-d', '--diff', action='store_true', dest='diff', help='print the diff for the fixed source') parser.add_option('-i', '--in-place', action='store_true', @@ -1516,16 +1634,19 @@ def parse_args(args): help='run recursively; must be used with --in-place or ' '--diff') parser.add_option('-p', '--pep8-passes', - default=PEP8_PASSES_MAX, type='int', + default=100, type='int', help='maximum number of additional pep8 passes' ' (default: %default)') + parser.add_option('--list-fixes', action='store_true', + help='list codes for fixes; ' + 'used by --ignore and --select') parser.add_option('--ignore', default='', help='do not fix these errors/warnings (e.g. E4,W)') parser.add_option('--select', default='', - help='select errors/warnings (e.g. E4,W)') + help='fix only these errors/warnings (e.g. E4,W)') opts, args = parser.parse_args(args) - if not len(args): + if not len(args) and not opts.list_fixes: parser.error('incorrect number of arguments') if len(args) > 1 and not (opts.in_place or opts.diff): @@ -1542,9 +1663,31 @@ def parse_args(args): return opts, args +def supported_fixes(): + """Yield pep8 error codes that autopep8 fixes. + + Each item we yield is a tuple of the code followed by its description. + + """ + instance = FixPEP8(filename=None, options=None, contents='') + for attribute in dir(instance): + code = re.match('fix_([ew][0-9][0-9][0-9])', attribute) + if code: + yield (code.group(1).upper(), + re.sub(r'\s+', ' ', + getattr(instance, attribute).__doc__)) + + def main(): """Tool main.""" opts, args = parse_args(sys.argv[1:]) + + if opts.list_fixes: + for code, description in supported_fixes(): + print('{code} - {description}'.format( + code=code, description=description)) + return 0 + if opts.in_place or opts.diff: filenames = list(set(args)) else: @@ -1552,6 +1695,11 @@ def main(): assert not opts.recursive filenames = args[:1] + if sys.version_info[0] >= 3: + output = sys.stdout + else: + output = codecs.getwriter(locale.getpreferredencoding())(sys.stdout) + while filenames: name = filenames.pop(0) if opts.recursive and os.path.isdir(name): @@ -1564,11 +1712,11 @@ def main(): directories.remove(d) else: if opts.verbose: - sys.stderr.write('[file:%s]\n' % name) + print('[file:%s]' % name, file=sys.stderr) try: - fix_file(name, opts) - except (UnicodeDecodeError, UnicodeEncodeError, IOError) as error: - sys.stderr.write(str(error) + '\n') + fix_file(name, opts, output) + except IOError as error: + print(str(error), file=sys.stderr) if __name__ == '__main__': diff --git a/pylibs/pymode/auto.py b/pylibs/pymode/auto.py index cf43d0e7..87d38992 100644 --- a/pylibs/pymode/auto.py +++ b/pylibs/pymode/auto.py @@ -1,13 +1,13 @@ import vim -from pylibs.autopep8 import fix_file, PEP8_PASSES_MAX +from pylibs.autopep8 import fix_file class Options(): - verbose = False + verbose = 0 diff = False in_place = True recursive = False - pep8_passes = PEP8_PASSES_MAX + pep8_passes = 100 ignore = '' select = '' From cec26a7895f0d20f969e527bf3597393302e3ad2 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 14 Mar 2013 18:03:51 +0800 Subject: [PATCH 137/513] More strong linter checking --- doc/pymode.txt | 2 +- plugin/pymode.vim | 2 +- pylint.ini | 6 +----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/doc/pymode.txt b/doc/pymode.txt index d5b59945..ab096ea2 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -196,7 +196,7 @@ This option sets code checkers. Values: IDs of errors, separated by commas or empty strings E.g. "E501,W002", "E2,W" (Skip all Warnings and Errors startswith E2) and etc ~ -Default: "E501". +Default: "". Skip errors and warnings. See also: |'pymode_lint_select'|, |'pymode_lint_config'| diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 4c4e5803..e13133b5 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -91,7 +91,7 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint call pymode#Default("g:pymode_lint_maxheight", 6) " OPTION: g:pymode_lint_ignore -- string. Skip errors and warnings (e.g. E4,W) - call pymode#Default("g:pymode_lint_ignore", "E501") + call pymode#Default("g:pymode_lint_ignore", "") " OPTION: g:pymode_lint_select -- string. Select errors and warnings (e.g. E4,W) call pymode#Default("g:pymode_lint_select", "") diff --git a/pylint.ini b/pylint.ini index 7e9e5e5d..c58c4d0e 100644 --- a/pylint.ini +++ b/pylint.ini @@ -4,8 +4,6 @@ # # C0103: Invalid name "%s" (should match %s) # C0111: Missing docstring -# C0301: Line too long (%s/%s) -# E1002: Use super on an old style class # E1101: %s %r has no %r member # R0901: Too many ancestors (%s/%s) # R0902: Too many instance attributes (%s/%s) @@ -15,15 +13,13 @@ # R0915: Too many statements (%s/%s) # W0141: Used builtin function %r # W0142: Used * or ** magic -# W0212: Access to a protected member %s of a client class # W0221: Arguments number differs from %s method -# W0223: Method %r is abstract in class %r but is not overridden # W0232: Class has no __init__ method # W0401: Wildcard import %s # W0613: Unused argument %r # W0631: Using possibly undefined loop variable %r # -disable = C0103,C0111,C0301,E1002,E1101,R0901,R0902,R0903,R0904,R0913,R0915,W0141,W0142,W0212,W0221,W0223,W0232,W0401,W0613,W0631 +disable = C0103,C0111,E1101,R0901,R0902,R0903,R0904,R0913,R0915,W0141,W0142,W0221,W0232,W0401,W0613,W0631 [TYPECHECK] generated-members = REQUEST,acl_users,aq_parent,objects,DoesNotExist,_meta,status_code,content,context From 1c5a6875db82fc1fd6039ec6372c33681932723c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 14 Mar 2013 19:38:51 +0800 Subject: [PATCH 138/513] Migrate to pylama --- .gitignore | 3 +- pylibs/logilab/astng/COPYING | 339 ---- pylibs/logilab/astng/COPYING.LESSER | 510 ------ pylibs/logilab/astng/README | 54 - pylibs/logilab/astng/README.Python3 | 26 - pylibs/logilab/astng/inspector.py | 289 ---- pylibs/logilab/common/COPYING | 339 ---- pylibs/logilab/common/COPYING.LESSER | 510 ------ pylibs/logilab/common/README | 187 --- pylibs/logilab/common/README.Python3 | 29 - pylibs/logilab/common/announce.txt | 25 - pylibs/logilab/common/cache.py | 114 -- pylibs/logilab/common/clcommands.py | 332 ---- pylibs/logilab/common/cli.py | 208 --- pylibs/logilab/common/contexts.py | 5 - pylibs/logilab/common/corbautils.py | 117 -- pylibs/logilab/common/daemon.py | 100 -- pylibs/logilab/common/date.py | 327 ---- pylibs/logilab/common/dbf.py | 229 --- pylibs/logilab/common/debugger.py | 210 --- pylibs/logilab/common/deprecation.py | 130 -- pylibs/logilab/common/fileutils.py | 402 ----- pylibs/logilab/common/hg.py | 130 -- pylibs/logilab/common/logging_ext.py | 178 --- pylibs/logilab/common/optparser.py | 90 -- pylibs/logilab/common/pdf_ext.py | 111 -- pylibs/logilab/common/proc.py | 277 ---- pylibs/logilab/common/pyro_ext.py | 180 --- pylibs/logilab/common/pytest.py | 1177 -------------- pylibs/logilab/common/registry.py | 973 ------------ pylibs/logilab/common/shellutils.py | 456 ------ pylibs/logilab/common/sphinx_ext.py | 87 -- pylibs/logilab/common/sphinxutils.py | 122 -- pylibs/logilab/common/table.py | 923 ----------- pylibs/logilab/common/tasksqueue.py | 98 -- pylibs/logilab/common/testlib.py | 1389 ----------------- pylibs/logilab/common/umessage.py | 167 -- pylibs/logilab/common/urllib2ext.py | 87 -- pylibs/logilab/common/vcgutils.py | 216 --- pylibs/logilab/common/xmlrpcutils.py | 131 -- pylibs/logilab/common/xmlutils.py | 61 - pylibs/pylama/__init__.py | 8 + pylibs/pylama/main.py | 170 ++ pylibs/{ => pylama}/mccabe.py | 19 +- pylibs/{ => pylama}/pep8.py | 719 ++++----- pylibs/{ => pylama}/pyflakes/__init__.py | 0 pylibs/{ => pylama}/pyflakes/checker.py | 77 +- pylibs/{ => pylama}/pyflakes/messages.py | 0 pylibs/{ => pylama}/pylint/__init__.py | 35 +- pylibs/{ => pylama}/pylint/__pkginfo__.py | 6 +- .../{ => pylama}/pylint/checkers/__init__.py | 20 +- pylibs/{ => pylama}/pylint/checkers/base.py | 95 +- .../{ => pylama}/pylint/checkers/classes.py | 149 +- .../pylint/checkers/design_analysis.py | 103 +- .../pylint/checkers/exceptions.py | 50 +- pylibs/{ => pylama}/pylint/checkers/format.py | 137 +- .../{ => pylama}/pylint/checkers/imports.py | 54 +- .../{ => pylama}/pylint/checkers/logging.py | 26 +- pylibs/{ => pylama}/pylint/checkers/misc.py | 7 +- .../{ => pylama}/pylint/checkers/newstyle.py | 22 +- .../pylint/checkers/raw_metrics.py | 20 +- .../{ => pylama}/pylint/checkers/similar.py | 57 +- .../pylint/checkers/string_format.py | 22 +- .../{ => pylama}/pylint/checkers/typecheck.py | 24 +- pylibs/{ => pylama}/pylint/checkers/utils.py | 10 +- .../{ => pylama}/pylint/checkers/variables.py | 73 +- pylibs/{ => pylama}/pylint/config.py | 27 +- pylibs/{ => pylama}/pylint/interfaces.py | 4 +- pylibs/{ => pylama}/pylint/lint.py | 255 ++- .../{ => pylama/pylint}/logilab/__init__.py | 0 .../pylint}/logilab/astng/__init__.py | 29 +- .../pylint}/logilab/astng/__pkginfo__.py | 9 +- .../pylint}/logilab/astng/as_string.py | 0 .../pylint}/logilab/astng/bases.py | 10 +- .../pylint/logilab/astng/brain/__init__.py | 0 .../logilab/astng/brain/py2mechanize.py | 20 + .../pylint/logilab/astng/brain/py2qt4.py | 25 + .../pylint}/logilab/astng/brain/py2stdlib.py | 73 +- .../pylint}/logilab/astng/builder.py | 12 +- .../pylint}/logilab/astng/exceptions.py | 0 .../pylint}/logilab/astng/inference.py | 10 +- .../pylint}/logilab/astng/manager.py | 14 +- .../pylint}/logilab/astng/mixins.py | 2 +- .../pylint}/logilab/astng/node_classes.py | 23 +- .../pylint}/logilab/astng/nodes.py | 4 +- .../pylint}/logilab/astng/protocols.py | 10 +- .../pylint}/logilab/astng/raw_building.py | 18 +- .../pylint}/logilab/astng/rebuilder.py | 42 +- .../pylint}/logilab/astng/scoped_nodes.py | 26 +- .../pylint}/logilab/astng/utils.py | 4 +- .../pylint}/logilab/common/__init__.py | 2 +- .../pylint}/logilab/common/__pkginfo__.py | 9 +- .../pylint}/logilab/common/changelog.py | 0 .../pylint}/logilab/common/compat.py | 2 +- .../pylint}/logilab/common/configuration.py | 21 +- .../pylint}/logilab/common/decorators.py | 2 +- .../pylint/logilab/common/deprecation.py | 188 +++ .../pylint}/logilab/common/graph.py | 2 +- .../pylint}/logilab/common/interface.py | 0 .../pylint}/logilab/common/modutils.py | 35 +- .../pylint}/logilab/common/optik_ext.py | 2 +- .../pylint}/logilab/common/textutils.py | 2 +- .../pylint}/logilab/common/tree.py | 4 +- .../logilab/common/ureports/__init__.py | 8 +- .../logilab/common/ureports/docbook_writer.py | 2 +- .../logilab/common/ureports/html_writer.py | 2 +- .../pylint}/logilab/common/ureports/nodes.py | 2 +- .../logilab/common/ureports/text_writer.py | 4 +- .../pylint}/logilab/common/visitor.py | 0 .../{ => pylama}/pylint/reporters/__init__.py | 29 +- .../pylint/reporters/guireporter.py | 12 +- pylibs/{ => pylama}/pylint/reporters/html.py | 11 +- pylibs/{ => pylama}/pylint/reporters/text.py | 30 +- pylibs/{ => pylama}/pylint/utils.py | 126 +- pylibs/pylama/utils.py | 96 ++ pylibs/pylint/README | 70 - pylibs/pylint/README.Python3 | 37 - pylibs/pymode/lint.py | 164 +- 118 files changed, 2189 insertions(+), 12531 deletions(-) delete mode 100644 pylibs/logilab/astng/COPYING delete mode 100644 pylibs/logilab/astng/COPYING.LESSER delete mode 100644 pylibs/logilab/astng/README delete mode 100644 pylibs/logilab/astng/README.Python3 delete mode 100644 pylibs/logilab/astng/inspector.py delete mode 100644 pylibs/logilab/common/COPYING delete mode 100644 pylibs/logilab/common/COPYING.LESSER delete mode 100644 pylibs/logilab/common/README delete mode 100644 pylibs/logilab/common/README.Python3 delete mode 100644 pylibs/logilab/common/announce.txt delete mode 100644 pylibs/logilab/common/cache.py delete mode 100644 pylibs/logilab/common/clcommands.py delete mode 100644 pylibs/logilab/common/cli.py delete mode 100644 pylibs/logilab/common/contexts.py delete mode 100644 pylibs/logilab/common/corbautils.py delete mode 100644 pylibs/logilab/common/daemon.py delete mode 100644 pylibs/logilab/common/date.py delete mode 100644 pylibs/logilab/common/dbf.py delete mode 100644 pylibs/logilab/common/debugger.py delete mode 100644 pylibs/logilab/common/deprecation.py delete mode 100644 pylibs/logilab/common/fileutils.py delete mode 100644 pylibs/logilab/common/hg.py delete mode 100644 pylibs/logilab/common/logging_ext.py delete mode 100644 pylibs/logilab/common/optparser.py delete mode 100644 pylibs/logilab/common/pdf_ext.py delete mode 100644 pylibs/logilab/common/proc.py delete mode 100644 pylibs/logilab/common/pyro_ext.py delete mode 100644 pylibs/logilab/common/pytest.py delete mode 100644 pylibs/logilab/common/registry.py delete mode 100644 pylibs/logilab/common/shellutils.py delete mode 100644 pylibs/logilab/common/sphinx_ext.py delete mode 100644 pylibs/logilab/common/sphinxutils.py delete mode 100644 pylibs/logilab/common/table.py delete mode 100644 pylibs/logilab/common/tasksqueue.py delete mode 100644 pylibs/logilab/common/testlib.py delete mode 100644 pylibs/logilab/common/umessage.py delete mode 100644 pylibs/logilab/common/urllib2ext.py delete mode 100644 pylibs/logilab/common/vcgutils.py delete mode 100644 pylibs/logilab/common/xmlrpcutils.py delete mode 100644 pylibs/logilab/common/xmlutils.py create mode 100644 pylibs/pylama/__init__.py create mode 100644 pylibs/pylama/main.py rename pylibs/{ => pylama}/mccabe.py (95%) rename pylibs/{ => pylama}/pep8.py (79%) rename pylibs/{ => pylama}/pyflakes/__init__.py (100%) rename pylibs/{ => pylama}/pyflakes/checker.py (88%) rename pylibs/{ => pylama}/pyflakes/messages.py (100%) rename pylibs/{ => pylama}/pylint/__init__.py (50%) rename pylibs/{ => pylama}/pylint/__pkginfo__.py (93%) rename pylibs/{ => pylama}/pylint/checkers/__init__.py (91%) rename pylibs/{ => pylama}/pylint/checkers/base.py (91%) rename pylibs/{ => pylama}/pylint/checkers/classes.py (82%) rename pylibs/{ => pylama}/pylint/checkers/design_analysis.py (80%) rename pylibs/{ => pylama}/pylint/checkers/exceptions.py (84%) rename pylibs/{ => pylama}/pylint/checkers/format.py (71%) rename pylibs/{ => pylama}/pylint/checkers/imports.py (90%) rename pylibs/{ => pylama}/pylint/checkers/logging.py (88%) rename pylibs/{ => pylama}/pylint/checkers/misc.py (96%) rename pylibs/{ => pylama}/pylint/checkers/newstyle.py (90%) rename pylibs/{ => pylama}/pylint/checkers/raw_metrics.py (85%) rename pylibs/{ => pylama}/pylint/checkers/similar.py (86%) rename pylibs/{ => pylama}/pylint/checkers/string_format.py (93%) rename pylibs/{ => pylama}/pylint/checkers/typecheck.py (95%) rename pylibs/{ => pylama}/pylint/checkers/utils.py (98%) rename pylibs/{ => pylama}/pylint/checkers/variables.py (89%) rename pylibs/{ => pylama}/pylint/config.py (89%) rename pylibs/{ => pylama}/pylint/interfaces.py (96%) rename pylibs/{ => pylama}/pylint/lint.py (80%) rename pylibs/{ => pylama/pylint}/logilab/__init__.py (100%) rename pylibs/{ => pylama/pylint}/logilab/astng/__init__.py (75%) rename pylibs/{ => pylama/pylint}/logilab/astng/__pkginfo__.py (82%) rename pylibs/{ => pylama/pylint}/logilab/astng/as_string.py (100%) rename pylibs/{ => pylama/pylint}/logilab/astng/bases.py (98%) create mode 100644 pylibs/pylama/pylint/logilab/astng/brain/__init__.py create mode 100644 pylibs/pylama/pylint/logilab/astng/brain/py2mechanize.py create mode 100644 pylibs/pylama/pylint/logilab/astng/brain/py2qt4.py rename pylibs/{ => pylama/pylint}/logilab/astng/brain/py2stdlib.py (58%) rename pylibs/{ => pylama/pylint}/logilab/astng/builder.py (96%) rename pylibs/{ => pylama/pylint}/logilab/astng/exceptions.py (100%) rename pylibs/{ => pylama/pylint}/logilab/astng/inference.py (97%) rename pylibs/{ => pylama/pylint}/logilab/astng/manager.py (96%) rename pylibs/{ => pylama/pylint}/logilab/astng/mixins.py (98%) rename pylibs/{ => pylama/pylint}/logilab/astng/node_classes.py (97%) rename pylibs/{ => pylama/pylint}/logilab/astng/nodes.py (94%) rename pylibs/{ => pylama/pylint}/logilab/astng/protocols.py (98%) rename pylibs/{ => pylama/pylint}/logilab/astng/raw_building.py (96%) rename pylibs/{ => pylama/pylint}/logilab/astng/rebuilder.py (95%) rename pylibs/{ => pylama/pylint}/logilab/astng/scoped_nodes.py (98%) rename pylibs/{ => pylama/pylint}/logilab/astng/utils.py (99%) rename pylibs/{ => pylama/pylint}/logilab/common/__init__.py (98%) rename pylibs/{ => pylama/pylint}/logilab/common/__pkginfo__.py (91%) rename pylibs/{ => pylama/pylint}/logilab/common/changelog.py (100%) rename pylibs/{ => pylama/pylint}/logilab/common/compat.py (99%) rename pylibs/{ => pylama/pylint}/logilab/common/configuration.py (98%) rename pylibs/{ => pylama/pylint}/logilab/common/decorators.py (99%) create mode 100644 pylibs/pylama/pylint/logilab/common/deprecation.py rename pylibs/{ => pylama/pylint}/logilab/common/graph.py (99%) rename pylibs/{ => pylama/pylint}/logilab/common/interface.py (100%) rename pylibs/{ => pylama/pylint}/logilab/common/modutils.py (96%) rename pylibs/{ => pylama/pylint}/logilab/common/optik_ext.py (99%) rename pylibs/{ => pylama/pylint}/logilab/common/textutils.py (99%) rename pylibs/{ => pylama/pylint}/logilab/common/tree.py (98%) rename pylibs/{ => pylama/pylint}/logilab/common/ureports/__init__.py (96%) rename pylibs/{ => pylama/pylint}/logilab/common/ureports/docbook_writer.py (99%) rename pylibs/{ => pylama/pylint}/logilab/common/ureports/html_writer.py (99%) rename pylibs/{ => pylama/pylint}/logilab/common/ureports/nodes.py (99%) rename pylibs/{ => pylama/pylint}/logilab/common/ureports/text_writer.py (98%) rename pylibs/{ => pylama/pylint}/logilab/common/visitor.py (100%) rename pylibs/{ => pylama}/pylint/reporters/__init__.py (78%) rename pylibs/{ => pylama}/pylint/reporters/guireporter.py (76%) rename pylibs/{ => pylama}/pylint/reporters/html.py (90%) rename pylibs/{ => pylama}/pylint/reporters/text.py (87%) rename pylibs/{ => pylama}/pylint/utils.py (80%) create mode 100644 pylibs/pylama/utils.py delete mode 100644 pylibs/pylint/README delete mode 100644 pylibs/pylint/README.Python3 diff --git a/.gitignore b/.gitignore index 02e750e1..cddd4dea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc +.DS_Store +.ropeproject tags todo.txt -.ropeproject diff --git a/pylibs/logilab/astng/COPYING b/pylibs/logilab/astng/COPYING deleted file mode 100644 index d511905c..00000000 --- a/pylibs/logilab/astng/COPYING +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/pylibs/logilab/astng/COPYING.LESSER b/pylibs/logilab/astng/COPYING.LESSER deleted file mode 100644 index 2d2d780e..00000000 --- a/pylibs/logilab/astng/COPYING.LESSER +++ /dev/null @@ -1,510 +0,0 @@ - - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations -below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it -becomes a de-facto standard. To achieve this, non-free programs must -be allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control -compilation and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at least - three years, to give the same user the materials specified in - Subsection 6a, above, for a charge no more than the cost of - performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply, and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License -may add an explicit geographical distribution limitation excluding those -countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms -of the ordinary General Public License). - - To apply these terms, attach the following notices to the library. -It is safest to attach them to the start of each source file to most -effectively convey the exclusion of warranty; and each file should -have at least the "copyright" line and a pointer to where the full -notice is found. - - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or -your school, if any, to sign a "copyright disclaimer" for the library, -if necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James - Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - diff --git a/pylibs/logilab/astng/README b/pylibs/logilab/astng/README deleted file mode 100644 index db8dca1a..00000000 --- a/pylibs/logilab/astng/README +++ /dev/null @@ -1,54 +0,0 @@ -ASTNG -===== - -What's this ? -------------- - -The aim of this module is to provide a common base representation of -python source code for projects such as pychecker, pyreverse, -pylint... Well, actually the development of this library is essentially -governed by pylint's needs. - -It provides a compatible representation which comes from the `_ast` module. -It rebuilds the tree generated by the builtin _ast module by recursively -walking down the AST and building an extended ast (let's call it astng ;). The -new node classes have additional methods and attributes for different usages. -They include some support for static inference and local name scopes. -Furthermore, astng builds partial trees by inspecting living objects. - -Main modules are: - -* `bases`, `node_classses` and `scoped_nodes` contain the classes for the - different type of nodes of the tree. - -* the `manager` contains a high level object to get astng trees from - source files and living objects. It maintains a cache of previously - constructed tree for quick access - - -Installation ------------- - -Extract the tarball, jump into the created directory and run :: - - python setup.py install - -For installation options, see :: - - python setup.py install --help - - -If you have any questions, please mail the -python-project@lists.logilab.org mailing list for support. See -http://lists.logilab.org/mailman/listinfo/python-projects for -subscription information and archives. - -Test ----- - -Tests are in the 'test' subdirectory. To launch the whole tests suite -at once, you may use the 'pytest' utility from logilab-common (simply -type 'pytest' from within this directory) or if you're running python ->= 2.7, using discover, for instance:: - - python -m unittest discover -p "unittest*.py" diff --git a/pylibs/logilab/astng/README.Python3 b/pylibs/logilab/astng/README.Python3 deleted file mode 100644 index 55ea159c..00000000 --- a/pylibs/logilab/astng/README.Python3 +++ /dev/null @@ -1,26 +0,0 @@ -Python3 -======= - -Approach --------- - -We maintain a Python 2 base and use 2to3 to generate Python 3 code. - -2to3 is integrated into the distutils installation process and will be run as a -build step when invoked by the python3 interpreter:: - - python3 setup.py install --no-compile - - -Debian ------- - -For the Debian packaging, you can use the debian.py3k/ content against -the debian/ folder:: - - cp debian.py3k/* debian/ - - -Resources ---------- -http://wiki.python.org/moin/PortingPythonToPy3k diff --git a/pylibs/logilab/astng/inspector.py b/pylibs/logilab/astng/inspector.py deleted file mode 100644 index a4abd1f2..00000000 --- a/pylibs/logilab/astng/inspector.py +++ /dev/null @@ -1,289 +0,0 @@ -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com -# -# This file is part of logilab-astng. -# -# logilab-astng is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation, either version 2.1 of the License, or (at your -# option) any later version. -# -# logilab-astng 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 Lesser General Public License -# for more details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . -"""visitor doing some postprocessing on the astng tree. -Try to resolve definitions (namespace) dictionary, relationship... - -This module has been imported from pyreverse -""" - -__docformat__ = "restructuredtext en" - -from os.path import dirname - -from logilab.common.modutils import get_module_part, is_relative, \ - is_standard_module - -from logilab import astng -from logilab.astng.exceptions import InferenceError -from logilab.astng.utils import LocalsVisitor - -class IdGeneratorMixIn: - """ - Mixin adding the ability to generate integer uid - """ - def __init__(self, start_value=0): - self.id_count = start_value - - def init_counter(self, start_value=0): - """init the id counter - """ - self.id_count = start_value - - def generate_id(self): - """generate a new identifier - """ - self.id_count += 1 - return self.id_count - - -class Linker(IdGeneratorMixIn, LocalsVisitor): - """ - walk on the project tree and resolve relationships. - - According to options the following attributes may be added to visited nodes: - - * uid, - a unique identifier for the node (on astng.Project, astng.Module, - astng.Class and astng.locals_type). Only if the linker has been instantiated - with tag=True parameter (False by default). - - * Function - a mapping from locals names to their bounded value, which may be a - constant like a string or an integer, or an astng node (on astng.Module, - astng.Class and astng.Function). - - * instance_attrs_type - as locals_type but for klass member attributes (only on astng.Class) - - * implements, - list of implemented interface _objects_ (only on astng.Class nodes) - """ - - def __init__(self, project, inherited_interfaces=0, tag=False): - IdGeneratorMixIn.__init__(self) - LocalsVisitor.__init__(self) - # take inherited interface in consideration or not - self.inherited_interfaces = inherited_interfaces - # tag nodes or not - self.tag = tag - # visited project - self.project = project - - - def visit_project(self, node): - """visit an astng.Project node - - * optionally tag the node with a unique id - """ - if self.tag: - node.uid = self.generate_id() - for module in node.modules: - self.visit(module) - - def visit_package(self, node): - """visit an astng.Package node - - * optionally tag the node with a unique id - """ - if self.tag: - node.uid = self.generate_id() - for subelmt in node.values(): - self.visit(subelmt) - - def visit_module(self, node): - """visit an astng.Module node - - * set the locals_type mapping - * set the depends mapping - * optionally tag the node with a unique id - """ - if hasattr(node, 'locals_type'): - return - node.locals_type = {} - node.depends = [] - if self.tag: - node.uid = self.generate_id() - - def visit_class(self, node): - """visit an astng.Class node - - * set the locals_type and instance_attrs_type mappings - * set the implements list and build it - * optionally tag the node with a unique id - """ - if hasattr(node, 'locals_type'): - return - node.locals_type = {} - if self.tag: - node.uid = self.generate_id() - # resolve ancestors - for baseobj in node.ancestors(recurs=False): - specializations = getattr(baseobj, 'specializations', []) - specializations.append(node) - baseobj.specializations = specializations - # resolve instance attributes - node.instance_attrs_type = {} - for assattrs in node.instance_attrs.values(): - for assattr in assattrs: - self.handle_assattr_type(assattr, node) - # resolve implemented interface - try: - node.implements = list(node.interfaces(self.inherited_interfaces)) - except InferenceError: - node.implements = () - - def visit_function(self, node): - """visit an astng.Function node - - * set the locals_type mapping - * optionally tag the node with a unique id - """ - if hasattr(node, 'locals_type'): - return - node.locals_type = {} - if self.tag: - node.uid = self.generate_id() - - link_project = visit_project - link_module = visit_module - link_class = visit_class - link_function = visit_function - - def visit_assname(self, node): - """visit an astng.AssName node - - handle locals_type - """ - # avoid double parsing done by different Linkers.visit - # running over the same project: - if hasattr(node, '_handled'): - return - node._handled = True - if node.name in node.frame(): - frame = node.frame() - else: - # the name has been defined as 'global' in the frame and belongs - # there. Btw the frame is not yet visited as the name is in the - # root locals; the frame hence has no locals_type attribute - frame = node.root() - try: - values = node.infered() - try: - already_infered = frame.locals_type[node.name] - for valnode in values: - if not valnode in already_infered: - already_infered.append(valnode) - except KeyError: - frame.locals_type[node.name] = values - except astng.InferenceError: - pass - - def handle_assattr_type(self, node, parent): - """handle an astng.AssAttr node - - handle instance_attrs_type - """ - try: - values = list(node.infer()) - try: - already_infered = parent.instance_attrs_type[node.attrname] - for valnode in values: - if not valnode in already_infered: - already_infered.append(valnode) - except KeyError: - parent.instance_attrs_type[node.attrname] = values - except astng.InferenceError: - pass - - def visit_import(self, node): - """visit an astng.Import node - - resolve module dependencies - """ - context_file = node.root().file - for name in node.names: - relative = is_relative(name[0], context_file) - self._imported_module(node, name[0], relative) - - - def visit_from(self, node): - """visit an astng.From node - - resolve module dependencies - """ - basename = node.modname - context_file = node.root().file - if context_file is not None: - relative = is_relative(basename, context_file) - else: - relative = False - for name in node.names: - if name[0] == '*': - continue - # analyze dependencies - fullname = '%s.%s' % (basename, name[0]) - if fullname.find('.') > -1: - try: - # XXX: don't use get_module_part, missing package precedence - fullname = get_module_part(fullname) - except ImportError: - continue - if fullname != basename: - self._imported_module(node, fullname, relative) - - - def compute_module(self, context_name, mod_path): - """return true if the module should be added to dependencies""" - package_dir = dirname(self.project.path) - if context_name == mod_path: - return 0 - elif is_standard_module(mod_path, (package_dir,)): - return 1 - return 0 - - # protected methods ######################################################## - - def _imported_module(self, node, mod_path, relative): - """notify an imported module, used to analyze dependencies - """ - module = node.root() - context_name = module.name - if relative: - mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]), - mod_path) - if self.compute_module(context_name, mod_path): - # handle dependencies - if not hasattr(module, 'depends'): - module.depends = [] - mod_paths = module.depends - if not mod_path in mod_paths: - mod_paths.append(mod_path) diff --git a/pylibs/logilab/common/COPYING b/pylibs/logilab/common/COPYING deleted file mode 100644 index d511905c..00000000 --- a/pylibs/logilab/common/COPYING +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/pylibs/logilab/common/COPYING.LESSER b/pylibs/logilab/common/COPYING.LESSER deleted file mode 100644 index 2d2d780e..00000000 --- a/pylibs/logilab/common/COPYING.LESSER +++ /dev/null @@ -1,510 +0,0 @@ - - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations -below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it -becomes a de-facto standard. To achieve this, non-free programs must -be allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control -compilation and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at least - three years, to give the same user the materials specified in - Subsection 6a, above, for a charge no more than the cost of - performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply, and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License -may add an explicit geographical distribution limitation excluding those -countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms -of the ordinary General Public License). - - To apply these terms, attach the following notices to the library. -It is safest to attach them to the start of each source file to most -effectively convey the exclusion of warranty; and each file should -have at least the "copyright" line and a pointer to where the full -notice is found. - - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or -your school, if any, to sign a "copyright disclaimer" for the library, -if necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James - Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! - - diff --git a/pylibs/logilab/common/README b/pylibs/logilab/common/README deleted file mode 100644 index 9eb6b92a..00000000 --- a/pylibs/logilab/common/README +++ /dev/null @@ -1,187 +0,0 @@ -Logilab's common library -======================== - -What's this ? -------------- - -This package contains some modules used by differents Logilab's projects. - -It is released under the GNU Lesser General Public License. - -There is no documentation available yet but the source code should be clean and -well documented. - -Designed to ease: - -* handling command line options and configuration files -* writing interactive command line tools -* manipulation of files and character strings -* manipulation of common structures such as graph, tree, and pattern such as visitor -* generating text and HTML reports -* accessing some external libraries such as OmniORB_, Pyro_... -* more... - - -Installation ------------- - -Extract the tarball, jump into the created directory and run :: - - python setup.py install - -For installation options, see :: - - python setup.py install --help - - -Provided modules ----------------- - -Here is a brief description of the available modules. - -Modules providing high-level features -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* `cache`, a cache implementation with a least recently used algorithm. - -* `changelog`, a tiny library to manipulate our simplified ChangeLog file format. - -* `clcommands`, high-level classes to define command line programs handling - different subcommands. It is based on `configuration` to get easy command line - / configuration file handling. - -* `cli`, a base class for interactive programs using the command line. - -* `configuration`, some classes to handle unified configuration from both - command line (using optparse) and configuration file (using ConfigParser). - -* `dbf`, read Visual Fox Pro DBF files. - -* `proc`, interface to Linux /proc. - -* `umessage`, unicode email support. - -* `ureports`, micro-reports, a way to create simple reports using python objects - without care of the final formatting. ReST and html formatters are provided. - - -Modules providing low-level functions and structures -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* `compat`, provides a transparent compatibility layer between different python - versions. - -* `date`, a set of date manipulation functions. - -* `daemon`, a daemon function and mix-in class to properly start an Unix daemon - process. - -* `decorators`, function decorators such as cached, timed... - -* `deprecation`, decorator, metaclass & all to mark functions / classes as - deprecated or moved - -* `fileutils`, some file / file path manipulation utilities. - -* `graph`, graph manipulations functions such as cycle detection, bases for dot - file generation. - -* `modutils`, python module manipulation functions. - -* `shellutils`, some powerful shell like functions to replace shell scripts with - python scripts. - -* `tasksqueue`, a prioritized tasks queue implementation. - -* `textutils`, some text manipulation functions (ansi colorization, line wrapping, - rest support...). - -* `tree`, base class to represent tree structure, and some others to make it - works with the visitor implementation (see below). - -* `visitor`, a generic visitor pattern implementation. - - -Modules extending some standard modules -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* `debugger`, `pdb` customization. - -* `logging_ext`, extensions to `logging` module such as a colorized formatter - and an easier initialization function. - -* `optik_ext`, defines some new option types (regexp, csv, color, date, etc.) - for `optik` / `optparse` - -* `xmlrpcutils`, auth support for XML-RPC - - -Modules extending some external modules -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* `corbautils`, useful functions for use with the OmniORB_ CORBA library. - -* `hg`, some Mercurial_ utility functions. - -* `pdf_ext`, pdf and fdf file manipulations, with pdftk. - -* `pyro_ext`, some Pyro_ utility functions. - -* `sphinx_ext`, Sphinx_ plugin defining a `autodocstring` directive. - -* `vcgutils` , utilities functions to generate file readable with Georg Sander's - vcg tool (Visualization of Compiler Graphs). - - -To be deprecated modules -~~~~~~~~~~~~~~~~~~~~~~~~ - -Those `logilab.common` modules will much probably be deprecated in future -versions: - -* `testlib`: use `unittest2`_ instead -* `pytest`: use `discover`_ instead -* `interface`: use `zope.interface`_ if you really want this -* `table`, `xmlutils`: is that used? -* `sphinxutils`: we won't go that way imo (i == syt) - - -Deprecated modules -~~~~~~~~~~~~~~~~~~ - -Those `logilab.common` modules are only there for backward compatibility. They -can go away at anytime. - -* `optparser`: use `clcommands` instead - -* `adbh`, `db`, `sqlgen`: see `logilab.database`_ instead - -* `contexts`: content move to `shellutils` - -* `html`: deprecated without replacement - - -Comments, support, bug reports ------------------------------- - -Project page http://www.logilab.org/project/logilab-common - -Use the python-projects@lists.logilab.org mailing list. Since we do not have -publicly available bug tracker yet, bug reports should be emailed -there too. - -You can subscribe to this mailing list at -http://lists.logilab.org/mailman/listinfo/python-projects - -Archives are available at -http://lists.logilab.org/pipermail/python-projects/ - - -.. _Pyro: http://pyro.sourceforge.net/ -.. _OmniORB: http://omniorb.sourceforge.net/ -.. _Mercurial: http://mercurial.selenic.com -.. _Sphinx: http://sphinx.pocoo.org/ -.. _`logilab.database`: http://www.logilab.org/project/logilab-database/ -.. _`unittest2`: http://pypi.python.org/pypi/unittest2 -.. _`discover`: http://pypi.python.org/pypi/discover -.. _`zope.interface`: http://pypi.python.org/pypi/zope.interface diff --git a/pylibs/logilab/common/README.Python3 b/pylibs/logilab/common/README.Python3 deleted file mode 100644 index 10009486..00000000 --- a/pylibs/logilab/common/README.Python3 +++ /dev/null @@ -1,29 +0,0 @@ -Python3 -======= - -Approach --------- - -We maintain a Python 2 base and use 2to3 to generate Python 3 code. - -2to3 is integrated into the distutils installation process and will be run as a -build step when invoked by the python3 interpreter:: - - python3 setup.py install - -Tests ------ - -Set your PYTHONPATH and run pytest3 against the test directory. - -Debian ------- - -For the Debian packaging of python3-logilab-common, you can use the debian.sid/ -content against the debian/ folder:: - - cp debian.sid/* debian/ - -Resources ---------- -http://wiki.python.org/moin/PortingPythonToPy3k diff --git a/pylibs/logilab/common/announce.txt b/pylibs/logilab/common/announce.txt deleted file mode 100644 index 97960f9b..00000000 --- a/pylibs/logilab/common/announce.txt +++ /dev/null @@ -1,25 +0,0 @@ -I'm pleased to announce the %(VERSION)s release of %(DISTNAME)s. - -What's new ? ------------- -%(CHANGELOG)s - - -What is %(DISTNAME)s ? ------------------------- -%(LONG_DESC)s - - -Home page ---------- -%(WEB)s - -Download --------- -%(FTP)s - -Mailing list ------------- -%(MAILINGLIST)s - -%(ADDITIONAL_DESCR)s diff --git a/pylibs/logilab/common/cache.py b/pylibs/logilab/common/cache.py deleted file mode 100644 index 11ed1370..00000000 --- a/pylibs/logilab/common/cache.py +++ /dev/null @@ -1,114 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Cache module, with a least recently used algorithm for the management of the -deletion of entries. - - - - -""" -__docformat__ = "restructuredtext en" - -from threading import Lock - -from logilab.common.decorators import locked - -_marker = object() - -class Cache(dict): - """A dictionary like cache. - - inv: - len(self._usage) <= self.size - len(self.data) <= self.size - """ - - def __init__(self, size=100): - """ Warning : Cache.__init__() != dict.__init__(). - Constructor does not take any arguments beside size. - """ - assert size >= 0, 'cache size must be >= 0 (0 meaning no caching)' - self.size = size - self._usage = [] - self._lock = Lock() - super(Cache, self).__init__() - - def _acquire(self): - self._lock.acquire() - - def _release(self): - self._lock.release() - - def _update_usage(self, key): - if not self._usage: - self._usage.append(key) - elif self._usage[-1] != key: - try: - self._usage.remove(key) - except ValueError: - # we are inserting a new key - # check the size of the dictionary - # and remove the oldest item in the cache - if self.size and len(self._usage) >= self.size: - super(Cache, self).__delitem__(self._usage[0]) - del self._usage[0] - self._usage.append(key) - else: - pass # key is already the most recently used key - - def __getitem__(self, key): - value = super(Cache, self).__getitem__(key) - self._update_usage(key) - return value - __getitem__ = locked(_acquire, _release)(__getitem__) - - def __setitem__(self, key, item): - # Just make sure that size > 0 before inserting a new item in the cache - if self.size > 0: - super(Cache, self).__setitem__(key, item) - self._update_usage(key) - __setitem__ = locked(_acquire, _release)(__setitem__) - - def __delitem__(self, key): - super(Cache, self).__delitem__(key) - self._usage.remove(key) - __delitem__ = locked(_acquire, _release)(__delitem__) - - def clear(self): - super(Cache, self).clear() - self._usage = [] - clear = locked(_acquire, _release)(clear) - - def pop(self, key, default=_marker): - if key in self: - self._usage.remove(key) - #if default is _marker: - # return super(Cache, self).pop(key) - return super(Cache, self).pop(key, default) - pop = locked(_acquire, _release)(pop) - - def popitem(self): - raise NotImplementedError() - - def setdefault(self, key, default=None): - raise NotImplementedError() - - def update(self, other): - raise NotImplementedError() - - diff --git a/pylibs/logilab/common/clcommands.py b/pylibs/logilab/common/clcommands.py deleted file mode 100644 index 411931bb..00000000 --- a/pylibs/logilab/common/clcommands.py +++ /dev/null @@ -1,332 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Helper functions to support command line tools providing more than -one command. - -e.g called as "tool command [options] args..." where and are -command'specific -""" - -__docformat__ = "restructuredtext en" - -import sys -import logging -from os.path import basename - -from logilab.common.configuration import Configuration -from logilab.common.logging_ext import init_log, get_threshold -from logilab.common.deprecation import deprecated - - -class BadCommandUsage(Exception): - """Raised when an unknown command is used or when a command is not - correctly used (bad options, too much / missing arguments...). - - Trigger display of command usage. - """ - -class CommandError(Exception): - """Raised when a command can't be processed and we want to display it and - exit, without traceback nor usage displayed. - """ - - -# command line access point #################################################### - -class CommandLine(dict): - """Usage: - - >>> LDI = cli.CommandLine('ldi', doc='Logilab debian installer', - version=version, rcfile=RCFILE) - >>> LDI.register(MyCommandClass) - >>> LDI.register(MyOtherCommandClass) - >>> LDI.run(sys.argv[1:]) - - Arguments: - - * `pgm`, the program name, default to `basename(sys.argv[0])` - - * `doc`, a short description of the command line tool - - * `copyright`, additional doc string that will be appended to the generated - doc - - * `version`, version number of string of the tool. If specified, global - --version option will be available. - - * `rcfile`, path to a configuration file. If specified, global --C/--rc-file - option will be available? self.rcfile = rcfile - - * `logger`, logger to propagate to commands, default to - `logging.getLogger(self.pgm))` - """ - def __init__(self, pgm=None, doc=None, copyright=None, version=None, - rcfile=None, logthreshold=logging.ERROR, - check_duplicated_command=True): - if pgm is None: - pgm = basename(sys.argv[0]) - self.pgm = pgm - self.doc = doc - self.copyright = copyright - self.version = version - self.rcfile = rcfile - self.logger = None - self.logthreshold = logthreshold - self.check_duplicated_command = check_duplicated_command - - def register(self, cls, force=False): - """register the given :class:`Command` subclass""" - assert not self.check_duplicated_command or force or not cls.name in self, \ - 'a command %s is already defined' % cls.name - self[cls.name] = cls - return cls - - def run(self, args): - """main command line access point: - * init logging - * handle global options (-h/--help, --version, -C/--rc-file) - * check command - * run command - - Terminate by :exc:`SystemExit` - """ - init_log(debug=True, # so that we use StreamHandler - logthreshold=self.logthreshold, - logformat='%(levelname)s: %(message)s') - try: - arg = args.pop(0) - except IndexError: - self.usage_and_exit(1) - if arg in ('-h', '--help'): - self.usage_and_exit(0) - if self.version is not None and arg in ('--version'): - print self.version - sys.exit(0) - rcfile = self.rcfile - if rcfile is not None and arg in ('-C', '--rc-file'): - try: - rcfile = args.pop(0) - arg = args.pop(0) - except IndexError: - self.usage_and_exit(1) - try: - command = self.get_command(arg) - except KeyError: - print 'ERROR: no %s command' % arg - print - self.usage_and_exit(1) - try: - sys.exit(command.main_run(args, rcfile)) - except KeyboardInterrupt, exc: - print 'Interrupted', - if str(exc): - print ': %s' % exc, - print - sys.exit(4) - except BadCommandUsage, err: - print 'ERROR:', err - print - print command.help() - sys.exit(1) - - def create_logger(self, handler, logthreshold=None): - logger = logging.Logger(self.pgm) - logger.handlers = [handler] - if logthreshold is None: - logthreshold = get_threshold(self.logthreshold) - logger.setLevel(logthreshold) - return logger - - def get_command(self, cmd, logger=None): - if logger is None: - logger = self.logger - if logger is None: - logger = self.logger = logging.getLogger(self.pgm) - logger.setLevel(get_threshold(self.logthreshold)) - return self[cmd](logger) - - def usage(self): - """display usage for the main program (i.e. when no command supplied) - and exit - """ - print 'usage:', self.pgm, - if self.rcfile: - print '[--rc-file=]', - print ' [options] ...' - if self.doc: - print '\n%s' % self.doc - print ''' -Type "%(pgm)s --help" for more information about a specific -command. Available commands are :\n''' % self.__dict__ - max_len = max([len(cmd) for cmd in self]) - padding = ' ' * max_len - for cmdname, cmd in sorted(self.items()): - if not cmd.hidden: - print ' ', (cmdname + padding)[:max_len], cmd.short_description() - if self.rcfile: - print ''' -Use --rc-file= / -C before the command -to specify a configuration file. Default to %s. -''' % self.rcfile - print '''%(pgm)s -h/--help - display this usage information and exit''' % self.__dict__ - if self.version: - print '''%(pgm)s -v/--version - display version configuration and exit''' % self.__dict__ - if self.copyright: - print '\n', self.copyright - - def usage_and_exit(self, status): - self.usage() - sys.exit(status) - - -# base command classes ######################################################### - -class Command(Configuration): - """Base class for command line commands. - - Class attributes: - - * `name`, the name of the command - - * `min_args`, minimum number of arguments, None if unspecified - - * `max_args`, maximum number of arguments, None if unspecified - - * `arguments`, string describing arguments, used in command usage - - * `hidden`, boolean flag telling if the command should be hidden, e.g. does - not appear in help's commands list - - * `options`, options list, as allowed by :mod:configuration - """ - - arguments = '' - name = '' - # hidden from help ? - hidden = False - # max/min args, None meaning unspecified - min_args = None - max_args = None - - @classmethod - def description(cls): - return cls.__doc__.replace(' ', '') - - @classmethod - def short_description(cls): - return cls.description().split('.')[0] - - def __init__(self, logger): - usage = '%%prog %s %s\n\n%s' % (self.name, self.arguments, - self.description()) - Configuration.__init__(self, usage=usage) - self.logger = logger - - def check_args(self, args): - """check command's arguments are provided""" - if self.min_args is not None and len(args) < self.min_args: - raise BadCommandUsage('missing argument') - if self.max_args is not None and len(args) > self.max_args: - raise BadCommandUsage('too many arguments') - - def main_run(self, args, rcfile=None): - """Run the command and return status 0 if everything went fine. - - If :exc:`CommandError` is raised by the underlying command, simply log - the error and return status 2. - - Any other exceptions, including :exc:`BadCommandUsage` will be - propagated. - """ - if rcfile: - self.load_file_configuration(rcfile) - args = self.load_command_line_configuration(args) - try: - self.check_args(args) - self.run(args) - except CommandError, err: - self.logger.error(err) - return 2 - return 0 - - def run(self, args): - """run the command with its specific arguments""" - raise NotImplementedError() - - -class ListCommandsCommand(Command): - """list available commands, useful for bash completion.""" - name = 'listcommands' - arguments = '[command]' - hidden = True - - def run(self, args): - """run the command with its specific arguments""" - if args: - command = args.pop() - cmd = _COMMANDS[command] - for optname, optdict in cmd.options: - print '--help' - print '--' + optname - else: - commands = sorted(_COMMANDS.keys()) - for command in commands: - cmd = _COMMANDS[command] - if not cmd.hidden: - print command - - -# deprecated stuff ############################################################# - -_COMMANDS = CommandLine() - -DEFAULT_COPYRIGHT = '''\ -Copyright (c) 2004-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -http://www.logilab.fr/ -- mailto:contact@logilab.fr''' - -@deprecated('use cls.register(cli)') -def register_commands(commands): - """register existing commands""" - for command_klass in commands: - _COMMANDS.register(command_klass) - -@deprecated('use args.pop(0)') -def main_run(args, doc=None, copyright=None, version=None): - """command line tool: run command specified by argument list (without the - program name). Raise SystemExit with status 0 if everything went fine. - - >>> main_run(sys.argv[1:]) - """ - _COMMANDS.doc = doc - _COMMANDS.copyright = copyright - _COMMANDS.version = version - _COMMANDS.run(args) - -@deprecated('use args.pop(0)') -def pop_arg(args_list, expected_size_after=None, msg="Missing argument"): - """helper function to get and check command line arguments""" - try: - value = args_list.pop(0) - except IndexError: - raise BadCommandUsage(msg) - if expected_size_after is not None and len(args_list) > expected_size_after: - raise BadCommandUsage('too many arguments') - return value - diff --git a/pylibs/logilab/common/cli.py b/pylibs/logilab/common/cli.py deleted file mode 100644 index 42837329..00000000 --- a/pylibs/logilab/common/cli.py +++ /dev/null @@ -1,208 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Command line interface helper classes. - -It provides some default commands, a help system, a default readline -configuration with completion and persistent history. - -Example:: - - class BookShell(CLIHelper): - - def __init__(self): - # quit and help are builtins - # CMD_MAP keys are commands, values are topics - self.CMD_MAP['pionce'] = _("Sommeil") - self.CMD_MAP['ronfle'] = _("Sommeil") - CLIHelper.__init__(self) - - help_do_pionce = ("pionce", "pionce duree", _("met ton corps en veille")) - def do_pionce(self): - print 'nap is good' - - help_do_ronfle = ("ronfle", "ronfle volume", _("met les autres en veille")) - def do_ronfle(self): - print 'fuuuuuuuuuuuu rhhhhhrhrhrrh' - - cl = BookShell() -""" - -__docformat__ = "restructuredtext en" - -from logilab.common.compat import raw_input, builtins -if not hasattr(builtins, '_'): - builtins._ = str - - -def init_readline(complete_method, histfile=None): - """Init the readline library if available.""" - try: - import readline - readline.parse_and_bind("tab: complete") - readline.set_completer(complete_method) - string = readline.get_completer_delims().replace(':', '') - readline.set_completer_delims(string) - if histfile is not None: - try: - readline.read_history_file(histfile) - except IOError: - pass - import atexit - atexit.register(readline.write_history_file, histfile) - except: - print 'readline is not available :-(' - - -class Completer : - """Readline completer.""" - - def __init__(self, commands): - self.list = commands - - def complete(self, text, state): - """Hook called by readline when is pressed.""" - n = len(text) - matches = [] - for cmd in self.list : - if cmd[:n] == text : - matches.append(cmd) - try: - return matches[state] - except IndexError: - return None - - -class CLIHelper: - """An abstract command line interface client which recognize commands - and provide an help system. - """ - - CMD_MAP = {'help': _("Others"), - 'quit': _("Others"), - } - CMD_PREFIX = '' - - def __init__(self, histfile=None) : - self._topics = {} - self.commands = None - self._completer = Completer(self._register_commands()) - init_readline(self._completer.complete, histfile) - - def run(self): - """loop on user input, exit on EOF""" - while True: - try: - line = raw_input('>>> ') - except EOFError: - print - break - s_line = line.strip() - if not s_line: - continue - args = s_line.split() - if args[0] in self.commands: - try: - cmd = 'do_%s' % self.commands[args[0]] - getattr(self, cmd)(*args[1:]) - except EOFError: - break - except: - import traceback - traceback.print_exc() - else: - try: - self.handle_line(s_line) - except: - import traceback - traceback.print_exc() - - def handle_line(self, stripped_line): - """Method to overload in the concrete class (should handle - lines which are not commands). - """ - raise NotImplementedError() - - - # private methods ######################################################### - - def _register_commands(self): - """ register available commands method and return the list of - commands name - """ - self.commands = {} - self._command_help = {} - commands = [attr[3:] for attr in dir(self) if attr[:3] == 'do_'] - for command in commands: - topic = self.CMD_MAP[command] - help_method = getattr(self, 'help_do_%s' % command) - self._topics.setdefault(topic, []).append(help_method) - self.commands[self.CMD_PREFIX + command] = command - self._command_help[command] = help_method - return self.commands.keys() - - def _print_help(self, cmd, syntax, explanation): - print _('Command %s') % cmd - print _('Syntax: %s') % syntax - print '\t', explanation - print - - - # predefined commands ##################################################### - - def do_help(self, command=None) : - """base input of the help system""" - if command in self._command_help: - self._print_help(*self._command_help[command]) - elif command is None or command not in self._topics: - print _("Use help or help .") - print _("Available topics are:") - topics = sorted(self._topics.keys()) - for topic in topics: - print '\t', topic - print - print _("Available commands are:") - commands = self.commands.keys() - commands.sort() - for command in commands: - print '\t', command[len(self.CMD_PREFIX):] - - else: - print _('Available commands about %s:') % command - print - for command_help_method in self._topics[command]: - try: - if callable(command_help_method): - self._print_help(*command_help_method()) - else: - self._print_help(*command_help_method) - except: - import traceback - traceback.print_exc() - print 'ERROR in help method %s'% ( - command_help_method.func_name) - - help_do_help = ("help", "help [topic|command]", - _("print help message for the given topic/command or \ -available topics when no argument")) - - def do_quit(self): - """quit the CLI""" - raise EOFError() - - def help_do_quit(self): - return ("quit", "quit", _("quit the application")) diff --git a/pylibs/logilab/common/contexts.py b/pylibs/logilab/common/contexts.py deleted file mode 100644 index d78c3274..00000000 --- a/pylibs/logilab/common/contexts.py +++ /dev/null @@ -1,5 +0,0 @@ -from warnings import warn -warn('logilab.common.contexts module is deprecated, use logilab.common.shellutils instead', - DeprecationWarning, stacklevel=1) - -from logilab.common.shellutils import tempfile, pushd diff --git a/pylibs/logilab/common/corbautils.py b/pylibs/logilab/common/corbautils.py deleted file mode 100644 index 8dfb2ba3..00000000 --- a/pylibs/logilab/common/corbautils.py +++ /dev/null @@ -1,117 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""A set of utility function to ease the use of OmniORBpy. - - - - -""" -__docformat__ = "restructuredtext en" - -from omniORB import CORBA, PortableServer -import CosNaming - -orb = None - -def get_orb(): - """ - returns a reference to the ORB. - The first call to the method initialized the ORB - This method is mainly used internally in the module. - """ - - global orb - if orb is None: - import sys - orb = CORBA.ORB_init(sys.argv, CORBA.ORB_ID) - return orb - -def get_root_context(): - """ - returns a reference to the NameService object. - This method is mainly used internally in the module. - """ - - orb = get_orb() - nss = orb.resolve_initial_references("NameService") - rootContext = nss._narrow(CosNaming.NamingContext) - assert rootContext is not None, "Failed to narrow root naming context" - return rootContext - -def register_object_name(object, namepath): - """ - Registers a object in the NamingService. - The name path is a list of 2-uples (id,kind) giving the path. - - For instance if the path of an object is [('foo',''),('bar','')], - it is possible to get a reference to the object using the URL - 'corbaname::hostname#foo/bar'. - [('logilab','rootmodule'),('chatbot','application'),('chatter','server')] - is mapped to - 'corbaname::hostname#logilab.rootmodule/chatbot.application/chatter.server' - - The get_object_reference() function can be used to resolve such a URL. - """ - context = get_root_context() - for id, kind in namepath[:-1]: - name = [CosNaming.NameComponent(id, kind)] - try: - context = context.bind_new_context(name) - except CosNaming.NamingContext.AlreadyBound, ex: - context = context.resolve(name)._narrow(CosNaming.NamingContext) - assert context is not None, \ - 'test context exists but is not a NamingContext' - - id, kind = namepath[-1] - name = [CosNaming.NameComponent(id, kind)] - try: - context.bind(name, object._this()) - except CosNaming.NamingContext.AlreadyBound, ex: - context.rebind(name, object._this()) - -def activate_POA(): - """ - This methods activates the Portable Object Adapter. - You need to call it to enable the reception of messages in your code, - on both the client and the server. - """ - orb = get_orb() - poa = orb.resolve_initial_references('RootPOA') - poaManager = poa._get_the_POAManager() - poaManager.activate() - -def run_orb(): - """ - Enters the ORB mainloop on the server. - You should not call this method on the client. - """ - get_orb().run() - -def get_object_reference(url): - """ - Resolves a corbaname URL to an object proxy. - See register_object_name() for examples URLs - """ - return get_orb().string_to_object(url) - -def get_object_string(host, namepath): - """given an host name and a name path as described in register_object_name, - return a corba string identifier - """ - strname = '/'.join(['.'.join(path_elt) for path_elt in namepath]) - return 'corbaname::%s#%s' % (host, strname) diff --git a/pylibs/logilab/common/daemon.py b/pylibs/logilab/common/daemon.py deleted file mode 100644 index 66a69601..00000000 --- a/pylibs/logilab/common/daemon.py +++ /dev/null @@ -1,100 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""A daemonize function (for Unices)""" - -__docformat__ = "restructuredtext en" - -import os -import errno -import signal -import sys -import time -import warnings - -def setugid(user): - """Change process user and group ID - - Argument is a numeric user id or a user name""" - try: - from pwd import getpwuid - passwd = getpwuid(int(user)) - except ValueError: - from pwd import getpwnam - passwd = getpwnam(user) - - if hasattr(os, 'initgroups'): # python >= 2.7 - os.initgroups(passwd.pw_name, passwd.pw_gid) - else: - import ctypes - if ctypes.CDLL(None).initgroups(passwd.pw_name, passwd.pw_gid) < 0: - err = ctypes.c_int.in_dll(ctypes.pythonapi,"errno").value - raise OSError(err, os.strerror(err), 'initgroups') - os.setgid(passwd.pw_gid) - os.setuid(passwd.pw_uid) - os.environ['HOME'] = passwd.pw_dir - - -def daemonize(pidfile=None, uid=None, umask=077): - """daemonize a Unix process. Set paranoid umask by default. - - Return 1 in the original process, 2 in the first fork, and None for the - second fork (eg daemon process). - """ - # http://www.faqs.org/faqs/unix-faq/programmer/faq/ - # - # fork so the parent can exit - if os.fork(): # launch child and... - return 1 - # disconnect from tty and create a new session - os.setsid() - # fork again so the parent, (the session group leader), can exit. - # as a non-session group leader, we can never regain a controlling - # terminal. - if os.fork(): # launch child again. - return 2 - # move to the root to avoit mount pb - os.chdir('/') - # set umask if specified - if umask is not None: - os.umask(umask) - # redirect standard descriptors - null = os.open('/dev/null', os.O_RDWR) - for i in range(3): - try: - os.dup2(null, i) - except OSError, e: - if e.errno != errno.EBADF: - raise - os.close(null) - # filter warnings - warnings.filterwarnings('ignore') - # write pid in a file - if pidfile: - # ensure the directory where the pid-file should be set exists (for - # instance /var/run/cubicweb may be deleted on computer restart) - piddir = os.path.dirname(pidfile) - if not os.path.exists(piddir): - os.makedirs(piddir) - f = file(pidfile, 'w') - f.write(str(os.getpid())) - f.close() - os.chmod(pidfile, 0644) - # change process uid - if uid: - setugid(uid) - return None diff --git a/pylibs/logilab/common/date.py b/pylibs/logilab/common/date.py deleted file mode 100644 index b069a6fd..00000000 --- a/pylibs/logilab/common/date.py +++ /dev/null @@ -1,327 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Date manipulation helper functions.""" -from __future__ import division - -__docformat__ = "restructuredtext en" - -import math -import re -from locale import getpreferredencoding -from datetime import date, time, datetime, timedelta -from time import strptime as time_strptime -from calendar import monthrange, timegm - -try: - from mx.DateTime import RelativeDateTime, Date, DateTimeType -except ImportError: - endOfMonth = None - DateTimeType = datetime -else: - endOfMonth = RelativeDateTime(months=1, day=-1) - -# NOTE: should we implement a compatibility layer between date representations -# as we have in lgc.db ? - -FRENCH_FIXED_HOLIDAYS = { - 'jour_an': '%s-01-01', - 'fete_travail': '%s-05-01', - 'armistice1945': '%s-05-08', - 'fete_nat': '%s-07-14', - 'assomption': '%s-08-15', - 'toussaint': '%s-11-01', - 'armistice1918': '%s-11-11', - 'noel': '%s-12-25', - } - -FRENCH_MOBILE_HOLIDAYS = { - 'paques2004': '2004-04-12', - 'ascension2004': '2004-05-20', - 'pentecote2004': '2004-05-31', - - 'paques2005': '2005-03-28', - 'ascension2005': '2005-05-05', - 'pentecote2005': '2005-05-16', - - 'paques2006': '2006-04-17', - 'ascension2006': '2006-05-25', - 'pentecote2006': '2006-06-05', - - 'paques2007': '2007-04-09', - 'ascension2007': '2007-05-17', - 'pentecote2007': '2007-05-28', - - 'paques2008': '2008-03-24', - 'ascension2008': '2008-05-01', - 'pentecote2008': '2008-05-12', - - 'paques2009': '2009-04-13', - 'ascension2009': '2009-05-21', - 'pentecote2009': '2009-06-01', - - 'paques2010': '2010-04-05', - 'ascension2010': '2010-05-13', - 'pentecote2010': '2010-05-24', - - 'paques2011': '2011-04-25', - 'ascension2011': '2011-06-02', - 'pentecote2011': '2011-06-13', - - 'paques2012': '2012-04-09', - 'ascension2012': '2012-05-17', - 'pentecote2012': '2012-05-28', - } - -# XXX this implementation cries for multimethod dispatching - -def get_step(dateobj, nbdays=1): - # assume date is either a python datetime or a mx.DateTime object - if isinstance(dateobj, date): - return ONEDAY * nbdays - return nbdays # mx.DateTime is ok with integers - -def datefactory(year, month, day, sampledate): - # assume date is either a python datetime or a mx.DateTime object - if isinstance(sampledate, datetime): - return datetime(year, month, day) - if isinstance(sampledate, date): - return date(year, month, day) - return Date(year, month, day) - -def weekday(dateobj): - # assume date is either a python datetime or a mx.DateTime object - if isinstance(dateobj, date): - return dateobj.weekday() - return dateobj.day_of_week - -def str2date(datestr, sampledate): - # NOTE: datetime.strptime is not an option until we drop py2.4 compat - year, month, day = [int(chunk) for chunk in datestr.split('-')] - return datefactory(year, month, day, sampledate) - -def days_between(start, end): - if isinstance(start, date): - delta = end - start - # datetime.timedelta.days is always an integer (floored) - if delta.seconds: - return delta.days + 1 - return delta.days - else: - return int(math.ceil((end - start).days)) - -def get_national_holidays(begin, end): - """return french national days off between begin and end""" - begin = datefactory(begin.year, begin.month, begin.day, begin) - end = datefactory(end.year, end.month, end.day, end) - holidays = [str2date(datestr, begin) - for datestr in FRENCH_MOBILE_HOLIDAYS.values()] - for year in xrange(begin.year, end.year+1): - for datestr in FRENCH_FIXED_HOLIDAYS.values(): - date = str2date(datestr % year, begin) - if date not in holidays: - holidays.append(date) - return [day for day in holidays if begin <= day < end] - -def add_days_worked(start, days): - """adds date but try to only take days worked into account""" - step = get_step(start) - weeks, plus = divmod(days, 5) - end = start + ((weeks * 7) + plus) * step - if weekday(end) >= 5: # saturday or sunday - end += (2 * step) - end += len([x for x in get_national_holidays(start, end + step) - if weekday(x) < 5]) * step - if weekday(end) >= 5: # saturday or sunday - end += (2 * step) - return end - -def nb_open_days(start, end): - assert start <= end - step = get_step(start) - days = days_between(start, end) - weeks, plus = divmod(days, 7) - if weekday(start) > weekday(end): - plus -= 2 - elif weekday(end) == 6: - plus -= 1 - open_days = weeks * 5 + plus - nb_week_holidays = len([x for x in get_national_holidays(start, end+step) - if weekday(x) < 5 and x < end]) - open_days -= nb_week_holidays - if open_days < 0: - return 0 - return open_days - -def date_range(begin, end, incday=None, incmonth=None): - """yields each date between begin and end - - :param begin: the start date - :param end: the end date - :param incr: the step to use to iterate over dates. Default is - one day. - :param include: None (means no exclusion) or a function taking a - date as parameter, and returning True if the date - should be included. - - When using mx datetime, you should *NOT* use incmonth argument, use instead - oneDay, oneHour, oneMinute, oneSecond, oneWeek or endOfMonth (to enumerate - months) as `incday` argument - """ - assert not (incday and incmonth) - begin = todate(begin) - end = todate(end) - if incmonth: - while begin < end: - begin = next_month(begin, incmonth) - yield begin - else: - incr = get_step(begin, incday or 1) - while begin < end: - yield begin - begin += incr - -# makes py datetime usable ##################################################### - -ONEDAY = timedelta(days=1) -ONEWEEK = timedelta(days=7) - -try: - strptime = datetime.strptime -except AttributeError: # py < 2.5 - from time import strptime as time_strptime - def strptime(value, format): - return datetime(*time_strptime(value, format)[:6]) - -def strptime_time(value, format='%H:%M'): - return time(*time_strptime(value, format)[3:6]) - -def todate(somedate): - """return a date from a date (leaving unchanged) or a datetime""" - if isinstance(somedate, datetime): - return date(somedate.year, somedate.month, somedate.day) - assert isinstance(somedate, (date, DateTimeType)), repr(somedate) - return somedate - -def totime(somedate): - """return a time from a time (leaving unchanged), date or datetime""" - # XXX mx compat - if not isinstance(somedate, time): - return time(somedate.hour, somedate.minute, somedate.second) - assert isinstance(somedate, (time)), repr(somedate) - return somedate - -def todatetime(somedate): - """return a date from a date (leaving unchanged) or a datetime""" - # take care, datetime is a subclass of date - if isinstance(somedate, datetime): - return somedate - assert isinstance(somedate, (date, DateTimeType)), repr(somedate) - return datetime(somedate.year, somedate.month, somedate.day) - -def datetime2ticks(somedate): - return timegm(somedate.timetuple()) * 1000 - -def ticks2datetime(ticks): - miliseconds, microseconds = divmod(ticks, 1000) - try: - return datetime.fromtimestamp(miliseconds) - except (ValueError, OverflowError): - epoch = datetime.fromtimestamp(0) - nb_days, seconds = divmod(int(miliseconds), 86400) - delta = timedelta(nb_days, seconds=seconds, microseconds=microseconds) - try: - return epoch + delta - except (ValueError, OverflowError): - raise - -def days_in_month(somedate): - return monthrange(somedate.year, somedate.month)[1] - -def days_in_year(somedate): - feb = date(somedate.year, 2, 1) - if days_in_month(feb) == 29: - return 366 - else: - return 365 - -def previous_month(somedate, nbmonth=1): - while nbmonth: - somedate = first_day(somedate) - ONEDAY - nbmonth -= 1 - return somedate - -def next_month(somedate, nbmonth=1): - while nbmonth: - somedate = last_day(somedate) + ONEDAY - nbmonth -= 1 - return somedate - -def first_day(somedate): - return date(somedate.year, somedate.month, 1) - -def last_day(somedate): - return date(somedate.year, somedate.month, days_in_month(somedate)) - -def ustrftime(somedate, fmt='%Y-%m-%d'): - """like strftime, but returns a unicode string instead of an encoded - string which' may be problematic with localized date. - - encoding is guessed by locale.getpreferredencoding() - """ - encoding = getpreferredencoding(do_setlocale=False) or 'UTF-8' - try: - return unicode(somedate.strftime(str(fmt)), encoding) - except ValueError, exc: - if somedate.year >= 1900: - raise - # datetime is not happy with dates before 1900 - # we try to work around this, assuming a simple - # format string - fields = {'Y': somedate.year, - 'm': somedate.month, - 'd': somedate.day, - } - if isinstance(somedate, datetime): - fields.update({'H': somedate.hour, - 'M': somedate.minute, - 'S': somedate.second}) - fmt = re.sub('%([YmdHMS])', r'%(\1)02d', fmt) - return unicode(fmt) % fields - -def utcdatetime(dt): - if dt.tzinfo is None: - return dt - return datetime(*dt.utctimetuple()[:7]) - -def utctime(dt): - if dt.tzinfo is None: - return dt - return (dt + dt.utcoffset() + dt.dst()).replace(tzinfo=None) - -def datetime_to_seconds(date): - """return the number of seconds since the begining of the day for that date - """ - return date.second+60*date.minute + 3600*date.hour - -def timedelta_to_days(delta): - """return the time delta as a number of seconds""" - return delta.days + delta.seconds / (3600*24) - -def timedelta_to_seconds(delta): - """return the time delta as a fraction of days""" - return delta.days*(3600*24) + delta.seconds diff --git a/pylibs/logilab/common/dbf.py b/pylibs/logilab/common/dbf.py deleted file mode 100644 index 8def2d2e..00000000 --- a/pylibs/logilab/common/dbf.py +++ /dev/null @@ -1,229 +0,0 @@ -# -*- coding: utf-8 -*- -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""This is a DBF reader which reads Visual Fox Pro DBF format with Memo field - -Usage: - ->>> rec = readDbf('test.dbf') ->>> for line in rec: ->>> print line['name'] - - -:date: 13/07/2007 - -http://www.physics.ox.ac.uk/users/santoso/Software.Repository.html -page says code is "available as is without any warranty or support". -""" - -import struct -import os, os.path -import sys -import csv -import tempfile -import ConfigParser - -class Dbase: - def __init__(self): - self.fdb = None - self.fmemo = None - self.db_data = None - self.memo_data = None - self.fields = None - self.num_records = 0 - self.header = None - self.memo_file = '' - self.memo_header = None - self.memo_block_size = 0 - self.memo_header_len = 0 - - def _drop_after_NULL(self, txt): - for i in range(0, len(txt)): - if ord(struct.unpack('c', txt[i])[0])==0: - return txt[:i] - return txt - - def _reverse_endian(self, num): - if not len(num): - return 0 - val = struct.unpack('L', val[0]) - val = struct.unpack('>L', val) - return val[0] - - def _assign_ids(self, lst, ids): - result = {} - idx = 0 - for item in lst: - id = ids[idx] - result[id] = item - idx += 1 - return result - - def open(self, db_name): - filesize = os.path.getsize(db_name) - if filesize <= 68: - raise IOError, 'The file is not large enough to be a dbf file' - - self.fdb = open(db_name, 'rb') - - self.memo_file = '' - if os.path.isfile(db_name[0:-1] + 't'): - self.memo_file = db_name[0:-1] + 't' - elif os.path.isfile(db_name[0:-3] + 'fpt'): - self.memo_file = db_name[0:-3] + 'fpt' - - if self.memo_file: - #Read memo file - self.fmemo = open(self.memo_file, 'rb') - self.memo_data = self.fmemo.read() - self.memo_header = self._assign_ids(struct.unpack('>6x1H', self.memo_data[:8]), ['Block size']) - block_size = self.memo_header['Block size'] - if not block_size: - block_size = 512 - self.memo_block_size = block_size - self.memo_header_len = block_size - memo_size = os.path.getsize(self.memo_file) - - #Start reading data file - data = self.fdb.read(32) - self.header = self._assign_ids(struct.unpack(' self.num_records: - raise Exception, 'Unable to extract data outside the range' - - offset = self.header['Record Size'] * rec_no - data = self.db_data[offset:offset+self.row_len] - record = self._assign_ids(struct.unpack(self.row_format, data), self.row_ids) - - if self.memo_file: - for key in self.fields: - field = self.fields[key] - f_type = field['Field Type'] - f_name = field['Field Name'] - c_data = record[f_name] - - if f_type=='M' or f_type=='G' or f_type=='B' or f_type=='P': - c_data = self._reverse_endian(c_data) - if c_data: - record[f_name] = self.read_memo(c_data-1).strip() - else: - record[f_name] = c_data.strip() - return record - - def read_memo_record(self, num, in_length): - """ - Read the record of given number. The second parameter is the length of - the record to read. It can be undefined, meaning read the whole record, - and it can be negative, meaning at most the length - """ - if in_length < 0: - in_length = -self.memo_block_size - - offset = self.memo_header_len + num * self.memo_block_size - self.fmemo.seek(offset) - if in_length<0: - in_length = -in_length - if in_length==0: - return '' - return self.fmemo.read(in_length) - - def read_memo(self, num): - result = '' - buffer = self.read_memo_record(num, -1) - if len(buffer)<=0: - return '' - length = struct.unpack('>L', buffer[4:4+4])[0] + 8 - - block_size = self.memo_block_size - if length < block_size: - return buffer[8:length] - rest_length = length - block_size - rest_data = self.read_memo_record(num+1, rest_length) - if len(rest_data)<=0: - return '' - return buffer[8:] + rest_data - -def readDbf(filename): - """ - Read the DBF file specified by the filename and - return the records as a list of dictionary. - - :param: filename File name of the DBF - :return: List of rows - """ - db = Dbase() - db.open(filename) - num = db.get_numrecords() - rec = [] - for i in range(0, num): - record = db.get_record_with_names(i) - rec.append(record) - db.close() - return rec - -if __name__=='__main__': - rec = readDbf('dbf/sptable.dbf') - for line in rec: - print '%s %s' % (line['GENUS'].strip(), line['SPECIES'].strip()) diff --git a/pylibs/logilab/common/debugger.py b/pylibs/logilab/common/debugger.py deleted file mode 100644 index 75563227..00000000 --- a/pylibs/logilab/common/debugger.py +++ /dev/null @@ -1,210 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Customized version of pdb's default debugger. - -- sets up a history file -- uses ipython if available to colorize lines of code -- overrides list command to search for current block instead - of using 5 lines of context - - - - -""" -__docformat__ = "restructuredtext en" - -try: - import readline -except ImportError: - readline = None -import os -import os.path as osp -import sys -from pdb import Pdb -from cStringIO import StringIO -import inspect - -try: - from IPython import PyColorize -except ImportError: - def colorize(source, *args): - """fallback colorize function""" - return source - def colorize_source(source, *args): - return source -else: - def colorize(source, start_lineno, curlineno): - """colorize and annotate source with linenos - (as in pdb's list command) - """ - parser = PyColorize.Parser() - output = StringIO() - parser.format(source, output) - annotated = [] - for index, line in enumerate(output.getvalue().splitlines()): - lineno = index + start_lineno - if lineno == curlineno: - annotated.append('%4s\t->\t%s' % (lineno, line)) - else: - annotated.append('%4s\t\t%s' % (lineno, line)) - return '\n'.join(annotated) - - def colorize_source(source): - """colorize given source""" - parser = PyColorize.Parser() - output = StringIO() - parser.format(source, output) - return output.getvalue() - - -def getsource(obj): - """Return the text of the source code for an object. - - The argument may be a module, class, method, function, traceback, frame, - or code object. The source code is returned as a single string. An - IOError is raised if the source code cannot be retrieved.""" - lines, lnum = inspect.getsourcelines(obj) - return ''.join(lines), lnum - - -################################################################ -class Debugger(Pdb): - """custom debugger - - - sets up a history file - - uses ipython if available to colorize lines of code - - overrides list command to search for current block instead - of using 5 lines of context - """ - def __init__(self, tcbk=None): - Pdb.__init__(self) - self.reset() - if tcbk: - while tcbk.tb_next is not None: - tcbk = tcbk.tb_next - self._tcbk = tcbk - self._histfile = os.path.expanduser("~/.pdbhist") - - def setup_history_file(self): - """if readline is available, read pdb history file - """ - if readline is not None: - try: - # XXX try..except shouldn't be necessary - # read_history_file() can accept None - readline.read_history_file(self._histfile) - except IOError: - pass - - def start(self): - """starts the interactive mode""" - self.interaction(self._tcbk.tb_frame, self._tcbk) - - def setup(self, frame, tcbk): - """setup hook: set up history file""" - self.setup_history_file() - Pdb.setup(self, frame, tcbk) - - def set_quit(self): - """quit hook: save commands in the history file""" - if readline is not None: - readline.write_history_file(self._histfile) - Pdb.set_quit(self) - - def complete_p(self, text, line, begin_idx, end_idx): - """provide variable names completion for the ``p`` command""" - namespace = dict(self.curframe.f_globals) - namespace.update(self.curframe.f_locals) - if '.' in text: - return self.attr_matches(text, namespace) - return [varname for varname in namespace if varname.startswith(text)] - - - def attr_matches(self, text, namespace): - """implementation coming from rlcompleter.Completer.attr_matches - Compute matches when text contains a dot. - - Assuming the text is of the form NAME.NAME....[NAME], and is - evaluatable in self.namespace, it will be evaluated and its attributes - (as revealed by dir()) are used as possible completions. (For class - instances, class members are also considered.) - - WARNING: this can still invoke arbitrary C code, if an object - with a __getattr__ hook is evaluated. - - """ - import re - m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) - if not m: - return - expr, attr = m.group(1, 3) - object = eval(expr, namespace) - words = dir(object) - if hasattr(object, '__class__'): - words.append('__class__') - words = words + self.get_class_members(object.__class__) - matches = [] - n = len(attr) - for word in words: - if word[:n] == attr and word != "__builtins__": - matches.append("%s.%s" % (expr, word)) - return matches - - def get_class_members(self, klass): - """implementation coming from rlcompleter.get_class_members""" - ret = dir(klass) - if hasattr(klass, '__bases__'): - for base in klass.__bases__: - ret = ret + self.get_class_members(base) - return ret - - ## specific / overridden commands - def do_list(self, arg): - """overrides default list command to display the surrounding block - instead of 5 lines of context - """ - self.lastcmd = 'list' - if not arg: - try: - source, start_lineno = getsource(self.curframe) - print colorize(''.join(source), start_lineno, - self.curframe.f_lineno) - except KeyboardInterrupt: - pass - except IOError: - Pdb.do_list(self, arg) - else: - Pdb.do_list(self, arg) - do_l = do_list - - def do_open(self, arg): - """opens source file corresponding to the current stack level""" - filename = self.curframe.f_code.co_filename - lineno = self.curframe.f_lineno - cmd = 'emacsclient --no-wait +%s %s' % (lineno, filename) - os.system(cmd) - - do_o = do_open - -def pm(): - """use our custom debugger""" - dbg = Debugger(sys.last_traceback) - dbg.start() - -def set_trace(): - Debugger().set_trace(sys._getframe().f_back) diff --git a/pylibs/logilab/common/deprecation.py b/pylibs/logilab/common/deprecation.py deleted file mode 100644 index c14bd2af..00000000 --- a/pylibs/logilab/common/deprecation.py +++ /dev/null @@ -1,130 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Deprecation utilities.""" - -__docformat__ = "restructuredtext en" - -import sys -from warnings import warn - -class class_deprecated(type): - """metaclass to print a warning on instantiation of a deprecated class""" - - def __call__(cls, *args, **kwargs): - msg = getattr(cls, "__deprecation_warning__", - "%(cls)s is deprecated") % {'cls': cls.__name__} - warn(msg, DeprecationWarning, stacklevel=2) - return type.__call__(cls, *args, **kwargs) - - -def class_renamed(old_name, new_class, message=None): - """automatically creates a class which fires a DeprecationWarning - when instantiated. - - >>> Set = class_renamed('Set', set, 'Set is now replaced by set') - >>> s = Set() - sample.py:57: DeprecationWarning: Set is now replaced by set - s = Set() - >>> - """ - clsdict = {} - if message is None: - message = '%s is deprecated, use %s' % (old_name, new_class.__name__) - clsdict['__deprecation_warning__'] = message - try: - # new-style class - return class_deprecated(old_name, (new_class,), clsdict) - except (NameError, TypeError): - # old-style class - class DeprecatedClass(new_class): - """FIXME: There might be a better way to handle old/new-style class - """ - def __init__(self, *args, **kwargs): - warn(message, DeprecationWarning, stacklevel=2) - new_class.__init__(self, *args, **kwargs) - return DeprecatedClass - - -def class_moved(new_class, old_name=None, message=None): - """nice wrapper around class_renamed when a class has been moved into - another module - """ - if old_name is None: - old_name = new_class.__name__ - if message is None: - message = 'class %s is now available as %s.%s' % ( - old_name, new_class.__module__, new_class.__name__) - return class_renamed(old_name, new_class, message) - -def deprecated(reason=None, stacklevel=2, name=None, doc=None): - """Decorator that raises a DeprecationWarning to print a message - when the decorated function is called. - """ - def deprecated_decorator(func): - message = reason or 'The function "%s" is deprecated' - if '%s' in message: - message = message % func.func_name - def wrapped(*args, **kwargs): - warn(message, DeprecationWarning, stacklevel=stacklevel) - return func(*args, **kwargs) - try: - wrapped.__name__ = name or func.__name__ - except TypeError: # readonly attribute in 2.3 - pass - wrapped.__doc__ = doc or func.__doc__ - return wrapped - return deprecated_decorator - -def moved(modpath, objname): - """use to tell that a callable has been moved to a new module. - - It returns a callable wrapper, so that when its called a warning is printed - telling where the object can be found, import is done (and not before) and - the actual object is called. - - NOTE: the usage is somewhat limited on classes since it will fail if the - wrapper is use in a class ancestors list, use the `class_moved` function - instead (which has no lazy import feature though). - """ - def callnew(*args, **kwargs): - from logilab.common.modutils import load_module_from_name - message = "object %s has been moved to module %s" % (objname, modpath) - warn(message, DeprecationWarning, stacklevel=2) - m = load_module_from_name(modpath) - return getattr(m, objname)(*args, **kwargs) - return callnew - - - -class DeprecationWrapper(object): - """proxy to print a warning on access to any attribute of the wrapped object - """ - def __init__(self, proxied, msg=None): - self._proxied = proxied - self._msg = msg - - def __getattr__(self, attr): - warn(self._msg, DeprecationWarning, stacklevel=2) - return getattr(self._proxied, attr) - - def __setattr__(self, attr, value): - if attr in ('_proxied', '_msg'): - self.__dict__[attr] = value - else: - warn(self._msg, DeprecationWarning, stacklevel=2) - setattr(self._proxied, attr, value) diff --git a/pylibs/logilab/common/fileutils.py b/pylibs/logilab/common/fileutils.py deleted file mode 100644 index 4ac92702..00000000 --- a/pylibs/logilab/common/fileutils.py +++ /dev/null @@ -1,402 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""File and file-path manipulation utilities. - -:group path manipulation: first_level_directory, relative_path, is_binary,\ -get_by_ext, remove_dead_links -:group file manipulation: norm_read, norm_open, lines, stream_lines, lines,\ -write_open_mode, ensure_fs_mode, export -:sort: path manipulation, file manipulation -""" -__docformat__ = "restructuredtext en" - -import sys -import shutil -import mimetypes -from os.path import isabs, isdir, islink, split, exists, normpath, join -from os.path import abspath -from os import sep, mkdir, remove, listdir, stat, chmod, walk -from stat import ST_MODE, S_IWRITE -from cStringIO import StringIO - -from logilab.common import STD_BLACKLIST as BASE_BLACKLIST, IGNORED_EXTENSIONS -from logilab.common.shellutils import find -from logilab.common.deprecation import deprecated -from logilab.common.compat import FileIO, any - -def first_level_directory(path): - """Return the first level directory of a path. - - >>> first_level_directory('home/syt/work') - 'home' - >>> first_level_directory('/home/syt/work') - '/' - >>> first_level_directory('work') - 'work' - >>> - - :type path: str - :param path: the path for which we want the first level directory - - :rtype: str - :return: the first level directory appearing in `path` - """ - head, tail = split(path) - while head and tail: - head, tail = split(head) - if tail: - return tail - # path was absolute, head is the fs root - return head - -def abspath_listdir(path): - """Lists path's content using absolute paths. - - >>> os.listdir('/home') - ['adim', 'alf', 'arthur', 'auc'] - >>> abspath_listdir('/home') - ['/home/adim', '/home/alf', '/home/arthur', '/home/auc'] - """ - path = abspath(path) - return [join(path, filename) for filename in listdir(path)] - - -def is_binary(filename): - """Return true if filename may be a binary file, according to it's - extension. - - :type filename: str - :param filename: the name of the file - - :rtype: bool - :return: - true if the file is a binary file (actually if it's mime type - isn't beginning by text/) - """ - try: - return not mimetypes.guess_type(filename)[0].startswith('text') - except AttributeError: - return 1 - - -def write_open_mode(filename): - """Return the write mode that should used to open file. - - :type filename: str - :param filename: the name of the file - - :rtype: str - :return: the mode that should be use to open the file ('w' or 'wb') - """ - if is_binary(filename): - return 'wb' - return 'w' - - -def ensure_fs_mode(filepath, desired_mode=S_IWRITE): - """Check that the given file has the given mode(s) set, else try to - set it. - - :type filepath: str - :param filepath: path of the file - - :type desired_mode: int - :param desired_mode: - ORed flags describing the desired mode. Use constants from the - `stat` module for file permission's modes - """ - mode = stat(filepath)[ST_MODE] - if not mode & desired_mode: - chmod(filepath, mode | desired_mode) - - -# XXX (syt) unused? kill? -class ProtectedFile(FileIO): - """A special file-object class that automatically does a 'chmod +w' when - needed. - - XXX: for now, the way it is done allows 'normal file-objects' to be - created during the ProtectedFile object lifetime. - One way to circumvent this would be to chmod / unchmod on each - write operation. - - One other way would be to : - - - catch the IOError in the __init__ - - - if IOError, then create a StringIO object - - - each write operation writes in this StringIO object - - - on close()/del(), write/append the StringIO content to the file and - do the chmod only once - """ - def __init__(self, filepath, mode): - self.original_mode = stat(filepath)[ST_MODE] - self.mode_changed = False - if mode in ('w', 'a', 'wb', 'ab'): - if not self.original_mode & S_IWRITE: - chmod(filepath, self.original_mode | S_IWRITE) - self.mode_changed = True - FileIO.__init__(self, filepath, mode) - - def _restore_mode(self): - """restores the original mode if needed""" - if self.mode_changed: - chmod(self.name, self.original_mode) - # Don't re-chmod in case of several restore - self.mode_changed = False - - def close(self): - """restore mode before closing""" - self._restore_mode() - FileIO.close(self) - - def __del__(self): - if not self.closed: - self.close() - - -class UnresolvableError(Exception): - """Exception raised by relative path when it's unable to compute relative - path between two paths. - """ - -def relative_path(from_file, to_file): - """Try to get a relative path from `from_file` to `to_file` - (path will be absolute if to_file is an absolute file). This function - is useful to create link in `from_file` to `to_file`. This typical use - case is used in this function description. - - If both files are relative, they're expected to be relative to the same - directory. - - >>> relative_path( from_file='toto/index.html', to_file='index.html') - '../index.html' - >>> relative_path( from_file='index.html', to_file='toto/index.html') - 'toto/index.html' - >>> relative_path( from_file='tutu/index.html', to_file='toto/index.html') - '../toto/index.html' - >>> relative_path( from_file='toto/index.html', to_file='/index.html') - '/index.html' - >>> relative_path( from_file='/toto/index.html', to_file='/index.html') - '../index.html' - >>> relative_path( from_file='/toto/index.html', to_file='/toto/summary.html') - 'summary.html' - >>> relative_path( from_file='index.html', to_file='index.html') - '' - >>> relative_path( from_file='/index.html', to_file='toto/index.html') - Traceback (most recent call last): - File "", line 1, in ? - File "", line 37, in relative_path - UnresolvableError - >>> relative_path( from_file='/index.html', to_file='/index.html') - '' - >>> - - :type from_file: str - :param from_file: source file (where links will be inserted) - - :type to_file: str - :param to_file: target file (on which links point) - - :raise UnresolvableError: if it has been unable to guess a correct path - - :rtype: str - :return: the relative path of `to_file` from `from_file` - """ - from_file = normpath(from_file) - to_file = normpath(to_file) - if from_file == to_file: - return '' - if isabs(to_file): - if not isabs(from_file): - return to_file - elif isabs(from_file): - raise UnresolvableError() - from_parts = from_file.split(sep) - to_parts = to_file.split(sep) - idem = 1 - result = [] - while len(from_parts) > 1: - dirname = from_parts.pop(0) - if idem and len(to_parts) > 1 and dirname == to_parts[0]: - to_parts.pop(0) - else: - idem = 0 - result.append('..') - result += to_parts - return sep.join(result) - - -def norm_read(path): - """Return the content of the file with normalized line feeds. - - :type path: str - :param path: path to the file to read - - :rtype: str - :return: the content of the file with normalized line feeds - """ - return open(path, 'U').read() -norm_read = deprecated("use \"open(path, 'U').read()\"")(norm_read) - -def norm_open(path): - """Return a stream for a file with content with normalized line feeds. - - :type path: str - :param path: path to the file to open - - :rtype: file or StringIO - :return: the opened file with normalized line feeds - """ - return open(path, 'U') -norm_open = deprecated("use \"open(path, 'U')\"")(norm_open) - -def lines(path, comments=None): - """Return a list of non empty lines in the file located at `path`. - - :type path: str - :param path: path to the file - - :type comments: str or None - :param comments: - optional string which can be used to comment a line in the file - (i.e. lines starting with this string won't be returned) - - :rtype: list - :return: - a list of stripped line in the file, without empty and commented - lines - - :warning: at some point this function will probably return an iterator - """ - stream = open(path, 'U') - result = stream_lines(stream, comments) - stream.close() - return result - - -def stream_lines(stream, comments=None): - """Return a list of non empty lines in the given `stream`. - - :type stream: object implementing 'xreadlines' or 'readlines' - :param stream: file like object - - :type comments: str or None - :param comments: - optional string which can be used to comment a line in the file - (i.e. lines starting with this string won't be returned) - - :rtype: list - :return: - a list of stripped line in the file, without empty and commented - lines - - :warning: at some point this function will probably return an iterator - """ - try: - readlines = stream.xreadlines - except AttributeError: - readlines = stream.readlines - result = [] - for line in readlines(): - line = line.strip() - if line and (comments is None or not line.startswith(comments)): - result.append(line) - return result - - -def export(from_dir, to_dir, - blacklist=BASE_BLACKLIST, ignore_ext=IGNORED_EXTENSIONS, - verbose=0): - """Make a mirror of `from_dir` in `to_dir`, omitting directories and - files listed in the black list or ending with one of the given - extensions. - - :type from_dir: str - :param from_dir: directory to export - - :type to_dir: str - :param to_dir: destination directory - - :type blacklist: list or tuple - :param blacklist: - list of files or directories to ignore, default to the content of - `BASE_BLACKLIST` - - :type ignore_ext: list or tuple - :param ignore_ext: - list of extensions to ignore, default to the content of - `IGNORED_EXTENSIONS` - - :type verbose: bool - :param verbose: - flag indicating whether information about exported files should be - printed to stderr, default to False - """ - try: - mkdir(to_dir) - except OSError: - pass # FIXME we should use "exists" if the point is about existing dir - # else (permission problems?) shouldn't return / raise ? - for directory, dirnames, filenames in walk(from_dir): - for norecurs in blacklist: - try: - dirnames.remove(norecurs) - except ValueError: - continue - for dirname in dirnames: - src = join(directory, dirname) - dest = to_dir + src[len(from_dir):] - if isdir(src): - if not exists(dest): - mkdir(dest) - for filename in filenames: - # don't include binary files - # endswith does not accept tuple in 2.4 - if any([filename.endswith(ext) for ext in ignore_ext]): - continue - src = join(directory, filename) - dest = to_dir + src[len(from_dir):] - if verbose: - print >> sys.stderr, src, '->', dest - if exists(dest): - remove(dest) - shutil.copy2(src, dest) - - -def remove_dead_links(directory, verbose=0): - """Recursively traverse directory and remove all dead links. - - :type directory: str - :param directory: directory to cleanup - - :type verbose: bool - :param verbose: - flag indicating whether information about deleted links should be - printed to stderr, default to False - """ - for dirpath, dirname, filenames in walk(directory): - for filename in dirnames + filenames: - src = join(dirpath, filename) - if islink(src) and not exists(src): - if verbose: - print 'remove dead link', src - remove(src) - diff --git a/pylibs/logilab/common/hg.py b/pylibs/logilab/common/hg.py deleted file mode 100644 index edf2d3be..00000000 --- a/pylibs/logilab/common/hg.py +++ /dev/null @@ -1,130 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""mercurial utilities (mercurial should be installed)""" - -__docformat__ = "restructuredtext en" - -import os -import sys -import os.path as osp - -try: - from mercurial.error import RepoError - from mercurial.__version__ import version as hg_version -except ImportError: - from mercurial.repo import RepoError - from mercurial.version import get_version - hg_version = get_version() - -from mercurial.hg import repository as Repository -from mercurial.ui import ui as Ui -from mercurial.node import short -try: - # mercurial >= 1.2 (?) - from mercurial.cmdutil import walkchangerevs -except ImportError, ex: - from mercurial.commands import walkchangerevs -try: - # mercurial >= 1.1 (.1?) - from mercurial.util import cachefunc -except ImportError, ex: - def cachefunc(func): - return func -try: - # mercurial >= 1.3.1 - from mercurial import encoding - _encoding = encoding.encoding -except ImportError: - try: - from mercurial.util import _encoding - except ImportError: - import locale - # stay compatible with mercurial 0.9.1 (etch debian release) - # (borrowed from mercurial.util 1.1.2) - try: - _encoding = os.environ.get("HGENCODING") - if sys.platform == 'darwin' and not _encoding: - # On darwin, getpreferredencoding ignores the locale environment and - # always returns mac-roman. We override this if the environment is - # not C (has been customized by the user). - locale.setlocale(locale.LC_CTYPE, '') - _encoding = locale.getlocale()[1] - if not _encoding: - _encoding = locale.getpreferredencoding() or 'ascii' - except locale.Error: - _encoding = 'ascii' -try: - # demandimport causes problems when activated, ensure it isn't - # XXX put this in apycot where the pb has been noticed? - from mercurial import demandimport - demandimport.disable() -except: - pass - -Ui.warn = lambda *args, **kwargs: 0 # make it quiet - -def find_repository(path): - """returns 's mercurial repository - - None if is not under hg control - """ - path = osp.realpath(osp.abspath(path)) - while not osp.isdir(osp.join(path, ".hg")): - oldpath = path - path = osp.dirname(path) - if path == oldpath: - return None - return path - - -def get_repository(path): - """Simple function that open a hg repository""" - repopath = find_repository(path) - if repopath is None: - raise RuntimeError('no repository found in %s' % osp.abspath(path)) - return Repository(Ui(), path=repopath) - -def incoming(wdrepo, masterrepo): - try: - return wdrepo.findincoming(masterrepo) - except AttributeError: - from mercurial import hg, discovery - revs, checkout = hg.addbranchrevs(wdrepo, masterrepo, ('', []), None) - common, incoming, rheads = discovery.findcommonincoming( - wdrepo, masterrepo, heads=revs) - if not masterrepo.local(): - from mercurial import bundlerepo, changegroup - if revs is None and masterrepo.capable('changegroupsubset'): - revs = rheads - if revs is None: - cg = masterrepo.changegroup(incoming, "incoming") - else: - cg = masterrepo.changegroupsubset(incoming, revs, 'incoming') - fname = changegroup.writebundle(cg, None, "HG10UN") - # use the created uncompressed bundlerepo - masterrepo = bundlerepo.bundlerepository(wdrepo.ui, wdrepo.root, fname) - return masterrepo.changelog.nodesbetween(incoming, revs)[0] - -def outgoing(wdrepo, masterrepo): - try: - return wdrepo.findoutgoing(masterrepo) - except AttributeError: - from mercurial import hg, discovery - revs, checkout = hg.addbranchrevs(wdrepo, wdrepo, ('', []), None) - o = discovery.findoutgoing(wdrepo, masterrepo) - return wdrepo.changelog.nodesbetween(o, revs)[0] diff --git a/pylibs/logilab/common/logging_ext.py b/pylibs/logilab/common/logging_ext.py deleted file mode 100644 index 1b7a1e60..00000000 --- a/pylibs/logilab/common/logging_ext.py +++ /dev/null @@ -1,178 +0,0 @@ -# -*- coding: utf-8 -*- -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Extends the logging module from the standard library.""" - -__docformat__ = "restructuredtext en" - -import os -import sys -import logging - -from logilab.common.textutils import colorize_ansi - - -def set_log_methods(cls, logger): - """bind standard logger's methods as methods on the class""" - cls.__logger = logger - for attr in ('debug', 'info', 'warning', 'error', 'critical', 'exception'): - setattr(cls, attr, getattr(logger, attr)) - - -def xxx_cyan(record): - if 'XXX' in record.message: - return 'cyan' - -class ColorFormatter(logging.Formatter): - """ - A color Formatter for the logging standard module. - - By default, colorize CRITICAL and ERROR in red, WARNING in orange, INFO in - green and DEBUG in yellow. - - self.colors is customizable via the 'color' constructor argument (dictionary). - - self.colorfilters is a list of functions that get the LogRecord - and return a color name or None. - """ - - def __init__(self, fmt=None, datefmt=None, colors=None): - logging.Formatter.__init__(self, fmt, datefmt) - self.colorfilters = [] - self.colors = {'CRITICAL': 'red', - 'ERROR': 'red', - 'WARNING': 'magenta', - 'INFO': 'green', - 'DEBUG': 'yellow', - } - if colors is not None: - assert isinstance(colors, dict) - self.colors.update(colors) - - def format(self, record): - msg = logging.Formatter.format(self, record) - if record.levelname in self.colors: - color = self.colors[record.levelname] - return colorize_ansi(msg, color) - else: - for cf in self.colorfilters: - color = cf(record) - if color: - return colorize_ansi(msg, color) - return msg - -def set_color_formatter(logger=None, **kw): - """ - Install a color formatter on the 'logger'. If not given, it will - defaults to the default logger. - - Any additional keyword will be passed as-is to the ColorFormatter - constructor. - """ - if logger is None: - logger = logging.getLogger() - if not logger.handlers: - logging.basicConfig() - format_msg = logger.handlers[0].formatter._fmt - fmt = ColorFormatter(format_msg, **kw) - fmt.colorfilters.append(xxx_cyan) - logger.handlers[0].setFormatter(fmt) - - -LOG_FORMAT = '%(asctime)s - (%(name)s) %(levelname)s: %(message)s' -LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' - -def get_handler(debug=False, syslog=False, logfile=None, rotation_parameters=None): - """get an apropriate handler according to given parameters""" - if os.environ.get('APYCOT_ROOT'): - handler = logging.StreamHandler(sys.stdout) - if debug: - handler = logging.StreamHandler() - elif logfile is None: - if syslog: - from logging import handlers - handler = handlers.SysLogHandler() - else: - handler = logging.StreamHandler() - else: - try: - if rotation_parameters is None: - handler = logging.FileHandler(logfile) - else: - from logging.handlers import TimedRotatingFileHandler - handler = TimedRotatingFileHandler( - logfile, **rotation_parameters) - except IOError: - handler = logging.StreamHandler() - return handler - -def get_threshold(debug=False, logthreshold=None): - if logthreshold is None: - if debug: - logthreshold = logging.DEBUG - else: - logthreshold = logging.ERROR - elif isinstance(logthreshold, basestring): - logthreshold = getattr(logging, THRESHOLD_MAP.get(logthreshold, - logthreshold)) - return logthreshold - -def get_formatter(logformat=LOG_FORMAT, logdateformat=LOG_DATE_FORMAT): - isatty = hasattr(sys.__stdout__, 'isatty') and sys.__stdout__.isatty() - if isatty and sys.platform != 'win32': - fmt = ColorFormatter(logformat, logdateformat) - def col_fact(record): - if 'XXX' in record.message: - return 'cyan' - if 'kick' in record.message: - return 'red' - fmt.colorfilters.append(col_fact) - else: - fmt = logging.Formatter(logformat, logdateformat) - return fmt - -def init_log(debug=False, syslog=False, logthreshold=None, logfile=None, - logformat=LOG_FORMAT, logdateformat=LOG_DATE_FORMAT, fmt=None, - rotation_parameters=None, handler=None): - """init the log service""" - logger = logging.getLogger() - if handler is None: - handler = get_handler(debug, syslog, logfile, rotation_parameters) - # only addHandler and removeHandler method while I would like a setHandler - # method, so do it this way :$ - logger.handlers = [handler] - logthreshold = get_threshold(debug, logthreshold) - logger.setLevel(logthreshold) - if fmt is None: - if debug: - fmt = get_formatter(logformat=logformat, logdateformat=logdateformat) - else: - fmt = logging.Formatter(logformat, logdateformat) - handler.setFormatter(fmt) - return handler - -# map logilab.common.logger thresholds to logging thresholds -THRESHOLD_MAP = {'LOG_DEBUG': 'DEBUG', - 'LOG_INFO': 'INFO', - 'LOG_NOTICE': 'INFO', - 'LOG_WARN': 'WARNING', - 'LOG_WARNING': 'WARNING', - 'LOG_ERR': 'ERROR', - 'LOG_ERROR': 'ERROR', - 'LOG_CRIT': 'CRITICAL', - } diff --git a/pylibs/logilab/common/optparser.py b/pylibs/logilab/common/optparser.py deleted file mode 100644 index 0263dab6..00000000 --- a/pylibs/logilab/common/optparser.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Extend OptionParser with commands. - -Example: - ->>> parser = OptionParser() ->>> parser.usage = '%prog COMMAND [options] ...' ->>> parser.add_command('build', 'mymod.build') ->>> parser.add_command('clean', run_clean, add_opt_clean) ->>> run, options, args = parser.parse_command(sys.argv[1:]) ->>> return run(options, args[1:]) - -With mymod.build that defines two functions run and add_options -""" -__docformat__ = "restructuredtext en" - -from warnings import warn -warn('lgc.optparser module is deprecated, use lgc.clcommands instead', DeprecationWarning, - stacklevel=2) - -import sys -import optparse - -class OptionParser(optparse.OptionParser): - - def __init__(self, *args, **kwargs): - optparse.OptionParser.__init__(self, *args, **kwargs) - self._commands = {} - self.min_args, self.max_args = 0, 1 - - def add_command(self, name, mod_or_funcs, help=''): - """name of the command, name of module or tuple of functions - (run, add_options) - """ - assert isinstance(mod_or_funcs, str) or isinstance(mod_or_funcs, tuple), \ - "mod_or_funcs has to be a module name or a tuple of functions" - self._commands[name] = (mod_or_funcs, help) - - def print_main_help(self): - optparse.OptionParser.print_help(self) - print '\ncommands:' - for cmdname, (_, help) in self._commands.items(): - print '% 10s - %s' % (cmdname, help) - - def parse_command(self, args): - if len(args) == 0: - self.print_main_help() - sys.exit(1) - cmd = args[0] - args = args[1:] - if cmd not in self._commands: - if cmd in ('-h', '--help'): - self.print_main_help() - sys.exit(0) - elif self.version is not None and cmd == "--version": - self.print_version() - sys.exit(0) - self.error('unknown command') - self.prog = '%s %s' % (self.prog, cmd) - mod_or_f, help = self._commands[cmd] - # optparse inserts self.description between usage and options help - self.description = help - if isinstance(mod_or_f, str): - exec 'from %s import run, add_options' % mod_or_f - else: - run, add_options = mod_or_f - add_options(self) - (options, args) = self.parse_args(args) - if not (self.min_args <= len(args) <= self.max_args): - self.error('incorrect number of arguments') - return run, options, args - - diff --git a/pylibs/logilab/common/pdf_ext.py b/pylibs/logilab/common/pdf_ext.py deleted file mode 100644 index 71c483b2..00000000 --- a/pylibs/logilab/common/pdf_ext.py +++ /dev/null @@ -1,111 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Manipulate pdf and fdf files (pdftk recommended). - -Notes regarding pdftk, pdf forms and fdf files (form definition file) -fields names can be extracted with: - - pdftk orig.pdf generate_fdf output truc.fdf - -to merge fdf and pdf: - - pdftk orig.pdf fill_form test.fdf output result.pdf [flatten] - -without flatten, one could further edit the resulting form. -with flatten, everything is turned into text. - - - - -""" -__docformat__ = "restructuredtext en" -# XXX seems very unix specific -# TODO: check availability of pdftk at import - - -import os - -HEAD="""%FDF-1.2 -%\xE2\xE3\xCF\xD3 -1 0 obj -<< -/FDF -<< -/Fields [ -""" - -TAIL="""] ->> ->> -endobj -trailer - -<< -/Root 1 0 R ->> -%%EOF -""" - -def output_field( f ): - return "\xfe\xff" + "".join( [ "\x00"+c for c in f ] ) - -def extract_keys(lines): - keys = [] - for line in lines: - if line.startswith('/V'): - pass #print 'value',line - elif line.startswith('/T'): - key = line[7:-2] - key = ''.join(key.split('\x00')) - keys.append( key ) - return keys - -def write_field(out, key, value): - out.write("<<\n") - if value: - out.write("/V (%s)\n" %value) - else: - out.write("/V /\n") - out.write("/T (%s)\n" % output_field(key) ) - out.write(">> \n") - -def write_fields(out, fields): - out.write(HEAD) - for (key, value, comment) in fields: - write_field(out, key, value) - write_field(out, key+"a", value) # pour copie-carbone sur autres pages - out.write(TAIL) - -def extract_keys_from_pdf(filename): - # what about using 'pdftk filename dump_data_fields' and parsing the output ? - os.system('pdftk %s generate_fdf output /tmp/toto.fdf' % filename) - lines = file('/tmp/toto.fdf').readlines() - return extract_keys(lines) - - -def fill_pdf(infile, outfile, fields): - write_fields(file('/tmp/toto.fdf', 'w'), fields) - os.system('pdftk %s fill_form /tmp/toto.fdf output %s flatten' % (infile, outfile)) - -def testfill_pdf(infile, outfile): - keys = extract_keys_from_pdf(infile) - fields = [] - for key in keys: - fields.append( (key, key, '') ) - fill_pdf(infile, outfile, fields) - diff --git a/pylibs/logilab/common/proc.py b/pylibs/logilab/common/proc.py deleted file mode 100644 index c27356c6..00000000 --- a/pylibs/logilab/common/proc.py +++ /dev/null @@ -1,277 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""module providing: -* process information (linux specific: rely on /proc) -* a class for resource control (memory / time / cpu time) - -This module doesn't work on windows platforms (only tested on linux) - -:organization: Logilab - - - -""" -__docformat__ = "restructuredtext en" - -import os -import stat -from resource import getrlimit, setrlimit, RLIMIT_CPU, RLIMIT_AS -from signal import signal, SIGXCPU, SIGKILL, SIGUSR2, SIGUSR1 -from threading import Timer, currentThread, Thread, Event -from time import time - -from logilab.common.tree import Node - -class NoSuchProcess(Exception): pass - -def proc_exists(pid): - """check the a pid is registered in /proc - raise NoSuchProcess exception if not - """ - if not os.path.exists('/proc/%s' % pid): - raise NoSuchProcess() - -PPID = 3 -UTIME = 13 -STIME = 14 -CUTIME = 15 -CSTIME = 16 -VSIZE = 22 - -class ProcInfo(Node): - """provide access to process information found in /proc""" - - def __init__(self, pid): - self.pid = int(pid) - Node.__init__(self, self.pid) - proc_exists(self.pid) - self.file = '/proc/%s/stat' % self.pid - self.ppid = int(self.status()[PPID]) - - def memory_usage(self): - """return the memory usage of the process in Ko""" - try : - return int(self.status()[VSIZE]) - except IOError: - return 0 - - def lineage_memory_usage(self): - return self.memory_usage() + sum([child.lineage_memory_usage() - for child in self.children]) - - def time(self, children=0): - """return the number of jiffies that this process has been scheduled - in user and kernel mode""" - status = self.status() - time = int(status[UTIME]) + int(status[STIME]) - if children: - time += int(status[CUTIME]) + int(status[CSTIME]) - return time - - def status(self): - """return the list of fields found in /proc//stat""" - return open(self.file).read().split() - - def name(self): - """return the process name found in /proc//stat - """ - return self.status()[1].strip('()') - - def age(self): - """return the age of the process - """ - return os.stat(self.file)[stat.ST_MTIME] - -class ProcInfoLoader: - """manage process information""" - - def __init__(self): - self._loaded = {} - - def list_pids(self): - """return a list of existent process ids""" - for subdir in os.listdir('/proc'): - if subdir.isdigit(): - yield int(subdir) - - def load(self, pid): - """get a ProcInfo object for a given pid""" - pid = int(pid) - try: - return self._loaded[pid] - except KeyError: - procinfo = ProcInfo(pid) - procinfo.manager = self - self._loaded[pid] = procinfo - return procinfo - - - def load_all(self): - """load all processes information""" - for pid in self.list_pids(): - try: - procinfo = self.load(pid) - if procinfo.parent is None and procinfo.ppid: - pprocinfo = self.load(procinfo.ppid) - pprocinfo.append(procinfo) - except NoSuchProcess: - pass - - -try: - class ResourceError(BaseException): - """Error raise when resource limit is reached""" - limit = "Unknown Resource Limit" -except NameError: - class ResourceError(Exception): - """Error raise when resource limit is reached""" - limit = "Unknown Resource Limit" - - -class XCPUError(ResourceError): - """Error raised when CPU Time limit is reached""" - limit = "CPU Time" - -class LineageMemoryError(ResourceError): - """Error raised when the total amount of memory used by a process and - it's child is reached""" - limit = "Lineage total Memory" - -class TimeoutError(ResourceError): - """Error raised when the process is running for to much time""" - limit = "Real Time" - -# Can't use subclass because the StandardError MemoryError raised -RESOURCE_LIMIT_EXCEPTION = (ResourceError, MemoryError) - - -class MemorySentinel(Thread): - """A class checking a process don't use too much memory in a separated - daemonic thread - """ - def __init__(self, interval, memory_limit, gpid=os.getpid()): - Thread.__init__(self, target=self._run, name="Test.Sentinel") - self.memory_limit = memory_limit - self._stop = Event() - self.interval = interval - self.setDaemon(True) - self.gpid = gpid - - def stop(self): - """stop ap""" - self._stop.set() - - def _run(self): - pil = ProcInfoLoader() - while not self._stop.isSet(): - if self.memory_limit <= pil.load(self.gpid).lineage_memory_usage(): - os.killpg(self.gpid, SIGUSR1) - self._stop.wait(self.interval) - - -class ResourceController: - - def __init__(self, max_cpu_time=None, max_time=None, max_memory=None, - max_reprieve=60): - if SIGXCPU == -1: - raise RuntimeError("Unsupported platform") - self.max_time = max_time - self.max_memory = max_memory - self.max_cpu_time = max_cpu_time - self._reprieve = max_reprieve - self._timer = None - self._msentinel = None - self._old_max_memory = None - self._old_usr1_hdlr = None - self._old_max_cpu_time = None - self._old_usr2_hdlr = None - self._old_sigxcpu_hdlr = None - self._limit_set = 0 - self._abort_try = 0 - self._start_time = None - self._elapse_time = 0 - - def _hangle_sig_timeout(self, sig, frame): - raise TimeoutError() - - def _hangle_sig_memory(self, sig, frame): - if self._abort_try < self._reprieve: - self._abort_try += 1 - raise LineageMemoryError("Memory limit reached") - else: - os.killpg(os.getpid(), SIGKILL) - - def _handle_sigxcpu(self, sig, frame): - if self._abort_try < self._reprieve: - self._abort_try += 1 - raise XCPUError("Soft CPU time limit reached") - else: - os.killpg(os.getpid(), SIGKILL) - - def _time_out(self): - if self._abort_try < self._reprieve: - self._abort_try += 1 - os.killpg(os.getpid(), SIGUSR2) - if self._limit_set > 0: - self._timer = Timer(1, self._time_out) - self._timer.start() - else: - os.killpg(os.getpid(), SIGKILL) - - def setup_limit(self): - """set up the process limit""" - assert currentThread().getName() == 'MainThread' - os.setpgrp() - if self._limit_set <= 0: - if self.max_time is not None: - self._old_usr2_hdlr = signal(SIGUSR2, self._hangle_sig_timeout) - self._timer = Timer(max(1, int(self.max_time) - self._elapse_time), - self._time_out) - self._start_time = int(time()) - self._timer.start() - if self.max_cpu_time is not None: - self._old_max_cpu_time = getrlimit(RLIMIT_CPU) - cpu_limit = (int(self.max_cpu_time), self._old_max_cpu_time[1]) - self._old_sigxcpu_hdlr = signal(SIGXCPU, self._handle_sigxcpu) - setrlimit(RLIMIT_CPU, cpu_limit) - if self.max_memory is not None: - self._msentinel = MemorySentinel(1, int(self.max_memory) ) - self._old_max_memory = getrlimit(RLIMIT_AS) - self._old_usr1_hdlr = signal(SIGUSR1, self._hangle_sig_memory) - as_limit = (int(self.max_memory), self._old_max_memory[1]) - setrlimit(RLIMIT_AS, as_limit) - self._msentinel.start() - self._limit_set += 1 - - def clean_limit(self): - """reinstall the old process limit""" - if self._limit_set > 0: - if self.max_time is not None: - self._timer.cancel() - self._elapse_time += int(time())-self._start_time - self._timer = None - signal(SIGUSR2, self._old_usr2_hdlr) - if self.max_cpu_time is not None: - setrlimit(RLIMIT_CPU, self._old_max_cpu_time) - signal(SIGXCPU, self._old_sigxcpu_hdlr) - if self.max_memory is not None: - self._msentinel.stop() - self._msentinel = None - setrlimit(RLIMIT_AS, self._old_max_memory) - signal(SIGUSR1, self._old_usr1_hdlr) - self._limit_set -= 1 diff --git a/pylibs/logilab/common/pyro_ext.py b/pylibs/logilab/common/pyro_ext.py deleted file mode 100644 index 0f4d2790..00000000 --- a/pylibs/logilab/common/pyro_ext.py +++ /dev/null @@ -1,180 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Python Remote Object utilities - -Main functions available: - -* `register_object` to expose arbitrary object through pyro using delegation - approach and register it in the nameserver. -* `ns_unregister` unregister an object identifier from the nameserver. -* `ns_get_proxy` get a pyro proxy from a nameserver object identifier. -""" - -__docformat__ = "restructuredtext en" - -import logging -import tempfile - -from Pyro import core, naming, errors, util, config - -_LOGGER = logging.getLogger('pyro') -_MARKER = object() - -config.PYRO_STORAGE = tempfile.gettempdir() - -def ns_group_and_id(idstr, defaultnsgroup=_MARKER): - try: - nsgroup, nsid = idstr.rsplit('.', 1) - except ValueError: - if defaultnsgroup is _MARKER: - nsgroup = config.PYRO_NS_DEFAULTGROUP - else: - nsgroup = defaultnsgroup - nsid = idstr - if nsgroup is not None and not nsgroup.startswith(':'): - nsgroup = ':' + nsgroup - return nsgroup, nsid - -def host_and_port(hoststr): - if not hoststr: - return None, None - try: - hoststr, port = hoststr.split(':') - except ValueError: - port = None - else: - port = int(port) - return hoststr, port - -_DAEMONS = {} -_PYRO_OBJS = {} -def _get_daemon(daemonhost, start=True): - if not daemonhost in _DAEMONS: - if not start: - raise Exception('no daemon for %s' % daemonhost) - if not _DAEMONS: - core.initServer(banner=0) - host, port = host_and_port(daemonhost) - daemon = core.Daemon(host=host, port=port) - _DAEMONS[daemonhost] = daemon - return _DAEMONS[daemonhost] - - -def locate_ns(nshost): - """locate and return the pyro name server to the daemon""" - core.initClient(banner=False) - return naming.NameServerLocator().getNS(*host_and_port(nshost)) - - -def register_object(object, nsid, defaultnsgroup=_MARKER, - daemonhost=None, nshost=None, use_pyrons=True): - """expose the object as a pyro object and register it in the name-server - - if use_pyrons is False, then the object is exposed, but no - attempt to register it to a pyro nameserver is made. - - return the pyro daemon object - """ - nsgroup, nsid = ns_group_and_id(nsid, defaultnsgroup) - daemon = _get_daemon(daemonhost) - if use_pyrons: - nsd = locate_ns(nshost) - # make sure our namespace group exists - try: - nsd.createGroup(nsgroup) - except errors.NamingError: - pass - daemon.useNameServer(nsd) - # use Delegation approach - impl = core.ObjBase() - impl.delegateTo(object) - qnsid = '%s.%s' % (nsgroup, nsid) - uri = daemon.connect(impl, qnsid) - _PYRO_OBJS[qnsid] = str(uri) - _LOGGER.info('registered %s a pyro object using group %s and id %s', - object, nsgroup, nsid) - return daemon - -def get_object_uri(qnsid): - return _PYRO_OBJS[qnsid] - -def ns_unregister(nsid, defaultnsgroup=_MARKER, nshost=None): - """unregister the object with the given nsid from the pyro name server""" - nsgroup, nsid = ns_group_and_id(nsid, defaultnsgroup) - try: - nsd = locate_ns(nshost) - except errors.PyroError, ex: - # name server not responding - _LOGGER.error('can\'t locate pyro name server: %s', ex) - else: - try: - nsd.unregister('%s.%s' % (nsgroup, nsid)) - _LOGGER.info('%s unregistered from pyro name server', nsid) - except errors.NamingError: - _LOGGER.warning('%s not registered in pyro name server', nsid) - - -def ns_reregister(nsid, defaultnsgroup=_MARKER, nshost=None): - """reregister a pyro object into the name server. You only have to specify - the name-server id of the object (though you MUST have gone through - `register_object` for the given object previously). - - This is especially useful for long running server while the name server may - have been restarted, and its records lost. - """ - nsgroup, nsid = ns_group_and_id(nsid, defaultnsgroup) - qnsid = '%s.%s' % (nsgroup, nsid) - nsd = locate_ns(nshost) - try: - nsd.unregister(qnsid) - except errors.NamingError: - # make sure our namespace group exists - try: - nsd.createGroup(nsgroup) - except errors.NamingError: - pass - nsd.register(qnsid, _PYRO_OBJS[qnsid]) - -def ns_get_proxy(nsid, defaultnsgroup=_MARKER, nshost=None): - """ - if nshost is None, the nameserver is found by a broadcast. - """ - # resolve the Pyro object - nsgroup, nsid = ns_group_and_id(nsid, defaultnsgroup) - try: - nsd = locate_ns(nshost) - pyrouri = nsd.resolve('%s.%s' % (nsgroup, nsid)) - except errors.ProtocolError, ex: - raise errors.PyroError( - 'Could not connect to the Pyro name server (host: %s)' % nshost) - except errors.NamingError: - raise errors.PyroError( - 'Could not get proxy for %s (not registered in Pyro), ' - 'you may have to restart your server-side application' % nsid) - return core.getProxyForURI(pyrouri) - -def get_proxy(pyro_uri): - """get a proxy for the passed pyro uri without using a nameserver - """ - return core.getProxyForURI(pyro_uri) - -def set_pyro_log_threshold(level): - pyrologger = logging.getLogger('Pyro.%s' % str(id(util.Log))) - # remove handlers so only the root handler is used - pyrologger.handlers = [] - pyrologger.setLevel(level) diff --git a/pylibs/logilab/common/pytest.py b/pylibs/logilab/common/pytest.py deleted file mode 100644 index 2d6ccf95..00000000 --- a/pylibs/logilab/common/pytest.py +++ /dev/null @@ -1,1177 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""pytest is a tool that eases test running and debugging. - -To be able to use pytest, you should either write tests using -the logilab.common.testlib's framework or the unittest module of the -Python's standard library. - -You can customize pytest's behaviour by defining a ``pytestconf.py`` file -somewhere in your test directory. In this file, you can add options or -change the way tests are run. - -To add command line options, you must define a ``update_parser`` function in -your ``pytestconf.py`` file. The function must accept a single parameter -that will be the OptionParser's instance to customize. - -If you wish to customize the tester, you'll have to define a class named -``CustomPyTester``. This class should extend the default `PyTester` class -defined in the pytest module. Take a look at the `PyTester` and `DjangoTester` -classes for more information about what can be done. - -For instance, if you wish to add a custom -l option to specify a loglevel, you -could define the following ``pytestconf.py`` file :: - - import logging - from logilab.common.pytest import PyTester - - def update_parser(parser): - parser.add_option('-l', '--loglevel', dest='loglevel', action='store', - choices=('debug', 'info', 'warning', 'error', 'critical'), - default='critical', help="the default log level possible choices are " - "('debug', 'info', 'warning', 'error', 'critical')") - return parser - - - class CustomPyTester(PyTester): - def __init__(self, cvg, options): - super(CustomPyTester, self).__init__(cvg, options) - loglevel = options.loglevel.upper() - logger = logging.getLogger('erudi') - logger.setLevel(logging.getLevelName(loglevel)) - - -In your TestCase class you can then get the value of a specific option with -the ``optval`` method:: - - class MyTestCase(TestCase): - def test_foo(self): - loglevel = self.optval('loglevel') - # ... - - -You can also tag your tag your test for fine filtering - -With those tag:: - - from logilab.common.testlib import tag, TestCase - - class Exemple(TestCase): - - @tag('rouge', 'carre') - def toto(self): - pass - - @tag('carre', 'vert') - def tata(self): - pass - - @tag('rouge') - def titi(test): - pass - -you can filter the function with a simple python expression - - * ``toto`` and ``titi`` match ``rouge`` - * ``toto``, ``tata`` and ``titi``, match ``rouge or carre`` - * ``tata`` and ``titi`` match``rouge ^ carre`` - * ``titi`` match ``rouge and not carre`` -""" -__docformat__ = "restructuredtext en" - -PYTEST_DOC = """%prog [OPTIONS] [testfile [testpattern]] - -examples: - -pytest path/to/mytests.py -pytest path/to/mytests.py TheseTests -pytest path/to/mytests.py TheseTests.test_thisone -pytest path/to/mytests.py -m '(not long and database) or regr' - -pytest one (will run both test_thisone and test_thatone) -pytest path/to/mytests.py -s not (will skip test_notthisone) - -pytest --coverage test_foo.py - (only if logilab.devtools is available) -""" - -ENABLE_DBC = False -FILE_RESTART = ".pytest.restart" - -import os, sys, re -import os.path as osp -from time import time, clock -import warnings -import types - -from logilab.common.fileutils import abspath_listdir -from logilab.common import textutils -from logilab.common import testlib, STD_BLACKLIST -# use the same unittest module as testlib -from logilab.common.testlib import unittest, start_interactive_mode -from logilab.common.compat import any -import doctest - -import unittest as unittest_legacy -if not getattr(unittest_legacy, "__package__", None): - try: - import unittest2.suite as unittest_suite - except ImportError: - sys.exit("You have to install python-unittest2 to use this module") -else: - import unittest.suite as unittest_suite - -try: - import django - from logilab.common.modutils import modpath_from_file, load_module_from_modpath - DJANGO_FOUND = True -except ImportError: - DJANGO_FOUND = False - -CONF_FILE = 'pytestconf.py' - -## coverage hacks, do not read this, do not read this, do not read this - -# hey, but this is an aspect, right ?!!! -class TraceController(object): - nesting = 0 - - def pause_tracing(cls): - if not cls.nesting: - cls.tracefunc = staticmethod(getattr(sys, '__settrace__', sys.settrace)) - cls.oldtracer = getattr(sys, '__tracer__', None) - sys.__notrace__ = True - cls.tracefunc(None) - cls.nesting += 1 - pause_tracing = classmethod(pause_tracing) - - def resume_tracing(cls): - cls.nesting -= 1 - assert cls.nesting >= 0 - if not cls.nesting: - cls.tracefunc(cls.oldtracer) - delattr(sys, '__notrace__') - resume_tracing = classmethod(resume_tracing) - - -pause_tracing = TraceController.pause_tracing -resume_tracing = TraceController.resume_tracing - - -def nocoverage(func): - if hasattr(func, 'uncovered'): - return func - func.uncovered = True - def not_covered(*args, **kwargs): - pause_tracing() - try: - return func(*args, **kwargs) - finally: - resume_tracing() - not_covered.uncovered = True - return not_covered - - -## end of coverage hacks - - -TESTFILE_RE = re.compile("^((unit)?test.*|smoketest)\.py$") -def this_is_a_testfile(filename): - """returns True if `filename` seems to be a test file""" - return TESTFILE_RE.match(osp.basename(filename)) - -TESTDIR_RE = re.compile("^(unit)?tests?$") -def this_is_a_testdir(dirpath): - """returns True if `filename` seems to be a test directory""" - return TESTDIR_RE.match(osp.basename(dirpath)) - - -def load_pytest_conf(path, parser): - """loads a ``pytestconf.py`` file and update default parser - and / or tester. - """ - namespace = {} - execfile(path, namespace) - if 'update_parser' in namespace: - namespace['update_parser'](parser) - return namespace.get('CustomPyTester', PyTester) - - -def project_root(parser, projdir=os.getcwd()): - """try to find project's root and add it to sys.path""" - previousdir = curdir = osp.abspath(projdir) - testercls = PyTester - conf_file_path = osp.join(curdir, CONF_FILE) - if osp.isfile(conf_file_path): - testercls = load_pytest_conf(conf_file_path, parser) - while this_is_a_testdir(curdir) or \ - osp.isfile(osp.join(curdir, '__init__.py')): - newdir = osp.normpath(osp.join(curdir, os.pardir)) - if newdir == curdir: - break - previousdir = curdir - curdir = newdir - conf_file_path = osp.join(curdir, CONF_FILE) - if osp.isfile(conf_file_path): - testercls = load_pytest_conf(conf_file_path, parser) - return previousdir, testercls - - -class GlobalTestReport(object): - """this class holds global test statistics""" - def __init__(self): - self.ran = 0 - self.skipped = 0 - self.failures = 0 - self.errors = 0 - self.ttime = 0 - self.ctime = 0 - self.modulescount = 0 - self.errmodules = [] - - def feed(self, filename, testresult, ttime, ctime): - """integrates new test information into internal statistics""" - ran = testresult.testsRun - self.ran += ran - self.skipped += len(getattr(testresult, 'skipped', ())) - self.failures += len(testresult.failures) - self.errors += len(testresult.errors) - self.ttime += ttime - self.ctime += ctime - self.modulescount += 1 - if not testresult.wasSuccessful(): - problems = len(testresult.failures) + len(testresult.errors) - self.errmodules.append((filename[:-3], problems, ran)) - - def failed_to_test_module(self, filename): - """called when the test module could not be imported by unittest - """ - self.errors += 1 - self.modulescount += 1 - self.ran += 1 - self.errmodules.append((filename[:-3], 1, 1)) - - def skip_module(self, filename): - self.modulescount += 1 - self.ran += 1 - self.errmodules.append((filename[:-3], 0, 0)) - - def __str__(self): - """this is just presentation stuff""" - line1 = ['Ran %s test cases in %.2fs (%.2fs CPU)' - % (self.ran, self.ttime, self.ctime)] - if self.errors: - line1.append('%s errors' % self.errors) - if self.failures: - line1.append('%s failures' % self.failures) - if self.skipped: - line1.append('%s skipped' % self.skipped) - modulesok = self.modulescount - len(self.errmodules) - if self.errors or self.failures: - line2 = '%s modules OK (%s failed)' % (modulesok, - len(self.errmodules)) - descr = ', '.join(['%s [%s/%s]' % info for info in self.errmodules]) - line3 = '\nfailures: %s' % descr - elif modulesok: - line2 = 'All %s modules OK' % modulesok - line3 = '' - else: - return '' - return '%s\n%s%s' % (', '.join(line1), line2, line3) - - - -def remove_local_modules_from_sys(testdir): - """remove all modules from cache that come from `testdir` - - This is used to avoid strange side-effects when using the - testall() mode of pytest. - For instance, if we run pytest on this tree:: - - A/test/test_utils.py - B/test/test_utils.py - - we **have** to clean sys.modules to make sure the correct test_utils - module is ran in B - """ - for modname, mod in sys.modules.items(): - if mod is None: - continue - if not hasattr(mod, '__file__'): - # this is the case of some built-in modules like sys, imp, marshal - continue - modfile = mod.__file__ - # if modfile is not an absolute path, it was probably loaded locally - # during the tests - if not osp.isabs(modfile) or modfile.startswith(testdir): - del sys.modules[modname] - - - -class PyTester(object): - """encapsulates testrun logic""" - - def __init__(self, cvg, options): - self.report = GlobalTestReport() - self.cvg = cvg - self.options = options - self.firstwrite = True - self._errcode = None - - def show_report(self): - """prints the report and returns appropriate exitcode""" - # everything has been ran, print report - print "*" * 79 - print self.report - - def get_errcode(self): - # errcode set explicitly - if self._errcode is not None: - return self._errcode - return self.report.failures + self.report.errors - - def set_errcode(self, errcode): - self._errcode = errcode - errcode = property(get_errcode, set_errcode) - - def testall(self, exitfirst=False): - """walks through current working directory, finds something - which can be considered as a testdir and runs every test there - """ - here = os.getcwd() - for dirname, dirs, _ in os.walk(here): - for skipped in STD_BLACKLIST: - if skipped in dirs: - dirs.remove(skipped) - basename = osp.basename(dirname) - if this_is_a_testdir(basename): - print "going into", dirname - # we found a testdir, let's explore it ! - if not self.testonedir(dirname, exitfirst): - break - dirs[:] = [] - if self.report.ran == 0: - print "no test dir found testing here:", here - # if no test was found during the visit, consider - # the local directory as a test directory even if - # it doesn't have a traditional test directory name - self.testonedir(here) - - def testonedir(self, testdir, exitfirst=False): - """finds each testfile in the `testdir` and runs it - - return true when all tests has been executed, false if exitfirst and - some test has failed. - """ - for filename in abspath_listdir(testdir): - if this_is_a_testfile(filename): - if self.options.exitfirst and not self.options.restart: - # overwrite restart file - try: - restartfile = open(FILE_RESTART, "w") - restartfile.close() - except Exception, e: - print >> sys.__stderr__, "Error while overwriting \ -succeeded test file :", osp.join(os.getcwd(), FILE_RESTART) - raise e - # run test and collect information - prog = self.testfile(filename, batchmode=True) - if exitfirst and (prog is None or not prog.result.wasSuccessful()): - return False - self.firstwrite = True - # clean local modules - remove_local_modules_from_sys(testdir) - return True - - def testfile(self, filename, batchmode=False): - """runs every test in `filename` - - :param filename: an absolute path pointing to a unittest file - """ - here = os.getcwd() - dirname = osp.dirname(filename) - if dirname: - os.chdir(dirname) - # overwrite restart file if it has not been done already - if self.options.exitfirst and not self.options.restart and self.firstwrite: - try: - restartfile = open(FILE_RESTART, "w") - restartfile.close() - except Exception, e: - print >> sys.__stderr__, "Error while overwriting \ -succeeded test file :", osp.join(os.getcwd(), FILE_RESTART) - raise e - modname = osp.basename(filename)[:-3] - try: - print >> sys.stderr, (' %s ' % osp.basename(filename)).center(70, '=') - except TypeError: # < py 2.4 bw compat - print >> sys.stderr, (' %s ' % osp.basename(filename)).center(70) - try: - tstart, cstart = time(), clock() - try: - testprog = SkipAwareTestProgram(modname, batchmode=batchmode, cvg=self.cvg, - options=self.options, outstream=sys.stderr) - except KeyboardInterrupt: - raise - except SystemExit, exc: - self.errcode = exc.code - raise - except testlib.SkipTest: - print "Module skipped:", filename - self.report.skip_module(filename) - return None - except Exception: - self.report.failed_to_test_module(filename) - print >> sys.stderr, 'unhandled exception occurred while testing', modname - import traceback - traceback.print_exc(file=sys.stderr) - return None - - tend, cend = time(), clock() - ttime, ctime = (tend - tstart), (cend - cstart) - self.report.feed(filename, testprog.result, ttime, ctime) - return testprog - finally: - if dirname: - os.chdir(here) - - - -class DjangoTester(PyTester): - - def load_django_settings(self, dirname): - """try to find project's setting and load it""" - curdir = osp.abspath(dirname) - previousdir = curdir - while not osp.isfile(osp.join(curdir, 'settings.py')) and \ - osp.isfile(osp.join(curdir, '__init__.py')): - newdir = osp.normpath(osp.join(curdir, os.pardir)) - if newdir == curdir: - raise AssertionError('could not find settings.py') - previousdir = curdir - curdir = newdir - # late django initialization - settings = load_module_from_modpath(modpath_from_file(osp.join(curdir, 'settings.py'))) - from django.core.management import setup_environ - setup_environ(settings) - settings.DEBUG = False - self.settings = settings - # add settings dir to pythonpath since it's the project's root - if curdir not in sys.path: - sys.path.insert(1, curdir) - - def before_testfile(self): - # Those imports must be done **after** setup_environ was called - from django.test.utils import setup_test_environment - from django.test.utils import create_test_db - setup_test_environment() - create_test_db(verbosity=0) - self.dbname = self.settings.TEST_DATABASE_NAME - - def after_testfile(self): - # Those imports must be done **after** setup_environ was called - from django.test.utils import teardown_test_environment - from django.test.utils import destroy_test_db - teardown_test_environment() - print 'destroying', self.dbname - destroy_test_db(self.dbname, verbosity=0) - - def testall(self, exitfirst=False): - """walks through current working directory, finds something - which can be considered as a testdir and runs every test there - """ - for dirname, dirs, files in os.walk(os.getcwd()): - for skipped in ('CVS', '.svn', '.hg'): - if skipped in dirs: - dirs.remove(skipped) - if 'tests.py' in files: - if not self.testonedir(dirname, exitfirst): - break - dirs[:] = [] - else: - basename = osp.basename(dirname) - if basename in ('test', 'tests'): - print "going into", dirname - # we found a testdir, let's explore it ! - if not self.testonedir(dirname, exitfirst): - break - dirs[:] = [] - - def testonedir(self, testdir, exitfirst=False): - """finds each testfile in the `testdir` and runs it - - return true when all tests has been executed, false if exitfirst and - some test has failed. - """ - # special django behaviour : if tests are splitted in several files, - # remove the main tests.py file and tests each test file separately - testfiles = [fpath for fpath in abspath_listdir(testdir) - if this_is_a_testfile(fpath)] - if len(testfiles) > 1: - try: - testfiles.remove(osp.join(testdir, 'tests.py')) - except ValueError: - pass - for filename in testfiles: - # run test and collect information - prog = self.testfile(filename, batchmode=True) - if exitfirst and (prog is None or not prog.result.wasSuccessful()): - return False - # clean local modules - remove_local_modules_from_sys(testdir) - return True - - def testfile(self, filename, batchmode=False): - """runs every test in `filename` - - :param filename: an absolute path pointing to a unittest file - """ - here = os.getcwd() - dirname = osp.dirname(filename) - if dirname: - os.chdir(dirname) - self.load_django_settings(dirname) - modname = osp.basename(filename)[:-3] - print >>sys.stderr, (' %s ' % osp.basename(filename)).center(70, '=') - try: - try: - tstart, cstart = time(), clock() - self.before_testfile() - testprog = SkipAwareTestProgram(modname, batchmode=batchmode, cvg=self.cvg) - tend, cend = time(), clock() - ttime, ctime = (tend - tstart), (cend - cstart) - self.report.feed(filename, testprog.result, ttime, ctime) - return testprog - except SystemExit: - raise - except Exception, exc: - import traceback - traceback.print_exc() - self.report.failed_to_test_module(filename) - print 'unhandled exception occurred while testing', modname - print 'error: %s' % exc - return None - finally: - self.after_testfile() - if dirname: - os.chdir(here) - - -def make_parser(): - """creates the OptionParser instance - """ - from optparse import OptionParser - parser = OptionParser(usage=PYTEST_DOC) - - parser.newargs = [] - def rebuild_cmdline(option, opt, value, parser): - """carry the option to unittest_main""" - parser.newargs.append(opt) - - def rebuild_and_store(option, opt, value, parser): - """carry the option to unittest_main and store - the value on current parser - """ - parser.newargs.append(opt) - setattr(parser.values, option.dest, True) - - def capture_and_rebuild(option, opt, value, parser): - warnings.simplefilter('ignore', DeprecationWarning) - rebuild_cmdline(option, opt, value, parser) - - # pytest options - parser.add_option('-t', dest='testdir', default=None, - help="directory where the tests will be found") - parser.add_option('-d', dest='dbc', default=False, - action="store_true", help="enable design-by-contract") - # unittest_main options provided and passed through pytest - parser.add_option('-v', '--verbose', callback=rebuild_cmdline, - action="callback", help="Verbose output") - parser.add_option('-i', '--pdb', callback=rebuild_and_store, - dest="pdb", action="callback", - help="Enable test failure inspection (conflicts with --coverage)") - parser.add_option('-x', '--exitfirst', callback=rebuild_and_store, - dest="exitfirst", default=False, - action="callback", help="Exit on first failure " - "(only make sense when pytest run one test file)") - parser.add_option('-R', '--restart', callback=rebuild_and_store, - dest="restart", default=False, - action="callback", - help="Restart tests from where it failed (implies exitfirst) " - "(only make sense if tests previously ran with exitfirst only)") - parser.add_option('--color', callback=rebuild_cmdline, - action="callback", - help="colorize tracebacks") - parser.add_option('-s', '--skip', - # XXX: I wish I could use the callback action but it - # doesn't seem to be able to get the value - # associated to the option - action="store", dest="skipped", default=None, - help="test names matching this name will be skipped " - "to skip several patterns, use commas") - parser.add_option('-q', '--quiet', callback=rebuild_cmdline, - action="callback", help="Minimal output") - parser.add_option('-P', '--profile', default=None, dest='profile', - help="Profile execution and store data in the given file") - parser.add_option('-m', '--match', default=None, dest='tags_pattern', - help="only execute test whose tag match the current pattern") - - try: - from logilab.devtools.lib.coverage import Coverage - parser.add_option('--coverage', dest="coverage", default=False, - action="store_true", - help="run tests with pycoverage (conflicts with --pdb)") - except ImportError: - pass - - if DJANGO_FOUND: - parser.add_option('-J', '--django', dest='django', default=False, - action="store_true", - help='use pytest for django test cases') - return parser - - -def parseargs(parser): - """Parse the command line and return (options processed), (options to pass to - unittest_main()), (explicitfile or None). - """ - # parse the command line - options, args = parser.parse_args() - if options.pdb and getattr(options, 'coverage', False): - parser.error("'pdb' and 'coverage' options are exclusive") - filenames = [arg for arg in args if arg.endswith('.py')] - if filenames: - if len(filenames) > 1: - parser.error("only one filename is acceptable") - explicitfile = filenames[0] - args.remove(explicitfile) - else: - explicitfile = None - # someone wants DBC - testlib.ENABLE_DBC = options.dbc - newargs = parser.newargs - if options.skipped: - newargs.extend(['--skip', options.skipped]) - # restart implies exitfirst - if options.restart: - options.exitfirst = True - # append additional args to the new sys.argv and let unittest_main - # do the rest - newargs += args - return options, explicitfile - - - -def run(): - parser = make_parser() - rootdir, testercls = project_root(parser) - options, explicitfile = parseargs(parser) - # mock a new command line - sys.argv[1:] = parser.newargs - covermode = getattr(options, 'coverage', None) - cvg = None - if not '' in sys.path: - sys.path.insert(0, '') - if covermode: - # control_import_coverage(rootdir) - from logilab.devtools.lib.coverage import Coverage - cvg = Coverage([rootdir]) - cvg.erase() - cvg.start() - if DJANGO_FOUND and options.django: - tester = DjangoTester(cvg, options) - else: - tester = testercls(cvg, options) - if explicitfile: - cmd, args = tester.testfile, (explicitfile,) - elif options.testdir: - cmd, args = tester.testonedir, (options.testdir, options.exitfirst) - else: - cmd, args = tester.testall, (options.exitfirst,) - try: - try: - if options.profile: - import hotshot - prof = hotshot.Profile(options.profile) - prof.runcall(cmd, *args) - prof.close() - print 'profile data saved in', options.profile - else: - cmd(*args) - except SystemExit: - raise - except: - import traceback - traceback.print_exc() - finally: - if covermode: - cvg.stop() - cvg.save() - tester.show_report() - if covermode: - print 'coverage information stored, use it with pycoverage -ra' - sys.exit(tester.errcode) - -class SkipAwareTestProgram(unittest.TestProgram): - # XXX: don't try to stay close to unittest.py, use optparse - USAGE = """\ -Usage: %(progName)s [options] [test] [...] - -Options: - -h, --help Show this message - -v, --verbose Verbose output - -i, --pdb Enable test failure inspection - -x, --exitfirst Exit on first failure - -s, --skip skip test matching this pattern (no regexp for now) - -q, --quiet Minimal output - --color colorize tracebacks - - -m, --match Run only test whose tag match this pattern - - -P, --profile FILE: Run the tests using cProfile and saving results - in FILE - -Examples: - %(progName)s - run default set of tests - %(progName)s MyTestSuite - run suite 'MyTestSuite' - %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething - %(progName)s MyTestCase - run all 'test*' test methods - in MyTestCase -""" - def __init__(self, module='__main__', defaultTest=None, batchmode=False, - cvg=None, options=None, outstream=sys.stderr): - self.batchmode = batchmode - self.cvg = cvg - self.options = options - self.outstream = outstream - super(SkipAwareTestProgram, self).__init__( - module=module, defaultTest=defaultTest, - testLoader=NonStrictTestLoader()) - - def parseArgs(self, argv): - self.pdbmode = False - self.exitfirst = False - self.skipped_patterns = [] - self.test_pattern = None - self.tags_pattern = None - self.colorize = False - self.profile_name = None - import getopt - try: - options, args = getopt.getopt(argv[1:], 'hHvixrqcp:s:m:P:', - ['help', 'verbose', 'quiet', 'pdb', - 'exitfirst', 'restart', - 'skip=', 'color', 'match=', 'profile=']) - for opt, value in options: - if opt in ('-h', '-H', '--help'): - self.usageExit() - if opt in ('-i', '--pdb'): - self.pdbmode = True - if opt in ('-x', '--exitfirst'): - self.exitfirst = True - if opt in ('-r', '--restart'): - self.restart = True - self.exitfirst = True - if opt in ('-q', '--quiet'): - self.verbosity = 0 - if opt in ('-v', '--verbose'): - self.verbosity = 2 - if opt in ('-s', '--skip'): - self.skipped_patterns = [pat.strip() for pat in - value.split(', ')] - if opt == '--color': - self.colorize = True - if opt in ('-m', '--match'): - #self.tags_pattern = value - self.options["tag_pattern"] = value - if opt in ('-P', '--profile'): - self.profile_name = value - self.testLoader.skipped_patterns = self.skipped_patterns - if len(args) == 0 and self.defaultTest is None: - suitefunc = getattr(self.module, 'suite', None) - if isinstance(suitefunc, (types.FunctionType, - types.MethodType)): - self.test = self.module.suite() - else: - self.test = self.testLoader.loadTestsFromModule(self.module) - return - if len(args) > 0: - self.test_pattern = args[0] - self.testNames = args - else: - self.testNames = (self.defaultTest, ) - self.createTests() - except getopt.error, msg: - self.usageExit(msg) - - def runTests(self): - if self.profile_name: - import cProfile - cProfile.runctx('self._runTests()', globals(), locals(), self.profile_name ) - else: - return self._runTests() - - def _runTests(self): - self.testRunner = SkipAwareTextTestRunner(verbosity=self.verbosity, - stream=self.outstream, - exitfirst=self.exitfirst, - pdbmode=self.pdbmode, - cvg=self.cvg, - test_pattern=self.test_pattern, - skipped_patterns=self.skipped_patterns, - colorize=self.colorize, - batchmode=self.batchmode, - options=self.options) - - def removeSucceededTests(obj, succTests): - """ Recursive function that removes succTests from - a TestSuite or TestCase - """ - if isinstance(obj, unittest.TestSuite): - removeSucceededTests(obj._tests, succTests) - if isinstance(obj, list): - for el in obj[:]: - if isinstance(el, unittest.TestSuite): - removeSucceededTests(el, succTests) - elif isinstance(el, unittest.TestCase): - descr = '.'.join((el.__class__.__module__, - el.__class__.__name__, - el._testMethodName)) - if descr in succTests: - obj.remove(el) - # take care, self.options may be None - if getattr(self.options, 'restart', False): - # retrieve succeeded tests from FILE_RESTART - try: - restartfile = open(FILE_RESTART, 'r') - try: - succeededtests = list(elem.rstrip('\n\r') for elem in - restartfile.readlines()) - removeSucceededTests(self.test, succeededtests) - finally: - restartfile.close() - except Exception, ex: - raise Exception("Error while reading succeeded tests into %s: %s" - % (osp.join(os.getcwd(), FILE_RESTART), ex)) - - result = self.testRunner.run(self.test) - # help garbage collection: we want TestSuite, which hold refs to every - # executed TestCase, to be gc'ed - del self.test - if getattr(result, "debuggers", None) and \ - getattr(self, "pdbmode", None): - start_interactive_mode(result) - if not getattr(self, "batchmode", None): - sys.exit(not result.wasSuccessful()) - self.result = result - - -class SkipAwareTextTestRunner(unittest.TextTestRunner): - - def __init__(self, stream=sys.stderr, verbosity=1, - exitfirst=False, pdbmode=False, cvg=None, test_pattern=None, - skipped_patterns=(), colorize=False, batchmode=False, - options=None): - super(SkipAwareTextTestRunner, self).__init__(stream=stream, - verbosity=verbosity) - self.exitfirst = exitfirst - self.pdbmode = pdbmode - self.cvg = cvg - self.test_pattern = test_pattern - self.skipped_patterns = skipped_patterns - self.colorize = colorize - self.batchmode = batchmode - self.options = options - - def _this_is_skipped(self, testedname): - return any([(pat in testedname) for pat in self.skipped_patterns]) - - def _runcondition(self, test, skipgenerator=True): - if isinstance(test, testlib.InnerTest): - testname = test.name - else: - if isinstance(test, testlib.TestCase): - meth = test._get_test_method() - func = meth.im_func - testname = '%s.%s' % (meth.im_class.__name__, func.__name__) - elif isinstance(test, types.FunctionType): - func = test - testname = func.__name__ - elif isinstance(test, types.MethodType): - func = test.im_func - testname = '%s.%s' % (test.im_class.__name__, func.__name__) - else: - return True # Not sure when this happens - if testlib.is_generator(test) and skipgenerator: - return self.does_match_tags(test) # Let inner tests decide at run time - if self._this_is_skipped(testname): - return False # this was explicitly skipped - if self.test_pattern is not None: - try: - classpattern, testpattern = self.test_pattern.split('.') - klass, name = testname.split('.') - if classpattern not in klass or testpattern not in name: - return False - except ValueError: - if self.test_pattern not in testname: - return False - - return self.does_match_tags(test) - - def does_match_tags(self, test): - if self.options is not None: - tags_pattern = getattr(self.options, 'tags_pattern', None) - if tags_pattern is not None: - tags = getattr(test, 'tags', testlib.Tags()) - if tags.inherit and isinstance(test, types.MethodType): - tags = tags | getattr(test.im_class, 'tags', testlib.Tags()) - return tags.match(tags_pattern) - return True # no pattern - - def _makeResult(self): - return testlib.SkipAwareTestResult(self.stream, self.descriptions, - self.verbosity, self.exitfirst, - self.pdbmode, self.cvg, self.colorize) - - def run(self, test): - "Run the given test case or test suite." - result = self._makeResult() - startTime = time() - test(result, runcondition=self._runcondition, options=self.options) - stopTime = time() - timeTaken = stopTime - startTime - result.printErrors() - if not self.batchmode: - self.stream.writeln(result.separator2) - run = result.testsRun - self.stream.writeln("Ran %d test%s in %.3fs" % - (run, run != 1 and "s" or "", timeTaken)) - self.stream.writeln() - if not result.wasSuccessful(): - if self.colorize: - self.stream.write(textutils.colorize_ansi("FAILED", color='red')) - else: - self.stream.write("FAILED") - else: - if self.colorize: - self.stream.write(textutils.colorize_ansi("OK", color='green')) - else: - self.stream.write("OK") - failed, errored, skipped = map(len, (result.failures, - result.errors, - result.skipped)) - - det_results = [] - for name, value in (("failures", result.failures), - ("errors",result.errors), - ("skipped", result.skipped)): - if value: - det_results.append("%s=%i" % (name, len(value))) - if det_results: - self.stream.write(" (") - self.stream.write(', '.join(det_results)) - self.stream.write(")") - self.stream.writeln("") - return result - -class NonStrictTestLoader(unittest.TestLoader): - """ - Overrides default testloader to be able to omit classname when - specifying tests to run on command line. - - For example, if the file test_foo.py contains :: - - class FooTC(TestCase): - def test_foo1(self): # ... - def test_foo2(self): # ... - def test_bar1(self): # ... - - class BarTC(TestCase): - def test_bar2(self): # ... - - 'python test_foo.py' will run the 3 tests in FooTC - 'python test_foo.py FooTC' will run the 3 tests in FooTC - 'python test_foo.py test_foo' will run test_foo1 and test_foo2 - 'python test_foo.py test_foo1' will run test_foo1 - 'python test_foo.py test_bar' will run FooTC.test_bar1 and BarTC.test_bar2 - """ - - def __init__(self): - self.skipped_patterns = () - - # some magic here to accept empty list by extending - # and to provide callable capability - def loadTestsFromNames(self, names, module=None): - suites = [] - for name in names: - suites.extend(self.loadTestsFromName(name, module)) - return self.suiteClass(suites) - - def _collect_tests(self, module): - tests = {} - for obj in vars(module).values(): - if (issubclass(type(obj), (types.ClassType, type)) and - issubclass(obj, unittest.TestCase)): - classname = obj.__name__ - if classname[0] == '_' or self._this_is_skipped(classname): - continue - methodnames = [] - # obj is a TestCase class - for attrname in dir(obj): - if attrname.startswith(self.testMethodPrefix): - attr = getattr(obj, attrname) - if callable(attr): - methodnames.append(attrname) - # keep track of class (obj) for convenience - tests[classname] = (obj, methodnames) - return tests - - def loadTestsFromSuite(self, module, suitename): - try: - suite = getattr(module, suitename)() - except AttributeError: - return [] - assert hasattr(suite, '_tests'), \ - "%s.%s is not a valid TestSuite" % (module.__name__, suitename) - # python2.3 does not implement __iter__ on suites, we need to return - # _tests explicitly - return suite._tests - - def loadTestsFromName(self, name, module=None): - parts = name.split('.') - if module is None or len(parts) > 2: - # let the base class do its job here - return [super(NonStrictTestLoader, self).loadTestsFromName(name)] - tests = self._collect_tests(module) - collected = [] - if len(parts) == 1: - pattern = parts[0] - if callable(getattr(module, pattern, None) - ) and pattern not in tests: - # consider it as a suite - return self.loadTestsFromSuite(module, pattern) - if pattern in tests: - # case python unittest_foo.py MyTestTC - klass, methodnames = tests[pattern] - for methodname in methodnames: - collected = [klass(methodname) - for methodname in methodnames] - else: - # case python unittest_foo.py something - for klass, methodnames in tests.values(): - # skip methodname if matched by skipped_patterns - for skip_pattern in self.skipped_patterns: - methodnames = [methodname - for methodname in methodnames - if skip_pattern not in methodname] - collected += [klass(methodname) - for methodname in methodnames - if pattern in methodname] - elif len(parts) == 2: - # case "MyClass.test_1" - classname, pattern = parts - klass, methodnames = tests.get(classname, (None, [])) - for methodname in methodnames: - collected = [klass(methodname) for methodname in methodnames - if pattern in methodname] - return collected - - def _this_is_skipped(self, testedname): - return any([(pat in testedname) for pat in self.skipped_patterns]) - - def getTestCaseNames(self, testCaseClass): - """Return a sorted sequence of method names found within testCaseClass - """ - is_skipped = self._this_is_skipped - classname = testCaseClass.__name__ - if classname[0] == '_' or is_skipped(classname): - return [] - testnames = super(NonStrictTestLoader, self).getTestCaseNames( - testCaseClass) - return [testname for testname in testnames if not is_skipped(testname)] - -def _ts_run(self, result, runcondition=None, options=None): - self._wrapped_run(result,runcondition=runcondition, options=options) - self._tearDownPreviousClass(None, result) - self._handleModuleTearDown(result) - return result - -def _ts_wrapped_run(self, result, debug=False, runcondition=None, options=None): - for test in self: - if result.shouldStop: - break - if unittest_suite._isnotsuite(test): - self._tearDownPreviousClass(test, result) - self._handleModuleFixture(test, result) - self._handleClassSetUp(test, result) - result._previousTestClass = test.__class__ - if (getattr(test.__class__, '_classSetupFailed', False) or - getattr(result, '_moduleSetUpFailed', False)): - continue - - if hasattr(test, '_wrapped_run'): - try: - test._wrapped_run(result, debug, runcondition=runcondition, options=options) - except TypeError: - test._wrapped_run(result, debug) - elif not debug: - try: - test(result, runcondition, options) - except TypeError: - test(result) - else: - test.debug() - - -def enable_dbc(*args): - """ - Without arguments, return True if contracts can be enabled and should be - enabled (see option -d), return False otherwise. - - With arguments, return False if contracts can't or shouldn't be enabled, - otherwise weave ContractAspect with items passed as arguments. - """ - if not ENABLE_DBC: - return False - try: - from logilab.aspects.weaver import weaver - from logilab.aspects.lib.contracts import ContractAspect - except ImportError: - sys.stderr.write( - 'Warning: logilab.aspects is not available. Contracts disabled.') - return False - for arg in args: - weaver.weave_module(arg, ContractAspect) - return True - - -# monkeypatch unittest and doctest (ouch !) -unittest._TextTestResult = testlib.SkipAwareTestResult -unittest.TextTestRunner = SkipAwareTextTestRunner -unittest.TestLoader = NonStrictTestLoader -unittest.TestProgram = SkipAwareTestProgram - -if sys.version_info >= (2, 4): - doctest.DocTestCase.__bases__ = (testlib.TestCase,) - # XXX check python2.6 compatibility - #doctest.DocTestCase._cleanups = [] - #doctest.DocTestCase._out = [] -else: - unittest.FunctionTestCase.__bases__ = (testlib.TestCase,) -unittest.TestSuite.run = _ts_run -unittest.TestSuite._wrapped_run = _ts_wrapped_run diff --git a/pylibs/logilab/common/registry.py b/pylibs/logilab/common/registry.py deleted file mode 100644 index 29f3c56b..00000000 --- a/pylibs/logilab/common/registry.py +++ /dev/null @@ -1,973 +0,0 @@ -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of Logilab-common. -# -# Logilab-common is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation, either version 2.1 of the License, or (at your -# option) any later version. -# -# Logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with Logilab-common. If not, see . -"""This module provides bases for predicates dispatching (the pattern in use -here is similar to what's refered as multi-dispatch or predicate-dispatch in the -literature, though a bit different since the idea is to select across different -implementation 'e.g. classes), not to dispatch a message to a function or -method. It contains the following classes: - -* :class:`RegistryStore`, the top level object which loads implementation - objects and stores them into registries. You'll usually use it to access - registries and their contained objects; - -* :class:`Registry`, the base class which contains objects semantically grouped - (for instance, sharing a same API, hence the 'implementation' name). You'll - use it to select the proper implementation according to a context. Notice you - may use registries on their own without using the store. - -.. Note:: - - implementation objects are usually designed to be accessed through the - registry and not by direct instantiation, besides to use it as base classe. - -The selection procedure is delegated to a selector, which is responsible for -scoring the object according to some context. At the end of the selection, if an -implementation has been found, an instance of this class is returned. A selector -is built from one or more predicates combined together using AND, OR, NOT -operators (actually `&`, `|` and `~`). You'll thus find some base classes to -build predicates: - -* :class:`Predicate`, the abstract base predicate class - -* :class:`AndPredicate`, :class:`OrPredicate`, :class:`NotPredicate`, which you - shouldn't have to use directly. You'll use `&`, `|` and '~' operators between - predicates directly - -* :func:`objectify_predicate` - -You'll eventually find one concrete predicate: :class:`yes` - -.. autoclass:: RegistryStore -.. autoclass:: Registry - -Predicates ----------- -.. autoclass:: Predicate -.. autofunc:: objectify_predicate -.. autoclass:: yes - -Debugging ---------- -.. autoclass:: traced_selection - -Exceptions ----------- -.. autoclass:: RegistryException -.. autoclass:: RegistryNotFound -.. autoclass:: ObjectNotFound -.. autoclass:: NoSelectableObject -""" - -__docformat__ = "restructuredtext en" - -import sys -import types -import weakref -from os import listdir, stat -from os.path import join, isdir, exists -from logging import getLogger - -from logilab.common.logging_ext import set_log_methods - - -class RegistryException(Exception): - """Base class for registry exception.""" - -class RegistryNotFound(RegistryException): - """Raised when an unknown registry is requested. - - This is usually a programming/typo error. - """ - -class ObjectNotFound(RegistryException): - """Raised when an unregistered object is requested. - - This may be a programming/typo or a misconfiguration error. - """ - -class NoSelectableObject(RegistryException): - """Raised when no object is selectable for a given context.""" - def __init__(self, args, kwargs, objects): - self.args = args - self.kwargs = kwargs - self.objects = objects - - def __str__(self): - return ('args: %s, kwargs: %s\ncandidates: %s' - % (self.args, self.kwargs.keys(), self.objects)) - - -def _toload_info(path, extrapath, _toload=None): - """Return a dictionary of : and an ordered list of - (file, module name) to load - """ - from logilab.common.modutils import modpath_from_file - if _toload is None: - assert isinstance(path, list) - _toload = {}, [] - for fileordir in path: - if isdir(fileordir) and exists(join(fileordir, '__init__.py')): - subfiles = [join(fileordir, fname) for fname in listdir(fileordir)] - _toload_info(subfiles, extrapath, _toload) - elif fileordir[-3:] == '.py': - modpath = modpath_from_file(fileordir, extrapath) - # omit '__init__' from package's name to avoid loading that module - # once for each name when it is imported by some other object - # module. This supposes import in modules are done as:: - # - # from package import something - # - # not:: - # - # from package.__init__ import something - # - # which seems quite correct. - if modpath[-1] == '__init__': - modpath.pop() - modname = '.'.join(modpath) - _toload[0][modname] = fileordir - _toload[1].append((fileordir, modname)) - return _toload - - -def classid(cls): - """returns a unique identifier for an object class""" - return '%s.%s' % (cls.__module__, cls.__name__) - -def class_registries(cls, registryname): - """return a tuple of registry names (see __registries__)""" - if registryname: - return (registryname,) - return cls.__registries__ - - -class Registry(dict): - """The registry store a set of implementations associated to identifier: - - * to each identifier are associated a list of implementations - - * to select an implementation of a given identifier, you should use one of the - :meth:`select` or :meth:`select_or_none` method - - * to select a list of implementations for a context, you should use the - :meth:`possible_objects` method - - * dictionary like access to an identifier will return the bare list of - implementations for this identifier. - - To be usable in a registry, the only requirement is to have a `__select__` - attribute. - - At the end of the registration process, the :meth:`__registered__` - method is called on each registered object which have them, given the - registry in which it's registered as argument. - - Registration methods: - - .. automethod: register - .. automethod: unregister - - Selection methods: - - .. automethod: select - .. automethod: select_or_none - .. automethod: possible_objects - .. automethod: object_by_id - """ - def __init__(self, debugmode): - super(Registry, self).__init__() - self.debugmode = debugmode - - def __getitem__(self, name): - """return the registry (list of implementation objects) associated to - this name - """ - try: - return super(Registry, self).__getitem__(name) - except KeyError: - raise ObjectNotFound(name), None, sys.exc_info()[-1] - - def initialization_completed(self): - """call method __registered__() on registered objects when the callback - is defined""" - for objects in self.itervalues(): - for objectcls in objects: - registered = getattr(objectcls, '__registered__', None) - if registered: - registered(self) - if self.debugmode: - wrap_predicates(_lltrace) - - def register(self, obj, oid=None, clear=False): - """base method to add an object in the registry""" - assert not '__abstract__' in obj.__dict__ - assert obj.__select__ - oid = oid or obj.__regid__ - assert oid - if clear: - objects = self[oid] = [] - else: - objects = self.setdefault(oid, []) - assert not obj in objects, \ - 'object %s is already registered' % obj - objects.append(obj) - - def register_and_replace(self, obj, replaced): - """remove and register """ - # XXXFIXME this is a duplication of unregister() - # remove register_and_replace in favor of unregister + register - # or simplify by calling unregister then register here - if not isinstance(replaced, basestring): - replaced = classid(replaced) - # prevent from misspelling - assert obj is not replaced, 'replacing an object by itself: %s' % obj - registered_objs = self.get(obj.__regid__, ()) - for index, registered in enumerate(registered_objs): - if classid(registered) == replaced: - del registered_objs[index] - break - else: - self.warning('trying to replace %s that is not registered with %s', - replaced, obj) - self.register(obj) - - def unregister(self, obj): - """remove object from this registry""" - clsid = classid(obj) - oid = obj.__regid__ - for registered in self.get(oid, ()): - # use classid() to compare classes because vreg will probably - # have its own version of the class, loaded through execfile - if classid(registered) == clsid: - self[oid].remove(registered) - break - else: - self.warning('can\'t remove %s, no id %s in the registry', - clsid, oid) - - def all_objects(self): - """return a list containing all objects in this registry. - """ - result = [] - for objs in self.values(): - result += objs - return result - - # dynamic selection methods ################################################ - - def object_by_id(self, oid, *args, **kwargs): - """return object with the `oid` identifier. Only one object is expected - to be found. - - raise :exc:`ObjectNotFound` if not object with id in - - raise :exc:`AssertionError` if there is more than one object there - """ - objects = self[oid] - assert len(objects) == 1, objects - return objects[0](*args, **kwargs) - - def select(self, __oid, *args, **kwargs): - """return the most specific object among those with the given oid - according to the given context. - - raise :exc:`ObjectNotFound` if not object with id in - - raise :exc:`NoSelectableObject` if not object apply - """ - obj = self._select_best(self[__oid], *args, **kwargs) - if obj is None: - raise NoSelectableObject(args, kwargs, self[__oid] ) - return obj - - def select_or_none(self, __oid, *args, **kwargs): - """return the most specific object among those with the given oid - according to the given context, or None if no object applies. - """ - try: - return self.select(__oid, *args, **kwargs) - except (NoSelectableObject, ObjectNotFound): - return None - - def possible_objects(self, *args, **kwargs): - """return an iterator on possible objects in this registry for the given - context - """ - for objects in self.itervalues(): - obj = self._select_best(objects, *args, **kwargs) - if obj is None: - continue - yield obj - - def _select_best(self, objects, *args, **kwargs): - """return an instance of the most specific object according - to parameters - - return None if not object apply (don't raise `NoSelectableObject` since - it's costly when searching objects using `possible_objects` - (e.g. searching for hooks). - """ - score, winners = 0, None - for obj in objects: - objectscore = obj.__select__(obj, *args, **kwargs) - if objectscore > score: - score, winners = objectscore, [obj] - elif objectscore > 0 and objectscore == score: - winners.append(obj) - if winners is None: - return None - if len(winners) > 1: - # log in production environement / test, error while debugging - msg = 'select ambiguity: %s\n(args: %s, kwargs: %s)' - if self.debugmode: - # raise bare exception in debug mode - raise Exception(msg % (winners, args, kwargs.keys())) - self.error(msg, winners, args, kwargs.keys()) - # return the result of calling the object - return winners[0](*args, **kwargs) - - # these are overridden by set_log_methods below - # only defining here to prevent pylint from complaining - info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None - - -class RegistryStore(dict): - """This class is responsible for loading implementations and storing them - in their registry which are created on the fly as needed. - - It handles dynamic registration of objects and provides a convenient api to - access them. To be recognized as an object that should be stored into one of - the store's registry (:class:`Registry`), an object (usually a class) has - the following attributes, used control how they interact with the registry: - - :attr:`__registry__` or `__registries__` - name of the registry for this object (string like 'views', 'templates'...) - or list of registry names if you want your object to be added to multiple - registries - - :attr:`__regid__` - implementation's identifier in the registry (string like 'main', - 'primary', 'folder_box') - - :attr:`__select__` - the implementation's selector - - Moreover, the :attr:`__abstract__` attribute may be set to `True` to - indicate that a class is abstract and should not be registered (inherited - attributes not considered). - - .. Note:: - - When using the store to load objects dynamically, you *always* have - to use **super()** to get the methods and attributes of the - superclasses, and not use the class identifier. Else, you'll get into - trouble when reloading comes into the place. - - For example, instead of writing:: - - class Thing(Parent): - __regid__ = 'athing' - __select__ = yes() - def f(self, arg1): - Parent.f(self, arg1) - - You must write:: - - class Thing(Parent): - __regid__ = 'athing' - __select__ = yes() - def f(self, arg1): - super(Parent, self).f(arg1) - - Controlling objects registration - -------------------------------- - - Dynamic loading is triggered by calling the :meth:`register_objects` method, - given a list of directory to inspect for python modules. - - .. automethod: register_objects - - For each module, by default, all compatible objects are registered - automatically, though if some objects have to replace other objects, or have - to be included only if some condition is met, you'll have to define a - `registration_callback(vreg)` function in your module and explicitly - register **all objects** in this module, using the api defined below. - - - .. automethod:: RegistryStore.register_all - .. automethod:: RegistryStore.register_and_replace - .. automethod:: RegistryStore.register - .. automethod:: RegistryStore.unregister - - .. Note:: - Once the function `registration_callback(vreg)` is implemented in a - module, all the objects from this module have to be explicitly - registered as it disables the automatic objects registration. - - - Examples: - - .. sourcecode:: python - - # cubicweb/web/views/basecomponents.py - def registration_callback(store): - # register everything in the module except SeeAlsoComponent - store.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) - # conditionally register SeeAlsoVComponent - if 'see_also' in store.schema: - store.register(SeeAlsoVComponent) - - In this example, we register all application object classes defined in the module - except `SeeAlsoVComponent`. This class is then registered only if the 'see_also' - relation type is defined in the instance'schema. - - .. sourcecode:: python - - # goa/appobjects/sessions.py - def registration_callback(store): - store.register(SessionsCleaner) - # replace AuthenticationManager by GAEAuthenticationManager - store.register_and_replace(GAEAuthenticationManager, AuthenticationManager) - # replace PersistentSessionManager by GAEPersistentSessionManager - store.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager) - - In this example, we explicitly register classes one by one: - - * the `SessionCleaner` class - * the `GAEAuthenticationManager` to replace the `AuthenticationManager` - * the `GAEPersistentSessionManager` to replace the `PersistentSessionManager` - - If at some point we register a new appobject class in this module, it won't be - registered at all without modification to the `registration_callback` - implementation. The previous example will register it though, thanks to the call - to the `register_all` method. - - Controlling registry instantation - --------------------------------- - The `REGISTRY_FACTORY` class dictionary allows to specify which class should - be instantiated for a given registry name. The class associated to `None` in - it will be the class used when there is no specific class for a name. - """ - - def __init__(self, debugmode=False): - super(RegistryStore, self).__init__() - self.debugmode = debugmode - - def reset(self): - """clear all registries managed by this store""" - # don't use self.clear, we want to keep existing subdictionaries - for subdict in self.itervalues(): - subdict.clear() - self._lastmodifs = {} - - def __getitem__(self, name): - """return the registry (dictionary of class objects) associated to - this name - """ - try: - return super(RegistryStore, self).__getitem__(name) - except KeyError: - raise RegistryNotFound(name), None, sys.exc_info()[-1] - - # methods for explicit (un)registration ################################### - - # default class, when no specific class set - REGISTRY_FACTORY = {None: Registry} - - def registry_class(self, regid): - """return existing registry named regid or use factory to create one and - return it""" - try: - return self.REGISTRY_FACTORY[regid] - except KeyError: - return self.REGISTRY_FACTORY[None] - - def setdefault(self, regid): - try: - return self[regid] - except KeyError: - self[regid] = self.registry_class(regid)(self.debugmode) - return self[regid] - - def register_all(self, objects, modname, butclasses=()): - """register all `objects` given. Objects which are not from the module - `modname` or which are in `butclasses` won't be registered. - - Typical usage is: - - .. sourcecode:: python - - store.register_all(globals().values(), __name__, (ClassIWantToRegisterExplicitly,)) - - So you get partially automatic registration, keeping manual registration - for some object (to use - :meth:`~logilab.common.registry.RegistryStore.register_and_replace` - for instance) - """ - for obj in objects: - try: - if obj.__module__ != modname or obj in butclasses: - continue - oid = obj.__regid__ - except AttributeError: - continue - if oid and not obj.__dict__.get('__abstract__'): - self.register(obj, oid=oid) - - def register(self, obj, registryname=None, oid=None, clear=False): - """register `obj` implementation into `registryname` or - `obj.__registry__` if not specified, with identifier `oid` or - `obj.__regid__` if not specified. - - If `clear` is true, all objects with the same identifier will be - previously unregistered. - """ - assert not obj.__dict__.get('__abstract__') - try: - vname = obj.__name__ - except AttributeError: - # XXX may occurs? - vname = obj.__class__.__name__ - for registryname in class_registries(obj, registryname): - registry = self.setdefault(registryname) - registry.register(obj, oid=oid, clear=clear) - self.debug('register %s in %s[\'%s\']', - vname, registryname, oid or obj.__regid__) - self._loadedmods.setdefault(obj.__module__, {})[classid(obj)] = obj - - def unregister(self, obj, registryname=None): - """unregister `obj` implementation object from the registry - `registryname` or `obj.__registry__` if not specified. - """ - for registryname in class_registries(obj, registryname): - self[registryname].unregister(obj) - - def register_and_replace(self, obj, replaced, registryname=None): - """register `obj` implementation object into `registryname` or - `obj.__registry__` if not specified. If found, the `replaced` object - will be unregistered first (else a warning will be issued as it's - generally unexpected). - """ - for registryname in class_registries(obj, registryname): - self[registryname].register_and_replace(obj, replaced) - - # initialization methods ################################################### - - def init_registration(self, path, extrapath=None): - """reset registry and walk down path to return list of (path, name) - file modules to be loaded""" - # XXX make this private by renaming it to _init_registration ? - self.reset() - # compute list of all modules that have to be loaded - self._toloadmods, filemods = _toload_info(path, extrapath) - # XXX is _loadedmods still necessary ? It seems like it's useful - # to avoid loading same module twice, especially with the - # _load_ancestors_then_object logic but this needs to be checked - self._loadedmods = {} - return filemods - - def register_objects(self, path, extrapath=None): - """register all objects found walking down """ - # load views from each directory in the instance's path - # XXX inline init_registration ? - filemods = self.init_registration(path, extrapath) - for filepath, modname in filemods: - self.load_file(filepath, modname) - self.initialization_completed() - - def initialization_completed(self): - """call initialization_completed() on all known registries""" - for reg in self.itervalues(): - reg.initialization_completed() - - def _mdate(self, filepath): - try: - return stat(filepath)[-2] - except OSError: - # this typically happens on emacs backup files (.#foo.py) - self.warning('Unable to load %s. It is likely to be a backup file', - filepath) - return None - - def is_reload_needed(self, path): - """return True if something module changed and the registry should be - reloaded - """ - lastmodifs = self._lastmodifs - for fileordir in path: - if isdir(fileordir) and exists(join(fileordir, '__init__.py')): - if self.is_reload_needed([join(fileordir, fname) - for fname in listdir(fileordir)]): - return True - elif fileordir[-3:] == '.py': - mdate = self._mdate(fileordir) - if mdate is None: - continue # backup file, see _mdate implementation - elif "flymake" in fileordir: - # flymake + pylint in use, don't consider these they will corrupt the registry - continue - if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate: - self.info('File %s changed since last visit', fileordir) - return True - return False - - def load_file(self, filepath, modname): - """load app objects from a python file""" - from logilab.common.modutils import load_module_from_name - if modname in self._loadedmods: - return - self._loadedmods[modname] = {} - mdate = self._mdate(filepath) - if mdate is None: - return # backup file, see _mdate implementation - elif "flymake" in filepath: - # flymake + pylint in use, don't consider these they will corrupt the registry - return - # set update time before module loading, else we get some reloading - # weirdness in case of syntax error or other error while importing the - # module - self._lastmodifs[filepath] = mdate - # load the module - module = load_module_from_name(modname) - self.load_module(module) - - def load_module(self, module): - """load objects from a module using registration_callback() when it exists - """ - self.info('loading %s from %s', module.__name__, module.__file__) - if hasattr(module, 'registration_callback'): - module.registration_callback(self) - else: - for objname, obj in vars(module).items(): - if objname.startswith('_'): - continue - self._load_ancestors_then_object(module.__name__, obj) - - def _load_ancestors_then_object(self, modname, objectcls): - """handle automatic object class registration: - - - first ensure parent classes are already registered - - - class with __abstract__ == True in their local dictionary or - with a name starting with an underscore are not registered - - - object class needs to have __registry__ and __regid__ attributes - set to a non empty string to be registered. - """ - # imported classes - objmodname = getattr(objectcls, '__module__', None) - if objmodname != modname: - if objmodname in self._toloadmods: - self.load_file(self._toloadmods[objmodname], objmodname) - return - # skip non registerable object - try: - if not (getattr(objectcls, '__regid__', None) - and getattr(objectcls, '__select__', None)): - return - except TypeError: - return - clsid = classid(objectcls) - if clsid in self._loadedmods[modname]: - return - self._loadedmods[modname][clsid] = objectcls - for parent in objectcls.__bases__: - self._load_ancestors_then_object(modname, parent) - if (objectcls.__dict__.get('__abstract__') - or objectcls.__name__[0] == '_' - or not objectcls.__registries__ - or not objectcls.__regid__): - return - try: - self.register(objectcls) - except Exception, ex: - if self.debugmode: - raise - self.exception('object %s registration failed: %s', - objectcls, ex) - - # these are overridden by set_log_methods below - # only defining here to prevent pylint from complaining - info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None - - -# init logging -set_log_methods(RegistryStore, getLogger('registry.store')) -set_log_methods(Registry, getLogger('registry')) - - -# helpers for debugging selectors -TRACED_OIDS = None - -def _trace_selector(cls, selector, args, ret): - vobj = args[0] - if TRACED_OIDS == 'all' or vobj.__regid__ in TRACED_OIDS: - print '%s -> %s for %s(%s)' % (cls, ret, vobj, vobj.__regid__) - -def _lltrace(selector): - """use this decorator on your predicates so they become traceable with - :class:`traced_selection` - """ - def traced(cls, *args, **kwargs): - ret = selector(cls, *args, **kwargs) - if TRACED_OIDS is not None: - _trace_selector(cls, selector, args, ret) - return ret - traced.__name__ = selector.__name__ - traced.__doc__ = selector.__doc__ - return traced - -class traced_selection(object): # pylint: disable=C0103 - """ - Typical usage is : - - .. sourcecode:: python - - >>> from logilab.common.registry import traced_selection - >>> with traced_selection(): - ... # some code in which you want to debug selectors - ... # for all objects - - Don't forget the 'from __future__ import with_statement' at the module top-level - if you're using python prior to 2.6. - - This will yield lines like this in the logs:: - - selector one_line_rset returned 0 for - - You can also give to :class:`traced_selection` the identifiers of objects on - which you want to debug selection ('oid1' and 'oid2' in the example above). - - .. sourcecode:: python - - >>> with traced_selection( ('regid1', 'regid2') ): - ... # some code in which you want to debug selectors - ... # for objects with __regid__ 'regid1' and 'regid2' - - A potentially useful point to set up such a tracing function is - the `logilab.common.registry.Registry.select` method body. - """ - - def __init__(self, traced='all'): - self.traced = traced - - def __enter__(self): - global TRACED_OIDS - TRACED_OIDS = self.traced - - def __exit__(self, exctype, exc, traceback): - global TRACED_OIDS - TRACED_OIDS = None - return traceback is None - -# selector base classes and operations ######################################## - -def objectify_predicate(selector_func): - """Most of the time, a simple score function is enough to build a selector. - The :func:`objectify_predicate` decorator turn it into a proper selector - class:: - - @objectify_predicate - def one(cls, req, rset=None, **kwargs): - return 1 - - class MyView(View): - __select__ = View.__select__ & one() - - """ - return type(selector_func.__name__, (Predicate,), - {'__doc__': selector_func.__doc__, - '__call__': lambda self, *a, **kw: selector_func(*a, **kw)}) - - -_PREDICATES = {} - -def wrap_predicates(decorator): - for predicate in _PREDICATES.itervalues(): - if not '_decorators' in predicate.__dict__: - predicate._decorators = set() - if decorator in predicate._decorators: - continue - predicate._decorators.add(decorator) - predicate.__call__ = decorator(predicate.__call__) - -class PredicateMetaClass(type): - def __new__(cls, *args, **kwargs): - # use __new__ so subclasses doesn't have to call Predicate.__init__ - inst = type.__new__(cls, *args, **kwargs) - proxy = weakref.proxy(inst, lambda p: _PREDICATES.pop(id(p))) - _PREDICATES[id(proxy)] = proxy - return inst - -class Predicate(object): - """base class for selector classes providing implementation - for operators ``&``, ``|`` and ``~`` - - This class is only here to give access to binary operators, the selector - logic itself should be implemented in the :meth:`__call__` method. Notice it - should usually accept any arbitrary arguments (the context), though that may - vary depending on your usage of the registry. - - a selector is called to help choosing the correct object for a - particular context by returning a score (`int`) telling how well - the implementation given as first argument fit to the given context. - - 0 score means that the class doesn't apply. - """ - __metaclass__ = PredicateMetaClass - - @property - def func_name(self): - # backward compatibility - return self.__class__.__name__ - - def search_selector(self, selector): - """search for the given selector, selector instance or tuple of - selectors in the selectors tree. Return None if not found. - """ - if self is selector: - return self - if (isinstance(selector, type) or isinstance(selector, tuple)) and \ - isinstance(self, selector): - return self - return None - - def __str__(self): - return self.__class__.__name__ - - def __and__(self, other): - return AndPredicate(self, other) - def __rand__(self, other): - return AndPredicate(other, self) - def __iand__(self, other): - return AndPredicate(self, other) - def __or__(self, other): - return OrPredicate(self, other) - def __ror__(self, other): - return OrPredicate(other, self) - def __ior__(self, other): - return OrPredicate(self, other) - - def __invert__(self): - return NotPredicate(self) - - # XXX (function | function) or (function & function) not managed yet - - def __call__(self, cls, *args, **kwargs): - return NotImplementedError("selector %s must implement its logic " - "in its __call__ method" % self.__class__) - - def __repr__(self): - return u'' % (self.__class__.__name__, id(self)) - - -class MultiPredicate(Predicate): - """base class for compound selector classes""" - - def __init__(self, *selectors): - self.selectors = self.merge_selectors(selectors) - - def __str__(self): - return '%s(%s)' % (self.__class__.__name__, - ','.join(str(s) for s in self.selectors)) - - @classmethod - def merge_selectors(cls, selectors): - """deal with selector instanciation when necessary and merge - multi-selectors if possible: - - AndPredicate(AndPredicate(sel1, sel2), AndPredicate(sel3, sel4)) - ==> AndPredicate(sel1, sel2, sel3, sel4) - """ - merged_selectors = [] - for selector in selectors: - # XXX do we really want magic-transformations below? - # if so, wanna warn about them? - if isinstance(selector, types.FunctionType): - selector = objectify_predicate(selector)() - if isinstance(selector, type) and issubclass(selector, Predicate): - selector = selector() - assert isinstance(selector, Predicate), selector - if isinstance(selector, cls): - merged_selectors += selector.selectors - else: - merged_selectors.append(selector) - return merged_selectors - - def search_selector(self, selector): - """search for the given selector or selector instance (or tuple of - selectors) in the selectors tree. Return None if not found - """ - for childselector in self.selectors: - if childselector is selector: - return childselector - found = childselector.search_selector(selector) - if found is not None: - return found - # if not found in children, maybe we are looking for self? - return super(MultiPredicate, self).search_selector(selector) - - -class AndPredicate(MultiPredicate): - """and-chained selectors""" - def __call__(self, cls, *args, **kwargs): - score = 0 - for selector in self.selectors: - partscore = selector(cls, *args, **kwargs) - if not partscore: - return 0 - score += partscore - return score - - -class OrPredicate(MultiPredicate): - """or-chained selectors""" - def __call__(self, cls, *args, **kwargs): - for selector in self.selectors: - partscore = selector(cls, *args, **kwargs) - if partscore: - return partscore - return 0 - -class NotPredicate(Predicate): - """negation selector""" - def __init__(self, selector): - self.selector = selector - - def __call__(self, cls, *args, **kwargs): - score = self.selector(cls, *args, **kwargs) - return int(not score) - - def __str__(self): - return 'NOT(%s)' % self.selector - - -class yes(Predicate): # pylint: disable=C0103 - """Return the score given as parameter, with a default score of 0.5 so any - other selector take precedence. - - Usually used for objects which can be selected whatever the context, or - also sometimes to add arbitrary points to a score. - - Take care, `yes(0)` could be named 'no'... - """ - def __init__(self, score=0.5): - self.score = score - - def __call__(self, *args, **kwargs): - return self.score diff --git a/pylibs/logilab/common/shellutils.py b/pylibs/logilab/common/shellutils.py deleted file mode 100644 index 749cbac3..00000000 --- a/pylibs/logilab/common/shellutils.py +++ /dev/null @@ -1,456 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""shell/term utilities, useful to write some python scripts instead of shell -scripts. -""" -__docformat__ = "restructuredtext en" - -import os -import glob -import shutil -import stat -import sys -import tempfile -import time -import fnmatch -import errno -import string -import random -from os.path import exists, isdir, islink, basename, join - -from logilab.common import STD_BLACKLIST, _handle_blacklist -from logilab.common.compat import raw_input -from logilab.common.compat import str_to_bytes - -try: - from logilab.common.proc import ProcInfo, NoSuchProcess -except ImportError: - # windows platform - class NoSuchProcess(Exception): pass - - def ProcInfo(pid): - raise NoSuchProcess() - - -class tempdir(object): - - def __enter__(self): - self.path = tempfile.mkdtemp() - return self.path - - def __exit__(self, exctype, value, traceback): - # rmtree in all cases - shutil.rmtree(self.path) - return traceback is None - - -class pushd(object): - def __init__(self, directory): - self.directory = directory - - def __enter__(self): - self.cwd = os.getcwd() - os.chdir(self.directory) - return self.directory - - def __exit__(self, exctype, value, traceback): - os.chdir(self.cwd) - - -def chown(path, login=None, group=None): - """Same as `os.chown` function but accepting user login or group name as - argument. If login or group is omitted, it's left unchanged. - - Note: you must own the file to chown it (or be root). Otherwise OSError is raised. - """ - if login is None: - uid = -1 - else: - try: - uid = int(login) - except ValueError: - import pwd # Platforms: Unix - uid = pwd.getpwnam(login).pw_uid - if group is None: - gid = -1 - else: - try: - gid = int(group) - except ValueError: - import grp - gid = grp.getgrnam(group).gr_gid - os.chown(path, uid, gid) - -def mv(source, destination, _action=shutil.move): - """A shell-like mv, supporting wildcards. - """ - sources = glob.glob(source) - if len(sources) > 1: - assert isdir(destination) - for filename in sources: - _action(filename, join(destination, basename(filename))) - else: - try: - source = sources[0] - except IndexError: - raise OSError('No file matching %s' % source) - if isdir(destination) and exists(destination): - destination = join(destination, basename(source)) - try: - _action(source, destination) - except OSError, ex: - raise OSError('Unable to move %r to %r (%s)' % ( - source, destination, ex)) - -def rm(*files): - """A shell-like rm, supporting wildcards. - """ - for wfile in files: - for filename in glob.glob(wfile): - if islink(filename): - os.remove(filename) - elif isdir(filename): - shutil.rmtree(filename) - else: - os.remove(filename) - -def cp(source, destination): - """A shell-like cp, supporting wildcards. - """ - mv(source, destination, _action=shutil.copy) - -def find(directory, exts, exclude=False, blacklist=STD_BLACKLIST): - """Recursively find files ending with the given extensions from the directory. - - :type directory: str - :param directory: - directory where the search should start - - :type exts: basestring or list or tuple - :param exts: - extensions or lists or extensions to search - - :type exclude: boolean - :param exts: - if this argument is True, returning files NOT ending with the given - extensions - - :type blacklist: list or tuple - :param blacklist: - optional list of files or directory to ignore, default to the value of - `logilab.common.STD_BLACKLIST` - - :rtype: list - :return: - the list of all matching files - """ - if isinstance(exts, basestring): - exts = (exts,) - if exclude: - def match(filename, exts): - for ext in exts: - if filename.endswith(ext): - return False - return True - else: - def match(filename, exts): - for ext in exts: - if filename.endswith(ext): - return True - return False - files = [] - for dirpath, dirnames, filenames in os.walk(directory): - _handle_blacklist(blacklist, dirnames, filenames) - # don't append files if the directory is blacklisted - dirname = basename(dirpath) - if dirname in blacklist: - continue - files.extend([join(dirpath, f) for f in filenames if match(f, exts)]) - return files - - -def globfind(directory, pattern, blacklist=STD_BLACKLIST): - """Recursively finds files matching glob `pattern` under `directory`. - - This is an alternative to `logilab.common.shellutils.find`. - - :type directory: str - :param directory: - directory where the search should start - - :type pattern: basestring - :param pattern: - the glob pattern (e.g *.py, foo*.py, etc.) - - :type blacklist: list or tuple - :param blacklist: - optional list of files or directory to ignore, default to the value of - `logilab.common.STD_BLACKLIST` - - :rtype: iterator - :return: - iterator over the list of all matching files - """ - for curdir, dirnames, filenames in os.walk(directory): - _handle_blacklist(blacklist, dirnames, filenames) - for fname in fnmatch.filter(filenames, pattern): - yield join(curdir, fname) - -def unzip(archive, destdir): - import zipfile - if not exists(destdir): - os.mkdir(destdir) - zfobj = zipfile.ZipFile(archive) - for name in zfobj.namelist(): - if name.endswith('/'): - os.mkdir(join(destdir, name)) - else: - outfile = open(join(destdir, name), 'wb') - outfile.write(zfobj.read(name)) - outfile.close() - -class Execute: - """This is a deadlock safe version of popen2 (no stdin), that returns - an object with errorlevel, out and err. - """ - - def __init__(self, command): - outfile = tempfile.mktemp() - errfile = tempfile.mktemp() - self.status = os.system("( %s ) >%s 2>%s" % - (command, outfile, errfile)) >> 8 - self.out = open(outfile, "r").read() - self.err = open(errfile, "r").read() - os.remove(outfile) - os.remove(errfile) - -def acquire_lock(lock_file, max_try=10, delay=10, max_delay=3600): - """Acquire a lock represented by a file on the file system - - If the process written in lock file doesn't exist anymore, we remove the - lock file immediately - If age of the lock_file is greater than max_delay, then we raise a UserWarning - """ - count = abs(max_try) - while count: - try: - fd = os.open(lock_file, os.O_EXCL | os.O_RDWR | os.O_CREAT) - os.write(fd, str_to_bytes(str(os.getpid())) ) - os.close(fd) - return True - except OSError, e: - if e.errno == errno.EEXIST: - try: - fd = open(lock_file, "r") - pid = int(fd.readline()) - pi = ProcInfo(pid) - age = (time.time() - os.stat(lock_file)[stat.ST_MTIME]) - if age / max_delay > 1 : - raise UserWarning("Command '%s' (pid %s) has locked the " - "file '%s' for %s minutes" - % (pi.name(), pid, lock_file, age/60)) - except UserWarning: - raise - except NoSuchProcess: - os.remove(lock_file) - except Exception: - # The try block is not essential. can be skipped. - # Note: ProcInfo object is only available for linux - # process information are not accessible... - # or lock_file is no more present... - pass - else: - raise - count -= 1 - time.sleep(delay) - else: - raise Exception('Unable to acquire %s' % lock_file) - -def release_lock(lock_file): - """Release a lock represented by a file on the file system.""" - os.remove(lock_file) - - -class ProgressBar(object): - """A simple text progression bar.""" - - def __init__(self, nbops, size=20, stream=sys.stdout, title=''): - if title: - self._fstr = '\r%s [%%-%ss]' % (title, int(size)) - else: - self._fstr = '\r[%%-%ss]' % int(size) - self._stream = stream - self._total = nbops - self._size = size - self._current = 0 - self._progress = 0 - self._current_text = None - self._last_text_write_size = 0 - - def _get_text(self): - return self._current_text - - def _set_text(self, text=None): - if text != self._current_text: - self._current_text = text - self.refresh() - - def _del_text(self): - self.text = None - - text = property(_get_text, _set_text, _del_text) - - def update(self, offset=1, exact=False): - """Move FORWARD to new cursor position (cursor will never go backward). - - :offset: fraction of ``size`` - - :exact: - - - False: offset relative to current cursor position if True - - True: offset as an asbsolute position - - """ - if exact: - self._current = offset - else: - self._current += offset - - progress = int((float(self._current)/float(self._total))*self._size) - if progress > self._progress: - self._progress = progress - self.refresh() - - def refresh(self): - """Refresh the progression bar display.""" - self._stream.write(self._fstr % ('.' * min(self._progress, self._size)) ) - if self._last_text_write_size or self._current_text: - template = ' %%-%is' % (self._last_text_write_size) - text = self._current_text - if text is None: - text = '' - self._stream.write(template % text) - self._last_text_write_size = len(text.rstrip()) - self._stream.flush() - - def finish(self): - self._stream.write('\n') - self._stream.flush() - - -class DummyProgressBar(object): - __slot__ = ('text',) - - def refresh(self): - pass - def update(self): - pass - def finish(self): - pass - - -_MARKER = object() -class progress(object): - - def __init__(self, nbops=_MARKER, size=_MARKER, stream=_MARKER, title=_MARKER, enabled=True): - self.nbops = nbops - self.size = size - self.stream = stream - self.title = title - self.enabled = enabled - - def __enter__(self): - if self.enabled: - kwargs = {} - for attr in ('nbops', 'size', 'stream', 'title'): - value = getattr(self, attr) - if value is not _MARKER: - kwargs[attr] = value - self.pb = ProgressBar(**kwargs) - else: - self.pb = DummyProgressBar() - return self.pb - - def __exit__(self, exc_type, exc_val, exc_tb): - self.pb.finish() - -class RawInput(object): - - def __init__(self, input=None, printer=None): - self._input = input or raw_input - self._print = printer - - def ask(self, question, options, default): - assert default in options - choices = [] - for option in options: - if option == default: - label = option[0].upper() - else: - label = option[0].lower() - if len(option) > 1: - label += '(%s)' % option[1:].lower() - choices.append((option, label)) - prompt = "%s [%s]: " % (question, - '/'.join([opt[1] for opt in choices])) - tries = 3 - while tries > 0: - answer = self._input(prompt).strip().lower() - if not answer: - return default - possible = [option for option, label in choices - if option.lower().startswith(answer)] - if len(possible) == 1: - return possible[0] - elif len(possible) == 0: - msg = '%s is not an option.' % answer - else: - msg = ('%s is an ambiguous answer, do you mean %s ?' % ( - answer, ' or '.join(possible))) - if self._print: - self._print(msg) - else: - print msg - tries -= 1 - raise Exception('unable to get a sensible answer') - - def confirm(self, question, default_is_yes=True): - default = default_is_yes and 'y' or 'n' - answer = self.ask(question, ('y', 'n'), default) - return answer == 'y' - -ASK = RawInput() - - -def getlogin(): - """avoid using os.getlogin() because of strange tty / stdin problems - (man 3 getlogin) - Another solution would be to use $LOGNAME, $USER or $USERNAME - """ - if sys.platform != 'win32': - import pwd # Platforms: Unix - return pwd.getpwuid(os.getuid())[0] - else: - return os.environ['USERNAME'] - -def generate_password(length=8, vocab=string.ascii_letters + string.digits): - """dumb password generation function""" - pwd = '' - for i in xrange(length): - pwd += random.choice(vocab) - return pwd diff --git a/pylibs/logilab/common/sphinx_ext.py b/pylibs/logilab/common/sphinx_ext.py deleted file mode 100644 index a24608ce..00000000 --- a/pylibs/logilab/common/sphinx_ext.py +++ /dev/null @@ -1,87 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -from logilab.common.decorators import monkeypatch - -from sphinx.ext import autodoc - -class DocstringOnlyModuleDocumenter(autodoc.ModuleDocumenter): - objtype = 'docstring' - def format_signature(self): - pass - def add_directive_header(self, sig): - pass - def document_members(self, all_members=False): - pass - - def resolve_name(self, modname, parents, path, base): - if modname is not None: - return modname, parents + [base] - return (path or '') + base, [] - - -#autodoc.add_documenter(DocstringOnlyModuleDocumenter) - -def setup(app): - app.add_autodocumenter(DocstringOnlyModuleDocumenter) - - - -from sphinx.ext.autodoc import (ViewList, Options, AutodocReporter, nodes, - assemble_option_dict, nested_parse_with_titles) - -@monkeypatch(autodoc.AutoDirective) -def run(self): - self.filename_set = set() # a set of dependent filenames - self.reporter = self.state.document.reporter - self.env = self.state.document.settings.env - self.warnings = [] - self.result = ViewList() - - # find out what documenter to call - objtype = self.name[4:] - doc_class = self._registry[objtype] - # process the options with the selected documenter's option_spec - self.genopt = Options(assemble_option_dict( - self.options.items(), doc_class.option_spec)) - # generate the output - documenter = doc_class(self, self.arguments[0]) - documenter.generate(more_content=self.content) - if not self.result: - return self.warnings - - # record all filenames as dependencies -- this will at least - # partially make automatic invalidation possible - for fn in self.filename_set: - self.env.note_dependency(fn) - - # use a custom reporter that correctly assigns lines to source - # filename/description and lineno - old_reporter = self.state.memo.reporter - self.state.memo.reporter = AutodocReporter(self.result, - self.state.memo.reporter) - if self.name in ('automodule', 'autodocstring'): - node = nodes.section() - # necessary so that the child nodes get the right source/line set - node.document = self.state.document - nested_parse_with_titles(self.state, self.result, node) - else: - node = nodes.paragraph() - node.document = self.state.document - self.state.nested_parse(self.result, 0, node) - self.state.memo.reporter = old_reporter - return self.warnings + node.children diff --git a/pylibs/logilab/common/sphinxutils.py b/pylibs/logilab/common/sphinxutils.py deleted file mode 100644 index ab6e8a18..00000000 --- a/pylibs/logilab/common/sphinxutils.py +++ /dev/null @@ -1,122 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Sphinx utils - -ModuleGenerator: Generate a file that lists all the modules of a list of -packages in order to pull all the docstring. -This should not be used in a makefile to systematically generate sphinx -documentation! - -Typical usage: - ->>> from logilab.common.sphinxutils import ModuleGenerator ->>> mgen = ModuleGenerator('logilab common', '/home/adim/src/logilab/common') ->>> mgen.generate('api_logilab_common.rst', exclude_dirs=('test',)) -""" - -import os, sys -import os.path as osp -import inspect - -from logilab.common import STD_BLACKLIST -from logilab.common.shellutils import globfind -from logilab.common.modutils import load_module_from_file, modpath_from_file - -def module_members(module): - members = [] - for name, value in inspect.getmembers(module): - if getattr(value, '__module__', None) == module.__name__: - members.append( (name, value) ) - return sorted(members) - - -def class_members(klass): - return sorted([name for name in vars(klass) - if name not in ('__doc__', '__module__', - '__dict__', '__weakref__')]) - -class ModuleGenerator: - file_header = """.. -*- coding: utf-8 -*-\n\n%s\n""" - module_def = """ -:mod:`%s` -=======%s - -.. automodule:: %s - :members: %s -""" - class_def = """ - -.. autoclass:: %s - :members: %s - -""" - - def __init__(self, project_title, code_dir): - self.title = project_title - self.code_dir = osp.abspath(code_dir) - - def generate(self, dest_file, exclude_dirs=STD_BLACKLIST): - """make the module file""" - self.fn = open(dest_file, 'w') - num = len(self.title) + 6 - title = "=" * num + "\n %s API\n" % self.title + "=" * num - self.fn.write(self.file_header % title) - self.gen_modules(exclude_dirs=exclude_dirs) - self.fn.close() - - def gen_modules(self, exclude_dirs): - """generate all modules""" - for module in self.find_modules(exclude_dirs): - modname = module.__name__ - classes = [] - modmembers = [] - for objname, obj in module_members(module): - if inspect.isclass(obj): - classmembers = class_members(obj) - classes.append( (objname, classmembers) ) - else: - modmembers.append(objname) - self.fn.write(self.module_def % (modname, '=' * len(modname), - modname, - ', '.join(modmembers))) - for klass, members in classes: - self.fn.write(self.class_def % (klass, ', '.join(members))) - - def find_modules(self, exclude_dirs): - basepath = osp.dirname(self.code_dir) - basedir = osp.basename(basepath) + osp.sep - if basedir not in sys.path: - sys.path.insert(1, basedir) - for filepath in globfind(self.code_dir, '*.py', exclude_dirs): - if osp.basename(filepath) in ('setup.py', '__pkginfo__.py'): - continue - try: - module = load_module_from_file(filepath) - except: # module might be broken or magic - dotted_path = modpath_from_file(filepath) - module = type('.'.join(dotted_path), (), {}) # mock it - yield module - - -if __name__ == '__main__': - # example : - title, code_dir, outfile = sys.argv[1:] - generator = ModuleGenerator(title, code_dir) - # XXX modnames = ['logilab'] - generator.generate(outfile, ('test', 'tests', 'examples', - 'data', 'doc', '.hg', 'migration')) diff --git a/pylibs/logilab/common/table.py b/pylibs/logilab/common/table.py deleted file mode 100644 index 744bb785..00000000 --- a/pylibs/logilab/common/table.py +++ /dev/null @@ -1,923 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Table management module.""" -__docformat__ = "restructuredtext en" - - -class Table(object): - """Table defines a data table with column and row names. - inv: - len(self.data) <= len(self.row_names) - forall(self.data, lambda x: len(x) <= len(self.col_names)) - """ - - def __init__(self, default_value=0, col_names=None, row_names=None): - self.col_names = [] - self.row_names = [] - self.data = [] - self.default_value = default_value - if col_names: - self.create_columns(col_names) - if row_names: - self.create_rows(row_names) - - def _next_row_name(self): - return 'row%s' % (len(self.row_names)+1) - - def __iter__(self): - return iter(self.data) - - def __eq__(self, other): - if other is None: - return False - else: - return list(self) == list(other) - - def __ne__(self, other): - return not self == other - - def __len__(self): - return len(self.row_names) - - ## Rows / Columns creation ################################################# - def create_rows(self, row_names): - """Appends row_names to the list of existing rows - """ - self.row_names.extend(row_names) - for row_name in row_names: - self.data.append([self.default_value]*len(self.col_names)) - - def create_columns(self, col_names): - """Appends col_names to the list of existing columns - """ - for col_name in col_names: - self.create_column(col_name) - - def create_row(self, row_name=None): - """Creates a rowname to the row_names list - """ - row_name = row_name or self._next_row_name() - self.row_names.append(row_name) - self.data.append([self.default_value]*len(self.col_names)) - - - def create_column(self, col_name): - """Creates a colname to the col_names list - """ - self.col_names.append(col_name) - for row in self.data: - row.append(self.default_value) - - ## Sort by column ########################################################## - def sort_by_column_id(self, col_id, method = 'asc'): - """Sorts the table (in-place) according to data stored in col_id - """ - try: - col_index = self.col_names.index(col_id) - self.sort_by_column_index(col_index, method) - except ValueError: - raise KeyError("Col (%s) not found in table" % (col_id)) - - - def sort_by_column_index(self, col_index, method = 'asc'): - """Sorts the table 'in-place' according to data stored in col_index - - method should be in ('asc', 'desc') - """ - sort_list = sorted([(row[col_index], row, row_name) - for row, row_name in zip(self.data, self.row_names)]) - # Sorting sort_list will sort according to col_index - # If we want reverse sort, then reverse list - if method.lower() == 'desc': - sort_list.reverse() - - # Rebuild data / row names - self.data = [] - self.row_names = [] - for val, row, row_name in sort_list: - self.data.append(row) - self.row_names.append(row_name) - - def groupby(self, colname, *others): - """builds indexes of data - :returns: nested dictionaries pointing to actual rows - """ - groups = {} - colnames = (colname,) + others - col_indexes = [self.col_names.index(col_id) for col_id in colnames] - for row in self.data: - ptr = groups - for col_index in col_indexes[:-1]: - ptr = ptr.setdefault(row[col_index], {}) - ptr = ptr.setdefault(row[col_indexes[-1]], - Table(default_value=self.default_value, - col_names=self.col_names)) - ptr.append_row(tuple(row)) - return groups - - def select(self, colname, value): - grouped = self.groupby(colname) - try: - return grouped[value] - except KeyError: - return [] - - def remove(self, colname, value): - col_index = self.col_names.index(colname) - for row in self.data[:]: - if row[col_index] == value: - self.data.remove(row) - - - ## The 'setter' part ####################################################### - def set_cell(self, row_index, col_index, data): - """sets value of cell 'row_indew', 'col_index' to data - """ - self.data[row_index][col_index] = data - - - def set_cell_by_ids(self, row_id, col_id, data): - """sets value of cell mapped by row_id and col_id to data - Raises a KeyError if row_id or col_id are not found in the table - """ - try: - row_index = self.row_names.index(row_id) - except ValueError: - raise KeyError("Row (%s) not found in table" % (row_id)) - else: - try: - col_index = self.col_names.index(col_id) - self.data[row_index][col_index] = data - except ValueError: - raise KeyError("Column (%s) not found in table" % (col_id)) - - - def set_row(self, row_index, row_data): - """sets the 'row_index' row - pre: - type(row_data) == types.ListType - len(row_data) == len(self.col_names) - """ - self.data[row_index] = row_data - - - def set_row_by_id(self, row_id, row_data): - """sets the 'row_id' column - pre: - type(row_data) == types.ListType - len(row_data) == len(self.row_names) - Raises a KeyError if row_id is not found - """ - try: - row_index = self.row_names.index(row_id) - self.set_row(row_index, row_data) - except ValueError: - raise KeyError('Row (%s) not found in table' % (row_id)) - - - def append_row(self, row_data, row_name=None): - """Appends a row to the table - pre: - type(row_data) == types.ListType - len(row_data) == len(self.col_names) - """ - row_name = row_name or self._next_row_name() - self.row_names.append(row_name) - self.data.append(row_data) - return len(self.data) - 1 - - def insert_row(self, index, row_data, row_name=None): - """Appends row_data before 'index' in the table. To make 'insert' - behave like 'list.insert', inserting in an out of range index will - insert row_data to the end of the list - pre: - type(row_data) == types.ListType - len(row_data) == len(self.col_names) - """ - row_name = row_name or self._next_row_name() - self.row_names.insert(index, row_name) - self.data.insert(index, row_data) - - - def delete_row(self, index): - """Deletes the 'index' row in the table, and returns it. - Raises an IndexError if index is out of range - """ - self.row_names.pop(index) - return self.data.pop(index) - - - def delete_row_by_id(self, row_id): - """Deletes the 'row_id' row in the table. - Raises a KeyError if row_id was not found. - """ - try: - row_index = self.row_names.index(row_id) - self.delete_row(row_index) - except ValueError: - raise KeyError('Row (%s) not found in table' % (row_id)) - - - def set_column(self, col_index, col_data): - """sets the 'col_index' column - pre: - type(col_data) == types.ListType - len(col_data) == len(self.row_names) - """ - - for row_index, cell_data in enumerate(col_data): - self.data[row_index][col_index] = cell_data - - - def set_column_by_id(self, col_id, col_data): - """sets the 'col_id' column - pre: - type(col_data) == types.ListType - len(col_data) == len(self.col_names) - Raises a KeyError if col_id is not found - """ - try: - col_index = self.col_names.index(col_id) - self.set_column(col_index, col_data) - except ValueError: - raise KeyError('Column (%s) not found in table' % (col_id)) - - - def append_column(self, col_data, col_name): - """Appends the 'col_index' column - pre: - type(col_data) == types.ListType - len(col_data) == len(self.row_names) - """ - self.col_names.append(col_name) - for row_index, cell_data in enumerate(col_data): - self.data[row_index].append(cell_data) - - - def insert_column(self, index, col_data, col_name): - """Appends col_data before 'index' in the table. To make 'insert' - behave like 'list.insert', inserting in an out of range index will - insert col_data to the end of the list - pre: - type(col_data) == types.ListType - len(col_data) == len(self.row_names) - """ - self.col_names.insert(index, col_name) - for row_index, cell_data in enumerate(col_data): - self.data[row_index].insert(index, cell_data) - - - def delete_column(self, index): - """Deletes the 'index' column in the table, and returns it. - Raises an IndexError if index is out of range - """ - self.col_names.pop(index) - return [row.pop(index) for row in self.data] - - - def delete_column_by_id(self, col_id): - """Deletes the 'col_id' col in the table. - Raises a KeyError if col_id was not found. - """ - try: - col_index = self.col_names.index(col_id) - self.delete_column(col_index) - except ValueError: - raise KeyError('Column (%s) not found in table' % (col_id)) - - - ## The 'getter' part ####################################################### - - def get_shape(self): - """Returns a tuple which represents the table's shape - """ - return len(self.row_names), len(self.col_names) - shape = property(get_shape) - - def __getitem__(self, indices): - """provided for convenience""" - rows, multirows = None, False - cols, multicols = None, False - if isinstance(indices, tuple): - rows = indices[0] - if len(indices) > 1: - cols = indices[1] - else: - rows = indices - # define row slice - if isinstance(rows, str): - try: - rows = self.row_names.index(rows) - except ValueError: - raise KeyError("Row (%s) not found in table" % (rows)) - if isinstance(rows, int): - rows = slice(rows, rows+1) - multirows = False - else: - rows = slice(None) - multirows = True - # define col slice - if isinstance(cols, str): - try: - cols = self.col_names.index(cols) - except ValueError: - raise KeyError("Column (%s) not found in table" % (cols)) - if isinstance(cols, int): - cols = slice(cols, cols+1) - multicols = False - else: - cols = slice(None) - multicols = True - # get sub-table - tab = Table() - tab.default_value = self.default_value - tab.create_rows(self.row_names[rows]) - tab.create_columns(self.col_names[cols]) - for idx, row in enumerate(self.data[rows]): - tab.set_row(idx, row[cols]) - if multirows : - if multicols: - return tab - else: - return [item[0] for item in tab.data] - else: - if multicols: - return tab.data[0] - else: - return tab.data[0][0] - - def get_cell_by_ids(self, row_id, col_id): - """Returns the element at [row_id][col_id] - """ - try: - row_index = self.row_names.index(row_id) - except ValueError: - raise KeyError("Row (%s) not found in table" % (row_id)) - else: - try: - col_index = self.col_names.index(col_id) - except ValueError: - raise KeyError("Column (%s) not found in table" % (col_id)) - return self.data[row_index][col_index] - - def get_row_by_id(self, row_id): - """Returns the 'row_id' row - """ - try: - row_index = self.row_names.index(row_id) - except ValueError: - raise KeyError("Row (%s) not found in table" % (row_id)) - return self.data[row_index] - - def get_column_by_id(self, col_id, distinct=False): - """Returns the 'col_id' col - """ - try: - col_index = self.col_names.index(col_id) - except ValueError: - raise KeyError("Column (%s) not found in table" % (col_id)) - return self.get_column(col_index, distinct) - - def get_columns(self): - """Returns all the columns in the table - """ - return [self[:, index] for index in range(len(self.col_names))] - - def get_column(self, col_index, distinct=False): - """get a column by index""" - col = [row[col_index] for row in self.data] - if distinct: - col = list(set(col)) - return col - - def apply_stylesheet(self, stylesheet): - """Applies the stylesheet to this table - """ - for instruction in stylesheet.instructions: - eval(instruction) - - - def transpose(self): - """Keeps the self object intact, and returns the transposed (rotated) - table. - """ - transposed = Table() - transposed.create_rows(self.col_names) - transposed.create_columns(self.row_names) - for col_index, column in enumerate(self.get_columns()): - transposed.set_row(col_index, column) - return transposed - - - def pprint(self): - """returns a string representing the table in a pretty - printed 'text' format. - """ - # The maximum row name (to know the start_index of the first col) - max_row_name = 0 - for row_name in self.row_names: - if len(row_name) > max_row_name: - max_row_name = len(row_name) - col_start = max_row_name + 5 - - lines = [] - # Build the 'first' line <=> the col_names one - # The first cell <=> an empty one - col_names_line = [' '*col_start] - for col_name in self.col_names: - col_names_line.append(col_name.encode('iso-8859-1') + ' '*5) - lines.append('|' + '|'.join(col_names_line) + '|') - max_line_length = len(lines[0]) - - # Build the table - for row_index, row in enumerate(self.data): - line = [] - # First, build the row_name's cell - row_name = self.row_names[row_index].encode('iso-8859-1') - line.append(row_name + ' '*(col_start-len(row_name))) - - # Then, build all the table's cell for this line. - for col_index, cell in enumerate(row): - col_name_length = len(self.col_names[col_index]) + 5 - data = str(cell) - line.append(data + ' '*(col_name_length - len(data))) - lines.append('|' + '|'.join(line) + '|') - if len(lines[-1]) > max_line_length: - max_line_length = len(lines[-1]) - - # Wrap the table with '-' to make a frame - lines.insert(0, '-'*max_line_length) - lines.append('-'*max_line_length) - return '\n'.join(lines) - - - def __repr__(self): - return repr(self.data) - - def as_text(self): - data = [] - # We must convert cells into strings before joining them - for row in self.data: - data.append([str(cell) for cell in row]) - lines = ['\t'.join(row) for row in data] - return '\n'.join(lines) - - - -class TableStyle: - """Defines a table's style - """ - - def __init__(self, table): - - self._table = table - self.size = dict([(col_name, '1*') for col_name in table.col_names]) - # __row_column__ is a special key to define the first column which - # actually has no name (<=> left most column <=> row names column) - self.size['__row_column__'] = '1*' - self.alignment = dict([(col_name, 'right') - for col_name in table.col_names]) - self.alignment['__row_column__'] = 'right' - - # We shouldn't have to create an entry for - # the 1st col (the row_column one) - self.units = dict([(col_name, '') for col_name in table.col_names]) - self.units['__row_column__'] = '' - - # XXX FIXME : params order should be reversed for all set() methods - def set_size(self, value, col_id): - """sets the size of the specified col_id to value - """ - self.size[col_id] = value - - def set_size_by_index(self, value, col_index): - """Allows to set the size according to the column index rather than - using the column's id. - BE CAREFUL : the '0' column is the '__row_column__' one ! - """ - if col_index == 0: - col_id = '__row_column__' - else: - col_id = self._table.col_names[col_index-1] - - self.size[col_id] = value - - - def set_alignment(self, value, col_id): - """sets the alignment of the specified col_id to value - """ - self.alignment[col_id] = value - - - def set_alignment_by_index(self, value, col_index): - """Allows to set the alignment according to the column index rather than - using the column's id. - BE CAREFUL : the '0' column is the '__row_column__' one ! - """ - if col_index == 0: - col_id = '__row_column__' - else: - col_id = self._table.col_names[col_index-1] - - self.alignment[col_id] = value - - - def set_unit(self, value, col_id): - """sets the unit of the specified col_id to value - """ - self.units[col_id] = value - - - def set_unit_by_index(self, value, col_index): - """Allows to set the unit according to the column index rather than - using the column's id. - BE CAREFUL : the '0' column is the '__row_column__' one ! - (Note that in the 'unit' case, you shouldn't have to set a unit - for the 1st column (the __row__column__ one)) - """ - if col_index == 0: - col_id = '__row_column__' - else: - col_id = self._table.col_names[col_index-1] - - self.units[col_id] = value - - - def get_size(self, col_id): - """Returns the size of the specified col_id - """ - return self.size[col_id] - - - def get_size_by_index(self, col_index): - """Allows to get the size according to the column index rather than - using the column's id. - BE CAREFUL : the '0' column is the '__row_column__' one ! - """ - if col_index == 0: - col_id = '__row_column__' - else: - col_id = self._table.col_names[col_index-1] - - return self.size[col_id] - - - def get_alignment(self, col_id): - """Returns the alignment of the specified col_id - """ - return self.alignment[col_id] - - - def get_alignment_by_index(self, col_index): - """Allors to get the alignment according to the column index rather than - using the column's id. - BE CAREFUL : the '0' column is the '__row_column__' one ! - """ - if col_index == 0: - col_id = '__row_column__' - else: - col_id = self._table.col_names[col_index-1] - - return self.alignment[col_id] - - - def get_unit(self, col_id): - """Returns the unit of the specified col_id - """ - return self.units[col_id] - - - def get_unit_by_index(self, col_index): - """Allors to get the unit according to the column index rather than - using the column's id. - BE CAREFUL : the '0' column is the '__row_column__' one ! - """ - if col_index == 0: - col_id = '__row_column__' - else: - col_id = self._table.col_names[col_index-1] - - return self.units[col_id] - - -import re -CELL_PROG = re.compile("([0-9]+)_([0-9]+)") - -class TableStyleSheet: - """A simple Table stylesheet - Rules are expressions where cells are defined by the row_index - and col_index separated by an underscore ('_'). - For example, suppose you want to say that the (2,5) cell must be - the sum of its two preceding cells in the row, you would create - the following rule : - 2_5 = 2_3 + 2_4 - You can also use all the math.* operations you want. For example: - 2_5 = sqrt(2_3**2 + 2_4**2) - """ - - def __init__(self, rules = None): - rules = rules or [] - self.rules = [] - self.instructions = [] - for rule in rules: - self.add_rule(rule) - - - def add_rule(self, rule): - """Adds a rule to the stylesheet rules - """ - try: - source_code = ['from math import *'] - source_code.append(CELL_PROG.sub(r'self.data[\1][\2]', rule)) - self.instructions.append(compile('\n'.join(source_code), - 'table.py', 'exec')) - self.rules.append(rule) - except SyntaxError: - print "Bad Stylesheet Rule : %s [skipped]"%rule - - - def add_rowsum_rule(self, dest_cell, row_index, start_col, end_col): - """Creates and adds a rule to sum over the row at row_index from - start_col to end_col. - dest_cell is a tuple of two elements (x,y) of the destination cell - No check is done for indexes ranges. - pre: - start_col >= 0 - end_col > start_col - """ - cell_list = ['%d_%d'%(row_index, index) for index in range(start_col, - end_col + 1)] - rule = '%d_%d=' % dest_cell + '+'.join(cell_list) - self.add_rule(rule) - - - def add_rowavg_rule(self, dest_cell, row_index, start_col, end_col): - """Creates and adds a rule to make the row average (from start_col - to end_col) - dest_cell is a tuple of two elements (x,y) of the destination cell - No check is done for indexes ranges. - pre: - start_col >= 0 - end_col > start_col - """ - cell_list = ['%d_%d'%(row_index, index) for index in range(start_col, - end_col + 1)] - num = (end_col - start_col + 1) - rule = '%d_%d=' % dest_cell + '('+'+'.join(cell_list)+')/%f'%num - self.add_rule(rule) - - - def add_colsum_rule(self, dest_cell, col_index, start_row, end_row): - """Creates and adds a rule to sum over the col at col_index from - start_row to end_row. - dest_cell is a tuple of two elements (x,y) of the destination cell - No check is done for indexes ranges. - pre: - start_row >= 0 - end_row > start_row - """ - cell_list = ['%d_%d'%(index, col_index) for index in range(start_row, - end_row + 1)] - rule = '%d_%d=' % dest_cell + '+'.join(cell_list) - self.add_rule(rule) - - - def add_colavg_rule(self, dest_cell, col_index, start_row, end_row): - """Creates and adds a rule to make the col average (from start_row - to end_row) - dest_cell is a tuple of two elements (x,y) of the destination cell - No check is done for indexes ranges. - pre: - start_row >= 0 - end_row > start_row - """ - cell_list = ['%d_%d'%(index, col_index) for index in range(start_row, - end_row + 1)] - num = (end_row - start_row + 1) - rule = '%d_%d=' % dest_cell + '('+'+'.join(cell_list)+')/%f'%num - self.add_rule(rule) - - - -class TableCellRenderer: - """Defines a simple text renderer - """ - - def __init__(self, **properties): - """keywords should be properties with an associated boolean as value. - For example : - renderer = TableCellRenderer(units = True, alignment = False) - An unspecified property will have a 'False' value by default. - Possible properties are : - alignment, unit - """ - self.properties = properties - - - def render_cell(self, cell_coord, table, table_style): - """Renders the cell at 'cell_coord' in the table, using table_style - """ - row_index, col_index = cell_coord - cell_value = table.data[row_index][col_index] - final_content = self._make_cell_content(cell_value, - table_style, col_index +1) - return self._render_cell_content(final_content, - table_style, col_index + 1) - - - def render_row_cell(self, row_name, table, table_style): - """Renders the cell for 'row_id' row - """ - cell_value = row_name.encode('iso-8859-1') - return self._render_cell_content(cell_value, table_style, 0) - - - def render_col_cell(self, col_name, table, table_style): - """Renders the cell for 'col_id' row - """ - cell_value = col_name.encode('iso-8859-1') - col_index = table.col_names.index(col_name) - return self._render_cell_content(cell_value, table_style, col_index +1) - - - - def _render_cell_content(self, content, table_style, col_index): - """Makes the appropriate rendering for this cell content. - Rendering properties will be searched using the - *table_style.get_xxx_by_index(col_index)' methods - - **This method should be overridden in the derived renderer classes.** - """ - return content - - - def _make_cell_content(self, cell_content, table_style, col_index): - """Makes the cell content (adds decoration data, like units for - example) - """ - final_content = cell_content - if 'skip_zero' in self.properties: - replacement_char = self.properties['skip_zero'] - else: - replacement_char = 0 - if replacement_char and final_content == 0: - return replacement_char - - try: - units_on = self.properties['units'] - if units_on: - final_content = self._add_unit( - cell_content, table_style, col_index) - except KeyError: - pass - - return final_content - - - def _add_unit(self, cell_content, table_style, col_index): - """Adds unit to the cell_content if needed - """ - unit = table_style.get_unit_by_index(col_index) - return str(cell_content) + " " + unit - - - -class DocbookRenderer(TableCellRenderer): - """Defines how to render a cell for a docboook table - """ - - def define_col_header(self, col_index, table_style): - """Computes the colspec element according to the style - """ - size = table_style.get_size_by_index(col_index) - return '\n' % \ - (col_index, size) - - - def _render_cell_content(self, cell_content, table_style, col_index): - """Makes the appropriate rendering for this cell content. - Rendering properties will be searched using the - table_style.get_xxx_by_index(col_index)' methods. - """ - try: - align_on = self.properties['alignment'] - alignment = table_style.get_alignment_by_index(col_index) - if align_on: - return "%s\n" % \ - (alignment, cell_content) - except KeyError: - # KeyError <=> Default alignment - return "%s\n" % cell_content - - -class TableWriter: - """A class to write tables - """ - - def __init__(self, stream, table, style, **properties): - self._stream = stream - self.style = style or TableStyle(table) - self._table = table - self.properties = properties - self.renderer = None - - - def set_style(self, style): - """sets the table's associated style - """ - self.style = style - - - def set_renderer(self, renderer): - """sets the way to render cell - """ - self.renderer = renderer - - - def update_properties(self, **properties): - """Updates writer's properties (for cell rendering) - """ - self.properties.update(properties) - - - def write_table(self, title = ""): - """Writes the table - """ - raise NotImplementedError("write_table must be implemented !") - - - -class DocbookTableWriter(TableWriter): - """Defines an implementation of TableWriter to write a table in Docbook - """ - - def _write_headers(self): - """Writes col headers - """ - # Define col_headers (colstpec elements) - for col_index in range(len(self._table.col_names)+1): - self._stream.write(self.renderer.define_col_header(col_index, - self.style)) - - self._stream.write("\n\n") - # XXX FIXME : write an empty entry <=> the first (__row_column) column - self._stream.write('\n') - for col_name in self._table.col_names: - self._stream.write(self.renderer.render_col_cell( - col_name, self._table, - self.style)) - - self._stream.write("\n\n") - - - def _write_body(self): - """Writes the table body - """ - self._stream.write('\n') - - for row_index, row in enumerate(self._table.data): - self._stream.write('\n') - row_name = self._table.row_names[row_index] - # Write the first entry (row_name) - self._stream.write(self.renderer.render_row_cell(row_name, - self._table, - self.style)) - - for col_index, cell in enumerate(row): - self._stream.write(self.renderer.render_cell( - (row_index, col_index), - self._table, self.style)) - - self._stream.write('\n') - - self._stream.write('\n') - - - def write_table(self, title = ""): - """Writes the table - """ - self._stream.write('\nCodestin Search App\n'%(title)) - self._stream.write( - '\n'% - (len(self._table.col_names)+1)) - self._write_headers() - self._write_body() - - self._stream.write('\n
\n') - - diff --git a/pylibs/logilab/common/tasksqueue.py b/pylibs/logilab/common/tasksqueue.py deleted file mode 100644 index e95a77e1..00000000 --- a/pylibs/logilab/common/tasksqueue.py +++ /dev/null @@ -1,98 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Prioritized tasks queue""" - -__docformat__ = "restructuredtext en" - -from bisect import insort_left -from Queue import Queue - -LOW = 0 -MEDIUM = 10 -HIGH = 100 - -PRIORITY = { - 'LOW': LOW, - 'MEDIUM': MEDIUM, - 'HIGH': HIGH, - } -REVERSE_PRIORITY = dict((values, key) for key, values in PRIORITY.iteritems()) - - - -class PrioritizedTasksQueue(Queue): - - def _init(self, maxsize): - """Initialize the queue representation""" - self.maxsize = maxsize - # ordered list of task, from the lowest to the highest priority - self.queue = [] - - def _put(self, item): - """Put a new item in the queue""" - for i, task in enumerate(self.queue): - # equivalent task - if task == item: - # if new task has a higher priority, remove the one already - # queued so the new priority will be considered - if task < item: - item.merge(task) - del self.queue[i] - break - # else keep it so current order is kept - task.merge(item) - return - insort_left(self.queue, item) - - def _get(self): - """Get an item from the queue""" - return self.queue.pop() - - def __iter__(self): - return iter(self.queue) - - def remove(self, tid): - """remove a specific task from the queue""" - # XXX acquire lock - for i, task in enumerate(self): - if task.id == tid: - self.queue.pop(i) - return - raise ValueError('not task of id %s in queue' % tid) - -class Task(object): - def __init__(self, tid, priority=LOW): - # task id - self.id = tid - # task priority - self.priority = priority - - def __repr__(self): - return '' % (self.id, id(self)) - - def __cmp__(self, other): - return cmp(self.priority, other.priority) - - def __lt__(self, other): - return self.priority < other.priority - - def __eq__(self, other): - return self.id == other.id - - def merge(self, other): - pass diff --git a/pylibs/logilab/common/testlib.py b/pylibs/logilab/common/testlib.py deleted file mode 100644 index a35ad98c..00000000 --- a/pylibs/logilab/common/testlib.py +++ /dev/null @@ -1,1389 +0,0 @@ -# -*- coding: utf-8 -*- -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Run tests. - -This will find all modules whose name match a given prefix in the test -directory, and run them. Various command line options provide -additional facilities. - -Command line options: - - -v verbose -- run tests in verbose mode with output to stdout - -q quiet -- don't print anything except if a test fails - -t testdir -- directory where the tests will be found - -x exclude -- add a test to exclude - -p profile -- profiled execution - -d dbc -- enable design-by-contract - -m match -- only run test matching the tag pattern which follow - -If no non-option arguments are present, prefixes used are 'test', -'regrtest', 'smoketest' and 'unittest'. - -""" -__docformat__ = "restructuredtext en" -# modified copy of some functions from test/regrtest.py from PyXml -# disable camel case warning -# pylint: disable=C0103 - -import sys -import os, os.path as osp -import re -import traceback -import inspect -import difflib -import tempfile -import math -import warnings -from shutil import rmtree -from operator import itemgetter -from ConfigParser import ConfigParser -from logilab.common.deprecation import deprecated -from itertools import dropwhile - -import unittest as unittest_legacy -if not getattr(unittest_legacy, "__package__", None): - try: - import unittest2 as unittest - from unittest2 import SkipTest - except ImportError: - raise ImportError("You have to install python-unittest2 to use %s" % __name__) -else: - import unittest - from unittest import SkipTest - -try: - from functools import wraps -except ImportError: - def wraps(wrapped): - def proxy(callable): - callable.__name__ = wrapped.__name__ - return callable - return proxy -try: - from test import test_support -except ImportError: - # not always available - class TestSupport: - def unload(self, test): - pass - test_support = TestSupport() - -# pylint: disable=W0622 -from logilab.common.compat import any, InheritableSet, callable -# pylint: enable=W0622 -from logilab.common.debugger import Debugger, colorize_source -from logilab.common.decorators import cached, classproperty -from logilab.common import textutils - - -__all__ = ['main', 'unittest_main', 'find_tests', 'run_test', 'spawn'] - -DEFAULT_PREFIXES = ('test', 'regrtest', 'smoketest', 'unittest', - 'func', 'validation') - - -if sys.version_info >= (2, 6): - # FIXME : this does not work as expected / breaks tests on testlib - # however testlib does not work on py3k for many reasons ... - from inspect import CO_GENERATOR -else: - from compiler.consts import CO_GENERATOR - -if sys.version_info >= (3, 0): - def is_generator(function): - flags = function.__code__.co_flags - return flags & CO_GENERATOR - -else: - def is_generator(function): - flags = function.func_code.co_flags - return flags & CO_GENERATOR - -# used by unittest to count the number of relevant levels in the traceback -__unittest = 1 - - -def with_tempdir(callable): - """A decorator ensuring no temporary file left when the function return - Work only for temporary file create with the tempfile module""" - @wraps(callable) - def proxy(*args, **kargs): - - old_tmpdir = tempfile.gettempdir() - new_tmpdir = tempfile.mkdtemp(prefix="temp-lgc-") - tempfile.tempdir = new_tmpdir - try: - return callable(*args, **kargs) - finally: - try: - rmtree(new_tmpdir, ignore_errors=True) - finally: - tempfile.tempdir = old_tmpdir - return proxy - -def in_tempdir(callable): - """A decorator moving the enclosed function inside the tempfile.tempfdir - """ - @wraps(callable) - def proxy(*args, **kargs): - - old_cwd = os.getcwd() - os.chdir(tempfile.tempdir) - try: - return callable(*args, **kargs) - finally: - os.chdir(old_cwd) - return proxy - -def within_tempdir(callable): - """A decorator run the enclosed function inside a tmpdir removed after execution - """ - proxy = with_tempdir(in_tempdir(callable)) - proxy.__name__ = callable.__name__ - return proxy - -def find_tests(testdir, - prefixes=DEFAULT_PREFIXES, suffix=".py", - excludes=(), - remove_suffix=True): - """ - Return a list of all applicable test modules. - """ - tests = [] - for name in os.listdir(testdir): - if not suffix or name.endswith(suffix): - for prefix in prefixes: - if name.startswith(prefix): - if remove_suffix and name.endswith(suffix): - name = name[:-len(suffix)] - if name not in excludes: - tests.append(name) - tests.sort() - return tests - - -## PostMortem Debug facilities ##### -def start_interactive_mode(result): - """starts an interactive shell so that the user can inspect errors - """ - debuggers = result.debuggers - descrs = result.error_descrs + result.fail_descrs - if len(debuggers) == 1: - # don't ask for test name if there's only one failure - debuggers[0].start() - else: - while True: - testindex = 0 - print "Choose a test to debug:" - # order debuggers in the same way than errors were printed - print "\n".join(['\t%s : %s' % (i, descr) for i, (_, descr) - in enumerate(descrs)]) - print "Type 'exit' (or ^D) to quit" - print - try: - todebug = raw_input('Enter a test name: ') - if todebug.strip().lower() == 'exit': - print - break - else: - try: - testindex = int(todebug) - debugger = debuggers[descrs[testindex][0]] - except (ValueError, IndexError): - print "ERROR: invalid test number %r" % (todebug, ) - else: - debugger.start() - except (EOFError, KeyboardInterrupt): - print - break - - -# test utils ################################################################## - -class SkipAwareTestResult(unittest._TextTestResult): - - def __init__(self, stream, descriptions, verbosity, - exitfirst=False, pdbmode=False, cvg=None, colorize=False): - super(SkipAwareTestResult, self).__init__(stream, - descriptions, verbosity) - self.skipped = [] - self.debuggers = [] - self.fail_descrs = [] - self.error_descrs = [] - self.exitfirst = exitfirst - self.pdbmode = pdbmode - self.cvg = cvg - self.colorize = colorize - self.pdbclass = Debugger - self.verbose = verbosity > 1 - - def descrs_for(self, flavour): - return getattr(self, '%s_descrs' % flavour.lower()) - - def _create_pdb(self, test_descr, flavour): - self.descrs_for(flavour).append( (len(self.debuggers), test_descr) ) - if self.pdbmode: - self.debuggers.append(self.pdbclass(sys.exc_info()[2])) - - def _iter_valid_frames(self, frames): - """only consider non-testlib frames when formatting traceback""" - lgc_testlib = osp.abspath(__file__) - std_testlib = osp.abspath(unittest.__file__) - invalid = lambda fi: osp.abspath(fi[1]) in (lgc_testlib, std_testlib) - for frameinfo in dropwhile(invalid, frames): - yield frameinfo - - def _exc_info_to_string(self, err, test): - """Converts a sys.exc_info()-style tuple of values into a string. - - This method is overridden here because we want to colorize - lines if --color is passed, and display local variables if - --verbose is passed - """ - exctype, exc, tb = err - output = ['Traceback (most recent call last)'] - frames = inspect.getinnerframes(tb) - colorize = self.colorize - frames = enumerate(self._iter_valid_frames(frames)) - for index, (frame, filename, lineno, funcname, ctx, ctxindex) in frames: - filename = osp.abspath(filename) - if ctx is None: # pyc files or C extensions for instance - source = '' - else: - source = ''.join(ctx) - if colorize: - filename = textutils.colorize_ansi(filename, 'magenta') - source = colorize_source(source) - output.append(' File "%s", line %s, in %s' % (filename, lineno, funcname)) - output.append(' %s' % source.strip()) - if self.verbose: - output.append('%r == %r' % (dir(frame), test.__module__)) - output.append('') - output.append(' ' + ' local variables '.center(66, '-')) - for varname, value in sorted(frame.f_locals.items()): - output.append(' %s: %r' % (varname, value)) - if varname == 'self': # special handy processing for self - for varname, value in sorted(vars(value).items()): - output.append(' self.%s: %r' % (varname, value)) - output.append(' ' + '-' * 66) - output.append('') - output.append(''.join(traceback.format_exception_only(exctype, exc))) - return '\n'.join(output) - - def addError(self, test, err): - """err -> (exc_type, exc, tcbk)""" - exc_type, exc, _ = err - if isinstance(exc, SkipTest): - assert exc_type == SkipTest - self.addSkip(test, exc) - else: - if self.exitfirst: - self.shouldStop = True - descr = self.getDescription(test) - super(SkipAwareTestResult, self).addError(test, err) - self._create_pdb(descr, 'error') - - def addFailure(self, test, err): - if self.exitfirst: - self.shouldStop = True - descr = self.getDescription(test) - super(SkipAwareTestResult, self).addFailure(test, err) - self._create_pdb(descr, 'fail') - - def addSkip(self, test, reason): - self.skipped.append((test, reason)) - if self.showAll: - self.stream.writeln("SKIPPED") - elif self.dots: - self.stream.write('S') - - def printErrors(self): - super(SkipAwareTestResult, self).printErrors() - self.printSkippedList() - - def printSkippedList(self): - # format (test, err) compatible with unittest2 - for test, err in self.skipped: - descr = self.getDescription(test) - self.stream.writeln(self.separator1) - self.stream.writeln("%s: %s" % ('SKIPPED', descr)) - self.stream.writeln("\t%s" % err) - - def printErrorList(self, flavour, errors): - for (_, descr), (test, err) in zip(self.descrs_for(flavour), errors): - self.stream.writeln(self.separator1) - self.stream.writeln("%s: %s" % (flavour, descr)) - self.stream.writeln(self.separator2) - self.stream.writeln(err) - self.stream.writeln('no stdout'.center(len(self.separator2))) - self.stream.writeln('no stderr'.center(len(self.separator2))) - -# Add deprecation warnings about new api used by module level fixtures in unittest2 -# http://www.voidspace.org.uk/python/articles/unittest2.shtml#setupmodule-and-teardownmodule -class _DebugResult(object): # simplify import statement among unittest flavors.. - "Used by the TestSuite to hold previous class when running in debug." - _previousTestClass = None - _moduleSetUpFailed = False - shouldStop = False - -from logilab.common.decorators import monkeypatch -@monkeypatch(unittest.TestSuite) -def _handleModuleTearDown(self, result): - previousModule = self._get_previous_module(result) - if previousModule is None: - return - if result._moduleSetUpFailed: - return - try: - module = sys.modules[previousModule] - except KeyError: - return - # add testlib specific deprecation warning and switch to new api - if hasattr(module, 'teardown_module'): - warnings.warn('Please rename teardown_module() to tearDownModule() instead.', - DeprecationWarning) - setattr(module, 'tearDownModule', module.teardown_module) - # end of monkey-patching - tearDownModule = getattr(module, 'tearDownModule', None) - if tearDownModule is not None: - try: - tearDownModule() - except Exception, e: - if isinstance(result, _DebugResult): - raise - errorName = 'tearDownModule (%s)' % previousModule - self._addClassOrModuleLevelException(result, e, errorName) - -@monkeypatch(unittest.TestSuite) -def _handleModuleFixture(self, test, result): - previousModule = self._get_previous_module(result) - currentModule = test.__class__.__module__ - if currentModule == previousModule: - return - self._handleModuleTearDown(result) - result._moduleSetUpFailed = False - try: - module = sys.modules[currentModule] - except KeyError: - return - # add testlib specific deprecation warning and switch to new api - if hasattr(module, 'setup_module'): - warnings.warn('Please rename setup_module() to setUpModule() instead.', - DeprecationWarning) - setattr(module, 'setUpModule', module.setup_module) - # end of monkey-patching - setUpModule = getattr(module, 'setUpModule', None) - if setUpModule is not None: - try: - setUpModule() - except Exception, e: - if isinstance(result, _DebugResult): - raise - result._moduleSetUpFailed = True - errorName = 'setUpModule (%s)' % currentModule - self._addClassOrModuleLevelException(result, e, errorName) - -# backward compatibility: TestSuite might be imported from lgc.testlib -TestSuite = unittest.TestSuite - -class keywords(dict): - """Keyword args (**kwargs) support for generative tests.""" - -class starargs(tuple): - """Variable arguments (*args) for generative tests.""" - def __new__(cls, *args): - return tuple.__new__(cls, args) - -unittest_main = unittest.main - - -class InnerTestSkipped(SkipTest): - """raised when a test is skipped""" - pass - -def parse_generative_args(params): - args = [] - varargs = () - kwargs = {} - flags = 0 # 2 <=> starargs, 4 <=> kwargs - for param in params: - if isinstance(param, starargs): - varargs = param - if flags: - raise TypeError('found starargs after keywords !') - flags |= 2 - args += list(varargs) - elif isinstance(param, keywords): - kwargs = param - if flags & 4: - raise TypeError('got multiple keywords parameters') - flags |= 4 - elif flags & 2 or flags & 4: - raise TypeError('found parameters after kwargs or args') - else: - args.append(param) - - return args, kwargs - - -class InnerTest(tuple): - def __new__(cls, name, *data): - instance = tuple.__new__(cls, data) - instance.name = name - return instance - -class Tags(InheritableSet): # 2.4 compat - """A set of tag able validate an expression""" - - def __init__(self, *tags, **kwargs): - self.inherit = kwargs.pop('inherit', True) - if kwargs: - raise TypeError("%s are an invalid keyword argument for this function" % kwargs.keys()) - - if len(tags) == 1 and not isinstance(tags[0], basestring): - tags = tags[0] - super(Tags, self).__init__(tags, **kwargs) - - def __getitem__(self, key): - return key in self - - def match(self, exp): - return eval(exp, {}, self) - - -# duplicate definition from unittest2 of the _deprecate decorator -def _deprecate(original_func): - def deprecated_func(*args, **kwargs): - warnings.warn( - ('Please use %s instead.' % original_func.__name__), - DeprecationWarning, 2) - return original_func(*args, **kwargs) - return deprecated_func - -class TestCase(unittest.TestCase): - """A unittest.TestCase extension with some additional methods.""" - maxDiff = None - pdbclass = Debugger - tags = Tags() - - def __init__(self, methodName='runTest'): - super(TestCase, self).__init__(methodName) - # internal API changed in python2.4 and needed by DocTestCase - if sys.version_info >= (2, 4): - self.__exc_info = sys.exc_info - self.__testMethodName = self._testMethodName - else: - # let's give easier access to _testMethodName to every subclasses - if hasattr(self, "__testMethodName"): - self._testMethodName = self.__testMethodName - self._current_test_descr = None - self._options_ = None - - @classproperty - @cached - def datadir(cls): # pylint: disable=E0213 - """helper attribute holding the standard test's data directory - - NOTE: this is a logilab's standard - """ - mod = __import__(cls.__module__) - return osp.join(osp.dirname(osp.abspath(mod.__file__)), 'data') - # cache it (use a class method to cache on class since TestCase is - # instantiated for each test run) - - @classmethod - def datapath(cls, *fname): - """joins the object's datadir and `fname`""" - return osp.join(cls.datadir, *fname) - - def set_description(self, descr): - """sets the current test's description. - This can be useful for generative tests because it allows to specify - a description per yield - """ - self._current_test_descr = descr - - # override default's unittest.py feature - def shortDescription(self): - """override default unittest shortDescription to handle correctly - generative tests - """ - if self._current_test_descr is not None: - return self._current_test_descr - return super(TestCase, self).shortDescription() - - def quiet_run(self, result, func, *args, **kwargs): - try: - func(*args, **kwargs) - except (KeyboardInterrupt, SystemExit): - raise - except: - result.addError(self, self.__exc_info()) - return False - return True - - def _get_test_method(self): - """return the test method""" - return getattr(self, self._testMethodName) - - def optval(self, option, default=None): - """return the option value or default if the option is not define""" - return getattr(self._options_, option, default) - - def __call__(self, result=None, runcondition=None, options=None): - """rewrite TestCase.__call__ to support generative tests - This is mostly a copy/paste from unittest.py (i.e same - variable names, same logic, except for the generative tests part) - """ - from logilab.common.pytest import FILE_RESTART - if result is None: - result = self.defaultTestResult() - result.pdbclass = self.pdbclass - self._options_ = options - # if result.cvg: - # result.cvg.start() - testMethod = self._get_test_method() - if runcondition and not runcondition(testMethod): - return # test is skipped - result.startTest(self) - try: - if not self.quiet_run(result, self.setUp): - return - generative = is_generator(testMethod.im_func) - # generative tests - if generative: - self._proceed_generative(result, testMethod, - runcondition) - else: - status = self._proceed(result, testMethod) - success = (status == 0) - if not self.quiet_run(result, self.tearDown): - return - if not generative and success: - if hasattr(options, "exitfirst") and options.exitfirst: - # add this test to restart file - try: - restartfile = open(FILE_RESTART, 'a') - try: - descr = '.'.join((self.__class__.__module__, - self.__class__.__name__, - self._testMethodName)) - restartfile.write(descr+os.linesep) - finally: - restartfile.close() - except Exception, ex: - print >> sys.__stderr__, "Error while saving \ -succeeded test into", osp.join(os.getcwd(), FILE_RESTART) - raise ex - result.addSuccess(self) - finally: - # if result.cvg: - # result.cvg.stop() - result.stopTest(self) - - def _proceed_generative(self, result, testfunc, runcondition=None): - # cancel startTest()'s increment - result.testsRun -= 1 - success = True - try: - for params in testfunc(): - if runcondition and not runcondition(testfunc, - skipgenerator=False): - if not (isinstance(params, InnerTest) - and runcondition(params)): - continue - if not isinstance(params, (tuple, list)): - params = (params, ) - func = params[0] - args, kwargs = parse_generative_args(params[1:]) - # increment test counter manually - result.testsRun += 1 - status = self._proceed(result, func, args, kwargs) - if status == 0: - result.addSuccess(self) - success = True - else: - success = False - # XXX Don't stop anymore if an error occured - #if status == 2: - # result.shouldStop = True - if result.shouldStop: # either on error or on exitfirst + error - break - except: - # if an error occurs between two yield - result.addError(self, self.__exc_info()) - success = False - return success - - def _proceed(self, result, testfunc, args=(), kwargs=None): - """proceed the actual test - returns 0 on success, 1 on failure, 2 on error - - Note: addSuccess can't be called here because we have to wait - for tearDown to be successfully executed to declare the test as - successful - """ - kwargs = kwargs or {} - try: - testfunc(*args, **kwargs) - except self.failureException: - result.addFailure(self, self.__exc_info()) - return 1 - except KeyboardInterrupt: - raise - except InnerTestSkipped, e: - result.addSkip(self, e) - return 1 - except SkipTest, e: - result.addSkip(self, e) - return 0 - except: - result.addError(self, self.__exc_info()) - return 2 - return 0 - - def defaultTestResult(self): - """return a new instance of the defaultTestResult""" - return SkipAwareTestResult() - - skip = _deprecate(unittest.TestCase.skipTest) - assertEquals = _deprecate(unittest.TestCase.assertEqual) - assertNotEquals = _deprecate(unittest.TestCase.assertNotEqual) - assertAlmostEquals = _deprecate(unittest.TestCase.assertAlmostEqual) - assertNotAlmostEquals = _deprecate(unittest.TestCase.assertNotAlmostEqual) - - def innerSkip(self, msg=None): - """mark a generative test as skipped for the reason""" - msg = msg or 'test was skipped' - raise InnerTestSkipped(msg) - - @deprecated('Please use assertDictEqual instead.') - def assertDictEquals(self, dict1, dict2, msg=None, context=None): - """compares two dicts - - If the two dict differ, the first difference is shown in the error - message - :param dict1: a Python Dictionary - :param dict2: a Python Dictionary - :param msg: custom message (String) in case of failure - """ - dict1 = dict(dict1) - msgs = [] - for key, value in dict2.items(): - try: - if dict1[key] != value: - msgs.append('%r != %r for key %r' % (dict1[key], value, - key)) - del dict1[key] - except KeyError: - msgs.append('missing %r key' % key) - if dict1: - msgs.append('dict2 is lacking %r' % dict1) - if msg: - self.failureException(msg) - elif msgs: - if context is not None: - base = '%s\n' % context - else: - base = '' - self.fail(base + '\n'.join(msgs)) - - @deprecated('Please use assertItemsEqual instead.') - def assertUnorderedIterableEquals(self, got, expected, msg=None): - """compares two iterable and shows difference between both - - :param got: the unordered Iterable that we found - :param expected: the expected unordered Iterable - :param msg: custom message (String) in case of failure - """ - got, expected = list(got), list(expected) - self.assertSetEqual(set(got), set(expected), msg) - if len(got) != len(expected): - if msg is None: - msg = ['Iterable have the same elements but not the same number', - '\t\ti\t'] - got_count = {} - expected_count = {} - for element in got: - got_count[element] = got_count.get(element, 0) + 1 - for element in expected: - expected_count[element] = expected_count.get(element, 0) + 1 - # we know that got_count.key() == expected_count.key() - # because of assertSetEqual - for element, count in got_count.iteritems(): - other_count = expected_count[element] - if other_count != count: - msg.append('\t%s\t%s\t%s' % (element, other_count, count)) - - self.fail(msg) - - assertUnorderedIterableEqual = assertUnorderedIterableEquals - assertUnordIterEquals = assertUnordIterEqual = assertUnorderedIterableEqual - - @deprecated('Please use assertSetEqual instead.') - def assertSetEquals(self,got,expected, msg=None): - """compares two sets and shows difference between both - - Don't use it for iterables other than sets. - - :param got: the Set that we found - :param expected: the second Set to be compared to the first one - :param msg: custom message (String) in case of failure - """ - - if not(isinstance(got, set) and isinstance(expected, set)): - warnings.warn("the assertSetEquals function if now intended for set only."\ - "use assertUnorderedIterableEquals instead.", - DeprecationWarning, 2) - return self.assertUnorderedIterableEquals(got, expected, msg) - - items={} - items['missing'] = expected - got - items['unexpected'] = got - expected - if any(items.itervalues()): - if msg is None: - msg = '\n'.join('%s:\n\t%s' % (key, "\n\t".join(str(value) for value in values)) - for key, values in items.iteritems() if values) - self.fail(msg) - - @deprecated('Please use assertListEqual instead.') - def assertListEquals(self, list_1, list_2, msg=None): - """compares two lists - - If the two list differ, the first difference is shown in the error - message - - :param list_1: a Python List - :param list_2: a second Python List - :param msg: custom message (String) in case of failure - """ - _l1 = list_1[:] - for i, value in enumerate(list_2): - try: - if _l1[0] != value: - from pprint import pprint - pprint(list_1) - pprint(list_2) - self.fail('%r != %r for index %d' % (_l1[0], value, i)) - del _l1[0] - except IndexError: - if msg is None: - msg = 'list_1 has only %d elements, not %s '\ - '(at least %r missing)'% (i, len(list_2), value) - self.fail(msg) - if _l1: - if msg is None: - msg = 'list_2 is lacking %r' % _l1 - self.fail(msg) - - @deprecated('Non-standard. Please use assertMultiLineEqual instead.') - def assertLinesEquals(self, string1, string2, msg=None, striplines=False): - """compare two strings and assert that the text lines of the strings - are equal. - - :param string1: a String - :param string2: a String - :param msg: custom message (String) in case of failure - :param striplines: Boolean to trigger line stripping before comparing - """ - lines1 = string1.splitlines() - lines2 = string2.splitlines() - if striplines: - lines1 = [l.strip() for l in lines1] - lines2 = [l.strip() for l in lines2] - self.assertListEqual(lines1, lines2, msg) - assertLineEqual = assertLinesEquals - - @deprecated('Non-standard: please copy test method to your TestCase class') - def assertXMLWellFormed(self, stream, msg=None, context=2): - """asserts the XML stream is well-formed (no DTD conformance check) - - :param context: number of context lines in standard message - (show all data if negative). - Only available with element tree - """ - try: - from xml.etree.ElementTree import parse - self._assertETXMLWellFormed(stream, parse, msg) - except ImportError: - from xml.sax import make_parser, SAXParseException - parser = make_parser() - try: - parser.parse(stream) - except SAXParseException, ex: - if msg is None: - stream.seek(0) - for _ in xrange(ex.getLineNumber()): - line = stream.readline() - pointer = ('' * (ex.getLineNumber() - 1)) + '^' - msg = 'XML stream not well formed: %s\n%s%s' % (ex, line, pointer) - self.fail(msg) - - @deprecated('Non-standard: please copy test method to your TestCase class') - def assertXMLStringWellFormed(self, xml_string, msg=None, context=2): - """asserts the XML string is well-formed (no DTD conformance check) - - :param context: number of context lines in standard message - (show all data if negative). - Only available with element tree - """ - try: - from xml.etree.ElementTree import fromstring - except ImportError: - from elementtree.ElementTree import fromstring - self._assertETXMLWellFormed(xml_string, fromstring, msg) - - def _assertETXMLWellFormed(self, data, parse, msg=None, context=2): - """internal function used by /assertXML(String)?WellFormed/ functions - - :param data: xml_data - :param parse: appropriate parser function for this data - :param msg: error message - :param context: number of context lines in standard message - (show all data if negative). - Only available with element tree - """ - from xml.parsers.expat import ExpatError - try: - from xml.etree.ElementTree import ParseError - except ImportError: - # compatibility for 1: - if len(tup)<=1: - self.fail( "tuple %s has no attributes (%s expected)"%(tup, - dict(element.attrib))) - self.assertDictEqual(element.attrib, tup[1]) - # check children - if len(element) or len(tup)>2: - if len(tup)<=2: - self.fail( "tuple %s has no children (%i expected)"%(tup, - len(element))) - if len(element) != len(tup[2]): - self.fail( "tuple %s has %i children%s (%i expected)"%(tup, - len(tup[2]), - ('', 's')[len(tup[2])>1], len(element))) - for index in xrange(len(tup[2])): - self.assertXMLEqualsTuple(element[index], tup[2][index]) - #check text - if element.text or len(tup)>3: - if len(tup)<=3: - self.fail( "tuple %s has no text value (%r expected)"%(tup, - element.text)) - self.assertTextEquals(element.text, tup[3]) - #check tail - if element.tail or len(tup)>4: - if len(tup)<=4: - self.fail( "tuple %s has no tail value (%r expected)"%(tup, - element.tail)) - self.assertTextEquals(element.tail, tup[4]) - - def _difftext(self, lines1, lines2, junk=None, msg_prefix='Texts differ'): - junk = junk or (' ', '\t') - # result is a generator - result = difflib.ndiff(lines1, lines2, charjunk=lambda x: x in junk) - read = [] - for line in result: - read.append(line) - # lines that don't start with a ' ' are diff ones - if not line.startswith(' '): - self.fail('\n'.join(['%s\n'%msg_prefix]+read + list(result))) - - @deprecated('Non-standard. Please use assertMultiLineEqual instead.') - def assertTextEquals(self, text1, text2, junk=None, - msg_prefix='Text differ', striplines=False): - """compare two multiline strings (using difflib and splitlines()) - - :param text1: a Python BaseString - :param text2: a second Python Basestring - :param junk: List of Caracters - :param msg_prefix: String (message prefix) - :param striplines: Boolean to trigger line stripping before comparing - """ - msg = [] - if not isinstance(text1, basestring): - msg.append('text1 is not a string (%s)'%(type(text1))) - if not isinstance(text2, basestring): - msg.append('text2 is not a string (%s)'%(type(text2))) - if msg: - self.fail('\n'.join(msg)) - lines1 = text1.strip().splitlines(True) - lines2 = text2.strip().splitlines(True) - if striplines: - lines1 = [line.strip() for line in lines1] - lines2 = [line.strip() for line in lines2] - self._difftext(lines1, lines2, junk, msg_prefix) - assertTextEqual = assertTextEquals - - @deprecated('Non-standard: please copy test method to your TestCase class') - def assertStreamEquals(self, stream1, stream2, junk=None, - msg_prefix='Stream differ'): - """compare two streams (using difflib and readlines())""" - # if stream2 is stream2, readlines() on stream1 will also read lines - # in stream2, so they'll appear different, although they're not - if stream1 is stream2: - return - # make sure we compare from the beginning of the stream - stream1.seek(0) - stream2.seek(0) - # compare - self._difftext(stream1.readlines(), stream2.readlines(), junk, - msg_prefix) - - assertStreamEqual = assertStreamEquals - - @deprecated('Non-standard: please copy test method to your TestCase class') - def assertFileEquals(self, fname1, fname2, junk=(' ', '\t')): - """compares two files using difflib""" - self.assertStreamEqual(open(fname1), open(fname2), junk, - msg_prefix='Files differs\n-:%s\n+:%s\n'%(fname1, fname2)) - - assertFileEqual = assertFileEquals - - @deprecated('Non-standard: please copy test method to your TestCase class') - def assertDirEquals(self, path_a, path_b): - """compares two files using difflib""" - assert osp.exists(path_a), "%s doesn't exists" % path_a - assert osp.exists(path_b), "%s doesn't exists" % path_b - - all_a = [ (ipath[len(path_a):].lstrip('/'), idirs, ifiles) - for ipath, idirs, ifiles in os.walk(path_a)] - all_a.sort(key=itemgetter(0)) - - all_b = [ (ipath[len(path_b):].lstrip('/'), idirs, ifiles) - for ipath, idirs, ifiles in os.walk(path_b)] - all_b.sort(key=itemgetter(0)) - - iter_a, iter_b = iter(all_a), iter(all_b) - partial_iter = True - ipath_a, idirs_a, ifiles_a = data_a = None, None, None - while True: - try: - ipath_a, idirs_a, ifiles_a = datas_a = iter_a.next() - partial_iter = False - ipath_b, idirs_b, ifiles_b = datas_b = iter_b.next() - partial_iter = True - - - self.assert_(ipath_a == ipath_b, - "unexpected %s in %s while looking %s from %s" % - (ipath_a, path_a, ipath_b, path_b)) - - - errors = {} - sdirs_a = set(idirs_a) - sdirs_b = set(idirs_b) - errors["unexpected directories"] = sdirs_a - sdirs_b - errors["missing directories"] = sdirs_b - sdirs_a - - sfiles_a = set(ifiles_a) - sfiles_b = set(ifiles_b) - errors["unexpected files"] = sfiles_a - sfiles_b - errors["missing files"] = sfiles_b - sfiles_a - - - msgs = [ "%s: %s"% (name, items) - for name, items in errors.iteritems() if items] - - if msgs: - msgs.insert(0, "%s and %s differ :" % ( - osp.join(path_a, ipath_a), - osp.join(path_b, ipath_b), - )) - self.fail("\n".join(msgs)) - - for files in (ifiles_a, ifiles_b): - files.sort() - - for index, path in enumerate(ifiles_a): - self.assertFileEquals(osp.join(path_a, ipath_a, path), - osp.join(path_b, ipath_b, ifiles_b[index])) - - except StopIteration: - break - - assertDirEqual = assertDirEquals - - def assertIsInstance(self, obj, klass, msg=None, strict=False): - """check if an object is an instance of a class - - :param obj: the Python Object to be checked - :param klass: the target class - :param msg: a String for a custom message - :param strict: if True, check that the class of is ; - else check with 'isinstance' - """ - if strict: - warnings.warn('[API] Non-standard. Strict parameter has vanished', - DeprecationWarning, stacklevel=2) - if msg is None: - if strict: - msg = '%r is not of class %s but of %s' - else: - msg = '%r is not an instance of %s but of %s' - msg = msg % (obj, klass, type(obj)) - if strict: - self.assert_(obj.__class__ is klass, msg) - else: - self.assert_(isinstance(obj, klass), msg) - - @deprecated('Please use assertIsNone instead.') - def assertNone(self, obj, msg=None): - """assert obj is None - - :param obj: Python Object to be tested - """ - if msg is None: - msg = "reference to %r when None expected"%(obj,) - self.assert_( obj is None, msg ) - - @deprecated('Please use assertIsNotNone instead.') - def assertNotNone(self, obj, msg=None): - """assert obj is not None""" - if msg is None: - msg = "unexpected reference to None" - self.assert_( obj is not None, msg ) - - @deprecated('Non-standard. Please use assertAlmostEqual instead.') - def assertFloatAlmostEquals(self, obj, other, prec=1e-5, - relative=False, msg=None): - """compares if two floats have a distance smaller than expected - precision. - - :param obj: a Float - :param other: another Float to be comparted to - :param prec: a Float describing the precision - :param relative: boolean switching to relative/absolute precision - :param msg: a String for a custom message - """ - if msg is None: - msg = "%r != %r" % (obj, other) - if relative: - prec = prec*math.fabs(obj) - self.assert_(math.fabs(obj - other) < prec, msg) - - def failUnlessRaises(self, excClass, callableObj=None, *args, **kwargs): - """override default failUnlessRaises method to return the raised - exception instance. - - Fail unless an exception of class excClass is thrown - by callableObj when invoked with arguments args and keyword - arguments kwargs. If a different type of exception is - thrown, it will not be caught, and the test case will be - deemed to have suffered an error, exactly as for an - unexpected exception. - - CAUTION! There are subtle differences between Logilab and unittest2 - - exc is not returned in standard version - - context capabilities in standard version - - try/except/else construction (minor) - - :param excClass: the Exception to be raised - :param callableObj: a callable Object which should raise - :param args: a List of arguments for - :param kwargs: a List of keyword arguments for - """ - # XXX cube vcslib : test_branches_from_app - if callableObj is None: - _assert = super(TestCase, self).assertRaises - return _assert(excClass, callableObj, *args, **kwargs) - try: - callableObj(*args, **kwargs) - except excClass, exc: - class ProxyException: - def __init__(self, obj): - self._obj = obj - def __getattr__(self, attr): - warn_msg = ("This exception was retrieved with the old testlib way " - "`exc = self.assertRaises(Exc, callable)`, please use " - "the context manager instead'") - warnings.warn(warn_msg, DeprecationWarning, 2) - return self._obj.__getattribute__(attr) - return ProxyException(exc) - else: - if hasattr(excClass, '__name__'): - excName = excClass.__name__ - else: - excName = str(excClass) - raise self.failureException("%s not raised" % excName) - - assertRaises = failUnlessRaises - - -import doctest - -class SkippedSuite(unittest.TestSuite): - def test(self): - """just there to trigger test execution""" - self.skipped_test('doctest module has no DocTestSuite class') - - -class DocTestFinder(doctest.DocTestFinder): - - def __init__(self, *args, **kwargs): - self.skipped = kwargs.pop('skipped', ()) - doctest.DocTestFinder.__init__(self, *args, **kwargs) - - def _get_test(self, obj, name, module, globs, source_lines): - """override default _get_test method to be able to skip tests - according to skipped attribute's value - - Note: Python (<=2.4) use a _name_filter which could be used for that - purpose but it's no longer available in 2.5 - Python 2.5 seems to have a [SKIP] flag - """ - if getattr(obj, '__name__', '') in self.skipped: - return None - return doctest.DocTestFinder._get_test(self, obj, name, module, - globs, source_lines) - - -class DocTest(TestCase): - """trigger module doctest - I don't know how to make unittest.main consider the DocTestSuite instance - without this hack - """ - skipped = () - def __call__(self, result=None, runcondition=None, options=None):\ - # pylint: disable=W0613 - try: - finder = DocTestFinder(skipped=self.skipped) - if sys.version_info >= (2, 4): - suite = doctest.DocTestSuite(self.module, test_finder=finder) - if sys.version_info >= (2, 5): - # XXX iirk - doctest.DocTestCase._TestCase__exc_info = sys.exc_info - else: - suite = doctest.DocTestSuite(self.module) - except AttributeError: - suite = SkippedSuite() - # doctest may gork the builtins dictionnary - # This happen to the "_" entry used by gettext - old_builtins = __builtins__.copy() - try: - return suite.run(result) - finally: - __builtins__.clear() - __builtins__.update(old_builtins) - run = __call__ - - def test(self): - """just there to trigger test execution""" - -MAILBOX = None - -class MockSMTP: - """fake smtplib.SMTP""" - - def __init__(self, host, port): - self.host = host - self.port = port - global MAILBOX - self.reveived = MAILBOX = [] - - def set_debuglevel(self, debuglevel): - """ignore debug level""" - - def sendmail(self, fromaddr, toaddres, body): - """push sent mail in the mailbox""" - self.reveived.append((fromaddr, toaddres, body)) - - def quit(self): - """ignore quit""" - - -class MockConfigParser(ConfigParser): - """fake ConfigParser.ConfigParser""" - - def __init__(self, options): - ConfigParser.__init__(self) - for section, pairs in options.iteritems(): - self.add_section(section) - for key, value in pairs.iteritems(): - self.set(section, key, value) - def write(self, _): - raise NotImplementedError() - - -class MockConnection: - """fake DB-API 2.0 connexion AND cursor (i.e. cursor() return self)""" - - def __init__(self, results): - self.received = [] - self.states = [] - self.results = results - - def cursor(self): - """Mock cursor method""" - return self - def execute(self, query, args=None): - """Mock execute method""" - self.received.append( (query, args) ) - def fetchone(self): - """Mock fetchone method""" - return self.results[0] - def fetchall(self): - """Mock fetchall method""" - return self.results - def commit(self): - """Mock commiy method""" - self.states.append( ('commit', len(self.received)) ) - def rollback(self): - """Mock rollback method""" - self.states.append( ('rollback', len(self.received)) ) - def close(self): - """Mock close method""" - pass - - -def mock_object(**params): - """creates an object using params to set attributes - >>> option = mock_object(verbose=False, index=range(5)) - >>> option.verbose - False - >>> option.index - [0, 1, 2, 3, 4] - """ - return type('Mock', (), params)() - - -def create_files(paths, chroot): - """Creates directories and files found in . - - :param paths: list of relative paths to files or directories - :param chroot: the root directory in which paths will be created - - >>> from os.path import isdir, isfile - >>> isdir('/tmp/a') - False - >>> create_files(['a/b/foo.py', 'a/b/c/', 'a/b/c/d/e.py'], '/tmp') - >>> isdir('/tmp/a') - True - >>> isdir('/tmp/a/b/c') - True - >>> isfile('/tmp/a/b/c/d/e.py') - True - >>> isfile('/tmp/a/b/foo.py') - True - """ - dirs, files = set(), set() - for path in paths: - path = osp.join(chroot, path) - filename = osp.basename(path) - # path is a directory path - if filename == '': - dirs.add(path) - # path is a filename path - else: - dirs.add(osp.dirname(path)) - files.add(path) - for dirpath in dirs: - if not osp.isdir(dirpath): - os.makedirs(dirpath) - for filepath in files: - open(filepath, 'w').close() - - -class AttrObject: # XXX cf mock_object - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - -def tag(*args, **kwargs): - """descriptor adding tag to a function""" - def desc(func): - assert not hasattr(func, 'tags') - func.tags = Tags(*args, **kwargs) - return func - return desc - -def require_version(version): - """ Compare version of python interpreter to the given one. Skip the test - if older. - """ - def check_require_version(f): - version_elements = version.split('.') - try: - compare = tuple([int(v) for v in version_elements]) - except ValueError: - raise ValueError('%s is not a correct version : should be X.Y[.Z].' % version) - current = sys.version_info[:3] - if current < compare: - def new_f(self, *args, **kwargs): - self.skipTest('Need at least %s version of python. Current version is %s.' % (version, '.'.join([str(element) for element in current]))) - new_f.__name__ = f.__name__ - return new_f - else: - return f - return check_require_version - -def require_module(module): - """ Check if the given module is loaded. Skip the test if not. - """ - def check_require_module(f): - try: - __import__(module) - return f - except ImportError: - def new_f(self, *args, **kwargs): - self.skipTest('%s can not be imported.' % module) - new_f.__name__ = f.__name__ - return new_f - return check_require_module - diff --git a/pylibs/logilab/common/umessage.py b/pylibs/logilab/common/umessage.py deleted file mode 100644 index 85d564c0..00000000 --- a/pylibs/logilab/common/umessage.py +++ /dev/null @@ -1,167 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Unicode email support (extends email from stdlib). - - - - -""" -__docformat__ = "restructuredtext en" - -import email -from encodings import search_function -import sys -if sys.version_info >= (2, 5): - from email.utils import parseaddr, parsedate - from email.header import decode_header -else: - from email.Utils import parseaddr, parsedate - from email.Header import decode_header - -from datetime import datetime - -try: - from mx.DateTime import DateTime -except ImportError: - DateTime = datetime - -import logilab.common as lgc - - -def decode_QP(string): - parts = [] - for decoded, charset in decode_header(string): - if not charset : - charset = 'iso-8859-15' - parts.append(unicode(decoded, charset, 'replace')) - - return u' '.join(parts) - -def message_from_file(fd): - try: - return UMessage(email.message_from_file(fd)) - except email.Errors.MessageParseError: - return '' - -def message_from_string(string): - try: - return UMessage(email.message_from_string(string)) - except email.Errors.MessageParseError: - return '' - -class UMessage: - """Encapsulates an email.Message instance and returns only unicode objects. - """ - - def __init__(self, message): - self.message = message - - # email.Message interface ################################################# - - def get(self, header, default=None): - value = self.message.get(header, default) - if value: - return decode_QP(value) - return value - - def get_all(self, header, default=()): - return [decode_QP(val) for val in self.message.get_all(header, default) - if val is not None] - - def get_payload(self, index=None, decode=False): - message = self.message - if index is None: - payload = message.get_payload(index, decode) - if isinstance(payload, list): - return [UMessage(msg) for msg in payload] - if message.get_content_maintype() != 'text': - return payload - - charset = message.get_content_charset() or 'iso-8859-1' - if search_function(charset) is None: - charset = 'iso-8859-1' - return unicode(payload or '', charset, "replace") - else: - payload = UMessage(message.get_payload(index, decode)) - return payload - - def is_multipart(self): - return self.message.is_multipart() - - def get_boundary(self): - return self.message.get_boundary() - - def walk(self): - for part in self.message.walk(): - yield UMessage(part) - - def get_content_maintype(self): - return unicode(self.message.get_content_maintype()) - - def get_content_type(self): - return unicode(self.message.get_content_type()) - - def get_filename(self, failobj=None): - value = self.message.get_filename(failobj) - if value is failobj: - return value - try: - return unicode(value) - except UnicodeDecodeError: - return u'error decoding filename' - - # other convenience methods ############################################### - - def headers(self): - """return an unicode string containing all the message's headers""" - values = [] - for header in self.message.keys(): - values.append(u'%s: %s' % (header, self.get(header))) - return '\n'.join(values) - - def multi_addrs(self, header): - """return a list of 2-uple (name, address) for the given address (which - is expected to be an header containing address such as from, to, cc...) - """ - persons = [] - for person in self.get_all(header, ()): - name, mail = parseaddr(person) - persons.append((name, mail)) - return persons - - def date(self, alternative_source=False, return_str=False): - """return a datetime object for the email's date or None if no date is - set or if it can't be parsed - """ - value = self.get('date') - if value is None and alternative_source: - unix_from = self.message.get_unixfrom() - if unix_from is not None: - try: - value = unix_from.split(" ", 2)[2] - except IndexError: - pass - if value is not None: - datetuple = parsedate(value) - if datetuple: - if lgc.USE_MX_DATETIME: - return DateTime(*datetuple[:6]) - return datetime(*datetuple[:6]) - elif not return_str: - return None - return value diff --git a/pylibs/logilab/common/urllib2ext.py b/pylibs/logilab/common/urllib2ext.py deleted file mode 100644 index 08797a41..00000000 --- a/pylibs/logilab/common/urllib2ext.py +++ /dev/null @@ -1,87 +0,0 @@ -import logging -import urllib2 - -import kerberos as krb - -class GssapiAuthError(Exception): - """raised on error during authentication process""" - -import re -RGX = re.compile('(?:.*,)*\s*Negotiate\s*([^,]*),?', re.I) - -def get_negociate_value(headers): - for authreq in headers.getheaders('www-authenticate'): - match = RGX.search(authreq) - if match: - return match.group(1) - -class HTTPGssapiAuthHandler(urllib2.BaseHandler): - """Negotiate HTTP authentication using context from GSSAPI""" - - handler_order = 400 # before Digest Auth - - def __init__(self): - self._reset() - - def _reset(self): - self._retried = 0 - self._context = None - - def clean_context(self): - if self._context is not None: - krb.authGSSClientClean(self._context) - - def http_error_401(self, req, fp, code, msg, headers): - try: - if self._retried > 5: - raise urllib2.HTTPError(req.get_full_url(), 401, - "negotiate auth failed", headers, None) - self._retried += 1 - logging.debug('gssapi handler, try %s' % self._retried) - negotiate = get_negociate_value(headers) - if negotiate is None: - logging.debug('no negociate found in a www-authenticate header') - return None - logging.debug('HTTPGssapiAuthHandler: negotiate 1 is %r' % negotiate) - result, self._context = krb.authGSSClientInit("HTTP@%s" % req.get_host()) - if result < 1: - raise GssapiAuthError("HTTPGssapiAuthHandler: init failed with %d" % result) - result = krb.authGSSClientStep(self._context, negotiate) - if result < 0: - raise GssapiAuthError("HTTPGssapiAuthHandler: step 1 failed with %d" % result) - client_response = krb.authGSSClientResponse(self._context) - logging.debug('HTTPGssapiAuthHandler: client response is %s...' % client_response[:10]) - req.add_unredirected_header('Authorization', "Negotiate %s" % client_response) - server_response = self.parent.open(req) - negotiate = get_negociate_value(server_response.info()) - if negotiate is None: - logging.warning('HTTPGssapiAuthHandler: failed to authenticate server') - else: - logging.debug('HTTPGssapiAuthHandler negotiate 2: %s' % negotiate) - result = krb.authGSSClientStep(self._context, negotiate) - if result < 1: - raise GssapiAuthError("HTTPGssapiAuthHandler: step 2 failed with %d" % result) - return server_response - except GssapiAuthError, exc: - logging.error(repr(exc)) - finally: - self.clean_context() - self._reset() - -if __name__ == '__main__': - import sys - # debug - import httplib - httplib.HTTPConnection.debuglevel = 1 - httplib.HTTPSConnection.debuglevel = 1 - # debug - import logging - logging.basicConfig(level=logging.DEBUG) - # handle cookies - import cookielib - cj = cookielib.CookieJar() - ch = urllib2.HTTPCookieProcessor(cj) - # test with url sys.argv[1] - h = HTTPGssapiAuthHandler() - response = urllib2.build_opener(h, ch).open(sys.argv[1]) - print '\nresponse: %s\n--------------\n' % response.code, response.info() diff --git a/pylibs/logilab/common/vcgutils.py b/pylibs/logilab/common/vcgutils.py deleted file mode 100644 index 9cd2acda..00000000 --- a/pylibs/logilab/common/vcgutils.py +++ /dev/null @@ -1,216 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""Functions to generate files readable with Georg Sander's vcg -(Visualization of Compiler Graphs). - -You can download vcg at http://rw4.cs.uni-sb.de/~sander/html/gshome.html -Note that vcg exists as a debian package. - -See vcg's documentation for explanation about the different values that -maybe used for the functions parameters. - - - - -""" -__docformat__ = "restructuredtext en" - -import string - -ATTRS_VAL = { - 'algos': ('dfs', 'tree', 'minbackward', - 'left_to_right', 'right_to_left', - 'top_to_bottom', 'bottom_to_top', - 'maxdepth', 'maxdepthslow', 'mindepth', 'mindepthslow', - 'mindegree', 'minindegree', 'minoutdegree', - 'maxdegree', 'maxindegree', 'maxoutdegree'), - 'booleans': ('yes', 'no'), - 'colors': ('black', 'white', 'blue', 'red', 'green', 'yellow', - 'magenta', 'lightgrey', - 'cyan', 'darkgrey', 'darkblue', 'darkred', 'darkgreen', - 'darkyellow', 'darkmagenta', 'darkcyan', 'gold', - 'lightblue', 'lightred', 'lightgreen', 'lightyellow', - 'lightmagenta', 'lightcyan', 'lilac', 'turquoise', - 'aquamarine', 'khaki', 'purple', 'yellowgreen', 'pink', - 'orange', 'orchid'), - 'shapes': ('box', 'ellipse', 'rhomb', 'triangle'), - 'textmodes': ('center', 'left_justify', 'right_justify'), - 'arrowstyles': ('solid', 'line', 'none'), - 'linestyles': ('continuous', 'dashed', 'dotted', 'invisible'), - } - -# meaning of possible values: -# O -> string -# 1 -> int -# list -> value in list -GRAPH_ATTRS = { - 'title': 0, - 'label': 0, - 'color': ATTRS_VAL['colors'], - 'textcolor': ATTRS_VAL['colors'], - 'bordercolor': ATTRS_VAL['colors'], - 'width': 1, - 'height': 1, - 'borderwidth': 1, - 'textmode': ATTRS_VAL['textmodes'], - 'shape': ATTRS_VAL['shapes'], - 'shrink': 1, - 'stretch': 1, - 'orientation': ATTRS_VAL['algos'], - 'vertical_order': 1, - 'horizontal_order': 1, - 'xspace': 1, - 'yspace': 1, - 'layoutalgorithm': ATTRS_VAL['algos'], - 'late_edge_labels': ATTRS_VAL['booleans'], - 'display_edge_labels': ATTRS_VAL['booleans'], - 'dirty_edge_labels': ATTRS_VAL['booleans'], - 'finetuning': ATTRS_VAL['booleans'], - 'manhattan_edges': ATTRS_VAL['booleans'], - 'smanhattan_edges': ATTRS_VAL['booleans'], - 'port_sharing': ATTRS_VAL['booleans'], - 'edges': ATTRS_VAL['booleans'], - 'nodes': ATTRS_VAL['booleans'], - 'splines': ATTRS_VAL['booleans'], - } -NODE_ATTRS = { - 'title': 0, - 'label': 0, - 'color': ATTRS_VAL['colors'], - 'textcolor': ATTRS_VAL['colors'], - 'bordercolor': ATTRS_VAL['colors'], - 'width': 1, - 'height': 1, - 'borderwidth': 1, - 'textmode': ATTRS_VAL['textmodes'], - 'shape': ATTRS_VAL['shapes'], - 'shrink': 1, - 'stretch': 1, - 'vertical_order': 1, - 'horizontal_order': 1, - } -EDGE_ATTRS = { - 'sourcename': 0, - 'targetname': 0, - 'label': 0, - 'linestyle': ATTRS_VAL['linestyles'], - 'class': 1, - 'thickness': 0, - 'color': ATTRS_VAL['colors'], - 'textcolor': ATTRS_VAL['colors'], - 'arrowcolor': ATTRS_VAL['colors'], - 'backarrowcolor': ATTRS_VAL['colors'], - 'arrowsize': 1, - 'backarrowsize': 1, - 'arrowstyle': ATTRS_VAL['arrowstyles'], - 'backarrowstyle': ATTRS_VAL['arrowstyles'], - 'textmode': ATTRS_VAL['textmodes'], - 'priority': 1, - 'anchor': 1, - 'horizontal_order': 1, - } - - -# Misc utilities ############################################################### - -def latin_to_vcg(st): - """Convert latin characters using vcg escape sequence. - """ - for char in st: - if char not in string.ascii_letters: - try: - num = ord(char) - if num >= 192: - st = st.replace(char, r'\fi%d'%ord(char)) - except: - pass - return st - - -class VCGPrinter: - """A vcg graph writer. - """ - - def __init__(self, output_stream): - self._stream = output_stream - self._indent = '' - - def open_graph(self, **args): - """open a vcg graph - """ - self._stream.write('%sgraph:{\n'%self._indent) - self._inc_indent() - self._write_attributes(GRAPH_ATTRS, **args) - - def close_graph(self): - """close a vcg graph - """ - self._dec_indent() - self._stream.write('%s}\n'%self._indent) - - - def node(self, title, **args): - """draw a node - """ - self._stream.write('%snode: {title:"%s"' % (self._indent, title)) - self._write_attributes(NODE_ATTRS, **args) - self._stream.write('}\n') - - - def edge(self, from_node, to_node, edge_type='', **args): - """draw an edge from a node to another. - """ - self._stream.write( - '%s%sedge: {sourcename:"%s" targetname:"%s"' % ( - self._indent, edge_type, from_node, to_node)) - self._write_attributes(EDGE_ATTRS, **args) - self._stream.write('}\n') - - - # private ################################################################## - - def _write_attributes(self, attributes_dict, **args): - """write graph, node or edge attributes - """ - for key, value in args.items(): - try: - _type = attributes_dict[key] - except KeyError: - raise Exception('''no such attribute %s -possible attributes are %s''' % (key, attributes_dict.keys())) - - if not _type: - self._stream.write('%s%s:"%s"\n' % (self._indent, key, value)) - elif _type == 1: - self._stream.write('%s%s:%s\n' % (self._indent, key, - int(value))) - elif value in _type: - self._stream.write('%s%s:%s\n' % (self._indent, key, value)) - else: - raise Exception('''value %s isn\'t correct for attribute %s -correct values are %s''' % (value, key, _type)) - - def _inc_indent(self): - """increment indentation - """ - self._indent = ' %s' % self._indent - - def _dec_indent(self): - """decrement indentation - """ - self._indent = self._indent[:-2] diff --git a/pylibs/logilab/common/xmlrpcutils.py b/pylibs/logilab/common/xmlrpcutils.py deleted file mode 100644 index 1d30d829..00000000 --- a/pylibs/logilab/common/xmlrpcutils.py +++ /dev/null @@ -1,131 +0,0 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""XML-RPC utilities.""" -__docformat__ = "restructuredtext en" - -import xmlrpclib -from base64 import encodestring -#from cStringIO import StringIO - -ProtocolError = xmlrpclib.ProtocolError - -## class BasicAuthTransport(xmlrpclib.Transport): -## def __init__(self, username=None, password=None): -## self.username = username -## self.password = password -## self.verbose = None -## self.has_ssl = httplib.__dict__.has_key("HTTPConnection") - -## def request(self, host, handler, request_body, verbose=None): -## # issue XML-RPC request -## if self.has_ssl: -## if host.startswith("https:"): h = httplib.HTTPSConnection(host) -## else: h = httplib.HTTPConnection(host) -## else: h = httplib.HTTP(host) - -## h.putrequest("POST", handler) - -## # required by HTTP/1.1 -## if not self.has_ssl: # HTTPConnection already does 1.1 -## h.putheader("Host", host) -## h.putheader("Connection", "close") - -## if request_body: h.send(request_body) -## if self.has_ssl: -## response = h.getresponse() -## if response.status != 200: -## raise xmlrpclib.ProtocolError(host + handler, -## response.status, -## response.reason, -## response.msg) -## file = response.fp -## else: -## errcode, errmsg, headers = h.getreply() -## if errcode != 200: -## raise xmlrpclib.ProtocolError(host + handler, errcode, -## errmsg, headers) - -## file = h.getfile() - -## return self.parse_response(file) - - - -class AuthMixin: - """basic http authentication mixin for xmlrpc transports""" - - def __init__(self, username, password, encoding): - self.verbose = 0 - self.username = username - self.password = password - self.encoding = encoding - - def request(self, host, handler, request_body, verbose=0): - """issue XML-RPC request""" - h = self.make_connection(host) - h.putrequest("POST", handler) - # required by XML-RPC - h.putheader("User-Agent", self.user_agent) - h.putheader("Content-Type", "text/xml") - h.putheader("Content-Length", str(len(request_body))) - h.putheader("Host", host) - h.putheader("Connection", "close") - # basic auth - if self.username is not None and self.password is not None: - h.putheader("AUTHORIZATION", "Basic %s" % encodestring( - "%s:%s" % (self.username, self.password)).replace("\012", "")) - h.endheaders() - # send body - if request_body: - h.send(request_body) - # get and check reply - errcode, errmsg, headers = h.getreply() - if errcode != 200: - raise ProtocolError(host + handler, errcode, errmsg, headers) - file = h.getfile() -## # FIXME: encoding ??? iirc, this fix a bug in xmlrpclib but... -## data = h.getfile().read() -## if self.encoding != 'UTF-8': -## data = data.replace("version='1.0'", -## "version='1.0' encoding='%s'" % self.encoding) -## result = StringIO() -## result.write(data) -## result.seek(0) -## return self.parse_response(result) - return self.parse_response(file) - -class BasicAuthTransport(AuthMixin, xmlrpclib.Transport): - """basic http authentication transport""" - -class BasicAuthSafeTransport(AuthMixin, xmlrpclib.SafeTransport): - """basic https authentication transport""" - - -def connect(url, user=None, passwd=None, encoding='ISO-8859-1'): - """return an xml rpc server on , using user / password if specified - """ - if user or passwd: - assert user and passwd is not None - if url.startswith('https://'): - transport = BasicAuthSafeTransport(user, passwd, encoding) - else: - transport = BasicAuthTransport(user, passwd, encoding) - else: - transport = None - server = xmlrpclib.ServerProxy(url, transport, encoding=encoding) - return server diff --git a/pylibs/logilab/common/xmlutils.py b/pylibs/logilab/common/xmlutils.py deleted file mode 100644 index d383b9d5..00000000 --- a/pylibs/logilab/common/xmlutils.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of logilab-common. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# logilab-common 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 Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with logilab-common. If not, see . -"""XML utilities. - -This module contains useful functions for parsing and using XML data. For the -moment, there is only one function that can parse the data inside a processing -instruction and return a Python dictionary. - - - - -""" -__docformat__ = "restructuredtext en" - -import re - -RE_DOUBLE_QUOTE = re.compile('([\w\-\.]+)="([^"]+)"') -RE_SIMPLE_QUOTE = re.compile("([\w\-\.]+)='([^']+)'") - -def parse_pi_data(pi_data): - """ - Utility function that parses the data contained in an XML - processing instruction and returns a dictionary of keywords and their - associated values (most of the time, the processing instructions contain - data like ``keyword="value"``, if a keyword is not associated to a value, - for example ``keyword``, it will be associated to ``None``). - - :param pi_data: data contained in an XML processing instruction. - :type pi_data: unicode - - :returns: Dictionary of the keywords (Unicode strings) associated to - their values (Unicode strings) as they were defined in the - data. - :rtype: dict - """ - results = {} - for elt in pi_data.split(): - if RE_DOUBLE_QUOTE.match(elt): - kwd, val = RE_DOUBLE_QUOTE.match(elt).groups() - elif RE_SIMPLE_QUOTE.match(elt): - kwd, val = RE_SIMPLE_QUOTE.match(elt).groups() - else: - kwd, val = elt, None - results[kwd] = val - return results diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py new file mode 100644 index 00000000..78e32370 --- /dev/null +++ b/pylibs/pylama/__init__.py @@ -0,0 +1,8 @@ +" pylama -- Python code audit. " + +version_info = (0, 2, 1) + +__version__ = version = '.'.join(map(str, version_info)) +__project__ = __name__ +__author__ = "Kirill Klenov " +__license__ = "GNU LGPL" diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py new file mode 100644 index 00000000..23099d8c --- /dev/null +++ b/pylibs/pylama/main.py @@ -0,0 +1,170 @@ +import logging +import fnmatch +import re +import sys +from argparse import ArgumentParser +from os import getcwd, walk, path as op + +from . import utils + + +default_linters = 'pep8', 'pyflakes', 'mccabe' +default_complexity = 10 +logger = logging.Logger('pylama') + + +def run(path, ignore=None, select=None, linters=default_linters, **meta): + errors = [] + ignore = ignore and list(ignore) or [] + select = select and list(select) or [] + + for lint in linters: + try: + linter = getattr(utils, lint) + except AttributeError: + logging.warning("Linter `{0}` not found.".format(lint)) + continue + + try: + code = open(path, "rU").read() + '\n\n' + params = parse_modeline(code) + + if params.get('lint_ignore'): + ignore += params.get('lint_ignore').split(',') + + if params.get('lint_select'): + select += params.get('lint_select').split(',') + + if params.get('lint'): + for e in linter(path, code=code, **meta): + e.update( + col=e.get('col', 0), + lnum=e.get('lnum', 0), + type=e.get('type', 'E'), + text="{0} [{1}]".format( + e.get('text', '').strip( + ).replace("'", "\"").split('\n')[0], + lint), + filename=path or '', + ) + errors.append(e) + + except IOError, e: + errors.append(dict( + lnum=0, + type='E', + col=0, + text=str(e) + )) + except SyntaxError, e: + errors.append(dict( + lnum=e.lineno or 0, + type='E', + col=e.offset or 0, + text=e.args[0] + )) + break + + except Exception, e: + import traceback + logging.error(traceback.format_exc()) + + errors = [e for e in errors if _ignore_error(e, select, ignore)] + return sorted(errors, key=lambda x: x['lnum']) + + +def _ignore_error(e, select, ignore): + for s in select: + if e['text'].startswith(s): + return True + for i in ignore: + if e['text'].startswith(i): + return False + return True + + +def shell(): + parser = ArgumentParser(description="Code audit tool for python.") + parser.add_argument("path", nargs='?', default=getcwd(), + help="Path on file or directory.") + parser.add_argument( + "--verbose", "-v", action='store_true', help="Verbose mode.") + + split_csp_list = lambda s: list(set(i for i in s.split(',') if i)) + + parser.add_argument( + "--select", "-s", default='', + type=split_csp_list, + help="Select errors and warnings. (comma-separated)") + parser.add_argument( + "--linters", "-l", default=','.join(default_linters), + type=split_csp_list, + help="Select linters. (comma-separated)") + parser.add_argument( + "--ignore", "-i", default='', + type=split_csp_list, + help="Ignore errors and warnings. (comma-separated)") + parser.add_argument( + "--skip", default='', + type=lambda s: [re.compile(fnmatch.translate(p)) + for p in s.split(',')], + help="Skip files by masks (comma-separated, Ex. */messages.py)") + parser.add_argument("--complexity", "-c", default=default_complexity, + type=int, help="Set mccabe complexity.") + parser.add_argument("--report", "-r", help="Filename for report.") + args = parser.parse_args() + + # Setup logger + logger.setLevel(logging.INFO if args.verbose else logging.WARN) + if args.report: + logger.addHandler(logging.FileHandler(args.report, mode='w')) + else: + logger.addHandler(logging.StreamHandler()) + + paths = [args.path] + + if op.isdir(args.path): + paths = [] + for root, _, files in walk(args.path): + paths += [op.join(root, f) for f in files if f.endswith('.py')] + + for path in skip_paths(args, paths): + logger.info("Parse file: %s" % path) + errors = run(path, ignore=args.ignore, select=args.select, + linters=args.linters, complexity=args.complexity) + for error in errors: + try: + error['rel'] = op.relpath( + error['filename'], op.dirname(args.path)) + error['col'] = error.get('col', 1) + logger.warning("%(rel)s:%(lnum)s:%(col)s: %(text)s", error) + except KeyError: + continue + + sys.exit(int(bool(errors))) + + +MODERE = re.compile( + r'^\s*#\s+(?:pymode\:)?((?:lint[\w_]*=[^:\n\s]+:?)+)', re.I | re.M) + + +def skip_paths(args, paths): + for path in paths: + if args.skip and any(pattern.match(path) for pattern in args.skip): + continue + yield path + + +def parse_modeline(code): + seek = MODERE.search(code) + params = dict(lint=1) + if seek: + params = dict(v.split('=') for v in seek.group(1).split(':')) + params['lint'] = int(params.get('lint', 1)) + return params + + +if __name__ == '__main__': + shell() + +# lint=12:lint_ignore=test diff --git a/pylibs/mccabe.py b/pylibs/pylama/mccabe.py similarity index 95% rename from pylibs/mccabe.py rename to pylibs/pylama/mccabe.py index 96fb6e72..01e00537 100644 --- a/pylibs/mccabe.py +++ b/pylibs/pylama/mccabe.py @@ -23,6 +23,7 @@ class ASTVisitor: def __init__(self): self.node = None self._cache = {} + self.visitor = None def default(self, node, *args): if hasattr(node, 'getChildNodes'): @@ -48,7 +49,7 @@ def preorder(self, tree, visitor, *args): """Do preorder walk of tree using visitor""" self.visitor = visitor visitor.visit = self.dispatch - self.dispatch(tree, *args) # XXX *args make sense? + self.dispatch(tree, *args) class PathNode: @@ -57,8 +58,8 @@ def __init__(self, name, look="circle"): self.look = look def to_dot(self): - print('node [shape=%s,label="%s"] %d;' % \ - (self.look, self.name, self.dot_id())) + print('node [shape=%s,label="%s"] %d;' % + (self.look, self.name, self.dot_id())) def dot_id(self): return id(self) @@ -79,8 +80,8 @@ def to_dot(self): for node in self.nodes: node.to_dot() for node, nexts in self.nodes.items(): - for next in nexts: - print('%s -- %s;' % (node.dot_id(), next.dot_id())) + for nxt in nexts: + print('%s -- %s;' % (node.dot_id(), nxt.dot_id())) print('}') def complexity(self): @@ -244,9 +245,9 @@ def get_code_complexity(code, min=7, filename='stdin'): continue if graph.complexity() >= min: complex.append(dict( - type = 'W', - lnum = graph.lineno, - text = '%s %r is too complex (%d)' % ( + type='W', + lnum=graph.lineno, + text='%s %r is too complex (%d)' % ( WARNING_CODE, graph.entity, graph.complexity(), @@ -291,3 +292,5 @@ def main(argv): if __name__ == '__main__': main(sys.argv[1:]) + +# pymode:lint=0 diff --git a/pylibs/pep8.py b/pylibs/pylama/pep8.py similarity index 79% rename from pylibs/pep8.py rename to pylibs/pylama/pep8.py index 35072ece..025b1d0b 100644 --- a/pylibs/pep8.py +++ b/pylibs/pylama/pep8.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # pep8.py - Check Python source code formatting, according to PEP 8 -# Copyright (C) 2006 Johann C. Rocholl +# Copyright (C) 2006-2009 Johann C. Rocholl +# Copyright (C) 2009-2013 Florent Xicluna # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files @@ -43,57 +44,8 @@ 600 deprecation 700 statements 900 syntax error - -You can add checks to this program by writing plugins. Each plugin is -a simple function that is called for each line of source code, either -physical or logical. - -Physical line: -- Raw line of text from the input file. - -Logical line: -- Multi-line statements converted to a single line. -- Stripped left and right. -- Contents of strings replaced with 'xxx' of same length. -- Comments removed. - -The check function requests physical or logical lines by the name of -the first argument: - -def maximum_line_length(physical_line) -def extraneous_whitespace(logical_line) -def blank_lines(logical_line, blank_lines, indent_level, line_number) - -The last example above demonstrates how check plugins can request -additional information with extra arguments. All attributes of the -Checker object are available. Some examples: - -lines: a list of the raw lines from the input file -tokens: the tokens that contribute to this logical line -line_number: line number in the input file -blank_lines: blank lines before this one -indent_char: first indentation character in this file (' ' or '\t') -indent_level: indentation (with tabs expanded to multiples of 8) -previous_indent_level: indentation on previous line -previous_logical: previous logical line - -The docstring of each check function shall be the relevant part of -text from PEP 8. It is printed if the user enables --show-pep8. -Several docstrings contain examples directly from the PEP 8 document. - -Okay: spam(ham[1], {eggs: 2}) -E201: spam( ham[1], {eggs: 2}) - -These examples are verified automatically when pep8.py is run with the ---doctest option. You can add examples for your own check functions. -The format is simple: "Okay" or error/warning code followed by colon -and space, the rest of the line is example source code. If you put 'r' -before the docstring, you can use \n for newline, \t for tab and \s -for space. - """ - -__version__ = '1.3.3' +__version__ = '1.4.5' import os import sys @@ -110,49 +62,49 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number) except ImportError: from ConfigParser import RawConfigParser -DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git' -DEFAULT_IGNORE = 'E24' +DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__' +DEFAULT_IGNORE = 'E226,E24' if sys.platform == 'win32': DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8') else: DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), 'pep8') +PROJECT_CONFIG = ('.pep8', 'tox.ini', 'setup.cfg') +TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') MAX_LINE_LENGTH = 79 REPORT_FORMAT = { 'default': '%(path)s:%(row)d:%(col)d: %(code)s %(text)s', 'pylint': '%(path)s:%(row)d: [%(code)s] %(text)s', } - +PyCF_ONLY_AST = 1024 SINGLETONS = frozenset(['False', 'None', 'True']) KEYWORDS = frozenset(keyword.kwlist + ['print']) - SINGLETONS -BINARY_OPERATORS = frozenset([ - '**=', '*=', '+=', '-=', '!=', '<>', - '%=', '^=', '&=', '|=', '==', '/=', '//=', '<=', '>=', '<<=', '>>=', - '%', '^', '&', '|', '=', '/', '//', '<', '>', '<<']) UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) -OPERATORS = BINARY_OPERATORS | UNARY_OPERATORS +ARITHMETIC_OP = frozenset(['**', '*', '/', '//', '+', '-']) +WS_OPTIONAL_OPERATORS = ARITHMETIC_OP.union(['^', '&', '|', '<<', '>>', '%']) +WS_NEEDED_OPERATORS = frozenset([ + '**=', '*=', '/=', '//=', '+=', '-=', '!=', '<>', '<', '>', + '%=', '^=', '&=', '|=', '==', '<=', '>=', '<<=', '>>=', '=']) WHITESPACE = frozenset(' \t') SKIP_TOKENS = frozenset([tokenize.COMMENT, tokenize.NL, tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT]) BENCHMARK_KEYS = ['directories', 'files', 'logical lines', 'physical lines'] INDENT_REGEX = re.compile(r'([ \t]*)') -RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*(,)') +RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,') RERAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*,\s*\w+\s*,\s*\w+') -SELFTEST_REGEX = re.compile(r'(Okay|[EW]\d{3}):\s(.*)') -ERRORCODE_REGEX = re.compile(r'[EW]\d{3}') +ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b') DOCSTRING_REGEX = re.compile(r'u?r?["\']') EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]') WHITESPACE_AFTER_COMMA_REGEX = re.compile(r'[,;:]\s*(?: |\t)') COMPARE_SINGLETON_REGEX = re.compile(r'([=!]=)\s*(None|False|True)') -COMPARE_TYPE_REGEX = re.compile(r'([=!]=|is|is\s+not)\s*type(?:s\.(\w+)Type' - r'|\(\s*(\(\s*\)|[^)]*[^ )])\s*\))') -KEYWORD_REGEX = re.compile(r'(?:[^\s])(\s*)\b(?:%s)\b(\s*)' % - r'|'.join(KEYWORDS)) -OPERATOR_REGEX = re.compile(r'(?:[^\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') +COMPARE_TYPE_REGEX = re.compile(r'(?:[=!]=|is(?:\s+not)?)\s*type(?:s.\w+Type' + r'|\s*\(\s*([^)]*[^ )])\s*\))') +KEYWORD_REGEX = re.compile(r'(\s*)\b(?:%s)\b(\s*)' % r'|'.join(KEYWORDS)) +OPERATOR_REGEX = re.compile(r'(?:[^,\s])(\s*)(?:[-+*/|!<=>%&^]+)(\s*)') LAMBDA_REGEX = re.compile(r'\blambda\b') -HUNK_REGEX = re.compile(r'^@@ -\d+,\d+ \+(\d+),(\d+) @@.*$') +HUNK_REGEX = re.compile(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$') # Work around Python < 2.6 behaviour, which does not generate NL after # a comment which is on a line by itself. @@ -212,8 +164,8 @@ def trailing_whitespace(physical_line): The warning returned varies on whether the line itself is blank, for easier filtering for those who want to indent their blank lines. - Okay: spam(1) - W291: spam(1)\s + Okay: spam(1)\n# + W291: spam(1) \n# W293: class Foo(object):\n \n bang = 12 """ physical_line = physical_line.rstrip('\n') # chr(10), newline @@ -264,6 +216,8 @@ def maximum_line_length(physical_line, max_line_length): line = physical_line.rstrip() length = len(line) if length > max_line_length: + if noqa(line): + return if hasattr(line, 'decode'): # Python 2 # The line could contain multi-byte characters try: @@ -302,7 +256,7 @@ def blank_lines(logical_line, blank_lines, indent_level, line_number, E303: def a():\n\n\n\n pass E304: @decorator\n\ndef a():\n pass """ - if line_number == 1: + if line_number < 3 and not previous_logical: return # Don't expect blank lines before the first line if previous_logical.startswith('@'): if blank_lines: @@ -387,13 +341,15 @@ def missing_whitespace(logical_line): Okay: a[1:4:2] E231: ['a','b'] E231: foo(bar,baz) + E231: [{'a':'b'}] """ line = logical_line for index in range(len(line) - 1): char = line[index] if char in ',;:' and line[index + 1] not in WHITESPACE: before = line[:index] - if char == ':' and before.count('[') > before.count(']'): + if char == ':' and before.count('[') > before.count(']') and \ + before.rfind('{') < before.rfind('['): continue # Slice syntax, no space required if char == ',' and line[index + 1] == ')': continue # Allow tuple with only one element: (3,) @@ -455,7 +411,7 @@ def continuation_line_indentation(logical_line, tokens, indent_level, verbose): """ first_row = tokens[0][2][0] nrows = 1 + tokens[-1][2][0] - first_row - if nrows == 1: + if nrows == 1 or noqa(tokens[0][4]): return # indent_next tells us whether the next block is indented; assuming @@ -470,13 +426,14 @@ def continuation_line_indentation(logical_line, tokens, indent_level, verbose): # relative indents of physical lines rel_indent = [0] * nrows # visual indents - indent = [indent_level] indent_chances = {} - last_indent = (0, 0) + last_indent = tokens[0][2] + indent = [last_indent[1]] if verbose >= 3: print(">>> " + tokens[0][4].rstrip()) for token_type, text, start, end, line in tokens: + newline = row < start[0] - first_row if newline: row = start[0] - first_row @@ -490,7 +447,7 @@ def continuation_line_indentation(logical_line, tokens, indent_level, verbose): print("... " + line.rstrip()) # record the initial indent. - rel_indent[row] = start[1] - indent_level + rel_indent[row] = expand_indent(line) - indent_level if depth: # a bracket expression in a continuation line. @@ -508,11 +465,11 @@ def continuation_line_indentation(logical_line, tokens, indent_level, verbose): # this line starts with a closing bracket if indent[depth]: if start[1] != indent[depth]: - yield (start, 'E124 closing bracket does not match ' - 'visual indentation') + yield (start, "E124 closing bracket does not match " + "visual indentation") elif hang: - yield (start, 'E123 closing bracket does not match ' - 'indentation of opening bracket\'s line') + yield (start, "E123 closing bracket does not match " + "indentation of opening bracket's line") elif visual_indent is True: # visual indent is verified if not indent[depth]: @@ -522,32 +479,37 @@ def continuation_line_indentation(logical_line, tokens, indent_level, verbose): pass elif indent[depth] and start[1] < indent[depth]: # visual indent is broken - yield (start, 'E128 continuation line ' - 'under-indented for visual indent') + yield (start, "E128 continuation line " + "under-indented for visual indent") elif hang == 4 or (indent_next and rel_indent[row] == 8): # hanging indent is verified pass else: # indent is broken if hang <= 0: - error = 'E122', 'missing indentation or outdented' + error = "E122", "missing indentation or outdented" elif indent[depth]: - error = 'E127', 'over-indented for visual indent' + error = "E127", "over-indented for visual indent" elif hang % 4: - error = 'E121', 'indentation is not a multiple of four' + error = "E121", "indentation is not a multiple of four" else: - error = 'E126', 'over-indented for hanging indent' + error = "E126", "over-indented for hanging indent" yield start, "%s continuation line %s" % error # look for visual indenting - if parens[row] and token_type != tokenize.NL and not indent[depth]: + if (parens[row] and token_type not in (tokenize.NL, tokenize.COMMENT) + and not indent[depth]): indent[depth] = start[1] indent_chances[start[1]] = True if verbose >= 4: print("bracket depth %s indent to %s" % (depth, start[1])) # deal with implicit string concatenation - elif token_type == tokenize.STRING or text in ('u', 'ur', 'b', 'br'): + elif (token_type in (tokenize.STRING, tokenize.COMMENT) or + text in ('u', 'ur', 'b', 'br')): indent_chances[start[1]] = str + # special case for the "if" statement because len("if (") == 4 + elif not indent_chances and not row and not depth and text == 'if': + indent_chances[end[1] + 1] = True # keep track of bracket depth if token_type == tokenize.OP: @@ -664,20 +626,16 @@ def missing_whitespace_around_operator(logical_line, tokens): Okay: hypot2 = x * x + y * y Okay: c = (a + b) * (a - b) Okay: foo(bar, key='word', *args, **kwargs) - Okay: baz(**kwargs) - Okay: negative = -1 - Okay: spam(-1) Okay: alpha[:-i] - Okay: if not -5 < x < +5:\n pass - Okay: lambda *args, **kw: (args, kw) E225: i=i+1 E225: submitted +=1 - E225: x = x*2 - 1 - E225: hypot2 = x*x + y*y - E225: c = (a+b) * (a-b) - E225: c = alpha -4 + E225: x = x /2 - 1 E225: z = x **y + E226: c = (a+b) * (a-b) + E226: hypot2 = x*x + y*y + E227: c = a|b + E228: msg = fmt%(errno, errmsg) """ parens = 0 need_space = False @@ -685,7 +643,7 @@ def missing_whitespace_around_operator(logical_line, tokens): prev_text = prev_end = None for token_type, text, start, end, line in tokens: if token_type in (tokenize.NL, tokenize.NEWLINE, tokenize.ERRORTOKEN): - # ERRORTOKEN is triggered by backticks in Python 3000 + # ERRORTOKEN is triggered by backticks in Python 3 continue if text in ('(', 'lambda'): parens += 1 @@ -693,32 +651,56 @@ def missing_whitespace_around_operator(logical_line, tokens): parens -= 1 if need_space: if start != prev_end: + # Found a (probably) needed space + if need_space is not True and not need_space[1]: + yield (need_space[0], + "E225 missing whitespace around operator") need_space = False elif text == '>' and prev_text in ('<', '-'): # Tolerate the "<>" operator, even if running Python 3 # Deal with Python 3's annotated return value "->" pass else: - yield prev_end, "E225 missing whitespace around operator" + if need_space is True or need_space[1]: + # A needed trailing space was not found + yield prev_end, "E225 missing whitespace around operator" + else: + code, optype = 'E226', 'arithmetic' + if prev_text == '%': + code, optype = 'E228', 'modulo' + elif prev_text not in ARITHMETIC_OP: + code, optype = 'E227', 'bitwise or shift' + yield (need_space[0], "%s missing whitespace " + "around %s operator" % (code, optype)) need_space = False elif token_type == tokenize.OP and prev_end is not None: if text == '=' and parens: # Allow keyword args or defaults: foo(bar=None). pass - elif text in BINARY_OPERATORS: + elif text in WS_NEEDED_OPERATORS: need_space = True elif text in UNARY_OPERATORS: + # Check if the operator is being used as a binary operator # Allow unary operators: -123, -x, +1. # Allow argument unpacking: foo(*args, **kwargs). if prev_type == tokenize.OP: - if prev_text in '}])': - need_space = True + binary_usage = (prev_text in '}])') elif prev_type == tokenize.NAME: - if prev_text not in KEYWORDS: - need_space = True - elif prev_type not in SKIP_TOKENS: - need_space = True - if need_space and start == prev_end: + binary_usage = (prev_text not in KEYWORDS) + else: + binary_usage = (prev_type not in SKIP_TOKENS) + + if binary_usage: + need_space = None + elif text in WS_OPTIONAL_OPERATORS: + need_space = None + + if need_space is None: + # Surrounding space is optional, but ensure that + # trailing space matches opening space + need_space = (prev_end, start != prev_end) + elif need_space and start == prev_end: + # A needed opening space was not found yield prev_end, "E225 missing whitespace around operator" need_space = False prev_type = token_type @@ -766,12 +748,12 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): parens = 0 no_space = False prev_end = None + message = "E251 unexpected spaces around keyword / parameter equals" for token_type, text, start, end, line in tokens: if no_space: no_space = False if start != prev_end: - yield (prev_end, - "E251 no spaces around keyword / parameter equals") + yield (prev_end, message) elif token_type == tokenize.OP: if text == '(': parens += 1 @@ -780,8 +762,7 @@ def whitespace_around_named_parameter_equals(logical_line, tokens): elif parens and text == '=': no_space = True if start != prev_end: - yield (prev_end, - "E251 no spaces around keyword / parameter equals") + yield (prev_end, message) prev_end = end @@ -807,7 +788,8 @@ def whitespace_before_inline_comment(logical_line, tokens): if prev_end[0] == start[0] and start[1] < prev_end[1] + 2: yield (prev_end, "E261 at least two spaces before inline comment") - if text.startswith('# ') or not text.startswith('# '): + symbol, sp, comment = text.partition(' ') + if symbol not in ('#', '#:') or comment[:1].isspace(): yield start, "E262 inline comment should start with '# '" elif token_type != tokenize.NL: prev_end = end @@ -829,7 +811,7 @@ def imports_on_separate_lines(logical_line): line = logical_line if line.startswith('import '): found = line.find(',') - if -1 < found: + if -1 < found and ';' not in line[:found]: yield found, "E401 multiple imports on one line" @@ -857,10 +839,12 @@ def compound_statements(logical_line): E701: if foo == 'blah': one(); two(); three() E702: do_one(); do_two(); do_three() + E703: do_four(); # useless semicolon """ line = logical_line + last_char = len(line) - 1 found = line.find(':') - if -1 < found < len(line) - 1: + if -1 < found < last_char: before = line[:found] if (before.count('{') <= before.count('}') and # {'a': 1} (dict) before.count('[') <= before.count(']') and # [1:2] (slice) @@ -869,7 +853,10 @@ def compound_statements(logical_line): yield found, "E701 multiple statements on one line (colon)" found = line.find(';') if -1 < found: - yield found, "E702 multiple statements on one line (semicolon)" + if found < last_char: + yield found, "E702 multiple statements on one line (semicolon)" + else: + yield found, "E703 statement ends with a semicolon" def explicit_line_join(logical_line, tokens): @@ -954,16 +941,16 @@ def comparison_type(logical_line): """ match = COMPARE_TYPE_REGEX.search(logical_line) if match: - inst = match.group(3) + inst = match.group(1) if inst and isidentifier(inst) and inst not in SINGLETONS: return # Allow comparison for types which are not obvious - yield match.start(1), "E721 do not compare types, use 'isinstance()'" + yield match.start(), "E721 do not compare types, use 'isinstance()'" def python_3000_has_key(logical_line): r""" - The {}.has_key() method will be removed in the future version of - Python. Use the 'in' operation instead. + The {}.has_key() method is removed in the Python 3. + Use the 'in' operation instead. Okay: if "alph" in d:\n print d["alph"] W601: assert d.has_key('alph') @@ -981,21 +968,21 @@ def python_3000_raise_comma(logical_line): The paren-using form is preferred because when the exception arguments are long or include string formatting, you don't need to use line continuation characters thanks to the containing parentheses. The older - form will be removed in Python 3000. + form is removed in Python 3. Okay: raise DummyError("Message") W602: raise DummyError, "Message" """ match = RAISE_COMMA_REGEX.match(logical_line) if match and not RERAISE_COMMA_REGEX.match(logical_line): - yield match.start(1), "W602 deprecated form of raising exception" + yield match.end() - 1, "W602 deprecated form of raising exception" def python_3000_not_equal(logical_line): """ != can also be written <>, but this is an obsolete usage kept for backwards compatibility only. New code should always use !=. - The older syntax is removed in Python 3000. + The older syntax is removed in Python 3. Okay: if a != 'no': W603: if a <> 'no': @@ -1007,7 +994,7 @@ def python_3000_not_equal(logical_line): def python_3000_backticks(logical_line): """ - Backticks are removed in Python 3000. + Backticks are removed in Python 3. Use repr() instead. Okay: val = repr(1 + 2) @@ -1051,8 +1038,11 @@ def readlines(filename): f.close() isidentifier = str.isidentifier - stdin_get_value = TextIOWrapper(sys.stdin.buffer, errors='ignore').read + + def stdin_get_value(): + return TextIOWrapper(sys.stdin.buffer, errors='ignore').read() readlines.__doc__ = " Read the source code." +noqa = re.compile(r'# no(?:qa|pep8)\b', re.I).search def expand_indent(line): @@ -1106,6 +1096,9 @@ def mute_string(text): def parse_udiff(diff, patterns=None, parent='.'): + """Return a dictionary of matching lines.""" + # For each file of the diff, the entry key is the filename, + # and the value is a set of row numbers to consider. rv = {} path = nrows = None for line in diff.splitlines(): @@ -1114,7 +1107,8 @@ def parse_udiff(diff, patterns=None, parent='.'): nrows -= 1 continue if line[:3] == '@@ ': - row, nrows = [int(g) for g in HUNK_REGEX.match(line).groups()] + hunk_match = HUNK_REGEX.match(line) + row, nrows = [int(g or '1') for g in hunk_match.groups()] rv[path].update(range(row, row + nrows)) elif line[:3] == '+++': path = line[4:].split('\t', 1)[0] @@ -1141,18 +1135,38 @@ def filename_match(filename, patterns, default=True): ############################################################################## -def find_checks(argument_name): +_checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}} + + +def register_check(check, codes=None): """ - Find all globally visible functions where the first argument name - starts with argument_name. + Register a new check object. """ - for name, function in globals().items(): - if not inspect.isfunction(function): - continue - args = inspect.getargspec(function)[0] - if args and args[0].startswith(argument_name): - codes = ERRORCODE_REGEX.findall(function.__doc__ or '') - yield name, codes, function, args + def _add_check(check, kind, codes, args): + if check in _checks[kind]: + _checks[kind][check][0].extend(codes or []) + else: + _checks[kind][check] = (codes or [''], args) + if inspect.isfunction(check): + args = inspect.getargspec(check)[0] + if args and args[0] in ('physical_line', 'logical_line'): + if codes is None: + codes = ERRORCODE_REGEX.findall(check.__doc__ or '') + _add_check(check, args[0], codes, args) + elif inspect.isclass(check): + if inspect.getargspec(check.__init__)[0][:2] == ['self', 'tree']: + _add_check(check, 'tree', codes, None) + + +def init_checks_registry(): + """ + Register all globally visible functions where the first argument name + is 'physical_line' or 'logical_line'. + """ + mod = inspect.getmodule(register_check) + for (name, function) in inspect.getmembers(mod, inspect.isfunction): + register_check(function) +init_checks_registry() class Checker(object): @@ -1160,7 +1174,7 @@ class Checker(object): Load a Python source file, tokenize it, check coding style. """ - def __init__(self, filename, lines=None, + def __init__(self, filename=None, lines=None, options=None, report=None, **kwargs): if options is None: options = StyleGuide(kwargs).options @@ -1169,12 +1183,16 @@ def __init__(self, filename, lines=None, self._io_error = None self._physical_checks = options.physical_checks self._logical_checks = options.logical_checks + self._ast_checks = options.ast_checks self.max_line_length = options.max_line_length self.verbose = options.verbose self.filename = filename if filename is None: self.filename = 'stdin' self.lines = lines or [] + elif filename == '-': + self.filename = 'stdin' + self.lines = stdin_get_value().splitlines(True) elif lines is None: try: self.lines = readlines(filename) @@ -1187,6 +1205,16 @@ def __init__(self, filename, lines=None, self.report = report or options.report self.report_error = self.report.error + def report_invalid_syntax(self): + exc_type, exc = sys.exc_info()[:2] + offset = exc.args[1] + if len(offset) > 2: + offset = offset[1:3] + self.report_error(offset[0], offset[1] or 0, + 'E901 %s: %s' % (exc_type.__name__, exc.args[0]), + self.report_invalid_syntax) + report_invalid_syntax.__doc__ = " Check if the syntax is valid." + def readline(self): """ Get the next line from the input buffer. @@ -1260,7 +1288,8 @@ def build_tokens_line(self): length += len(text) previous = token self.logical_line = ''.join(logical) - assert self.logical_line.strip() == self.logical_line + # With Python 2, if the line ends with '\r\r\n' the assertion fails + # assert self.logical_line.strip() == self.logical_line def check_logical(self): """ @@ -1289,6 +1318,17 @@ def check_logical(self): self.report_error(orig_number, orig_offset, text, check) self.previous_logical = self.logical_line + def check_ast(self): + try: + tree = compile(''.join(self.lines), '', 'exec', PyCF_ONLY_AST) + except SyntaxError: + return self.report_invalid_syntax() + for name, cls, _ in self._ast_checks: + checker = cls(tree, self.filename) + for lineno, offset, text, check in checker.run(): + if not noqa(self.lines[lineno - 1]): + self.report_error(lineno, offset, text, check) + def generate_tokens(self): if self._io_error: self.report_error(1, 0, 'E902 %s' % self._io_error, readlines) @@ -1297,20 +1337,15 @@ def generate_tokens(self): for token in tokengen: yield token except (SyntaxError, tokenize.TokenError): - exc_type, exc = sys.exc_info()[:2] - offset = exc.args[1] - if len(offset) > 2: - offset = offset[1:3] - self.report_error(offset[0], offset[1], - 'E901 %s: %s' % (exc_type.__name__, exc.args[0]), - self.generate_tokens) - generate_tokens.__doc__ = " Check if the syntax is valid." + self.report_invalid_syntax() def check_all(self, expected=None, line_offset=0): """ Run all checks on the input file. """ self.report.init_file(self.filename, self.lines, expected, line_offset) + if self._ast_checks: + self.check_ast() self.line_number = 0 self.indent_char = None self.indent_level = 0 @@ -1446,6 +1481,7 @@ def print_benchmark(self): class FileReport(BaseReport): + """Collect the results of the checks and print only the filenames.""" print_filename = True @@ -1460,17 +1496,29 @@ def __init__(self, options): self._show_source = options.show_source self._show_pep8 = options.show_pep8 + def init_file(self, filename, lines, expected, line_offset): + """Signal a new file.""" + self._deferred_print = [] + return super(StandardReport, self).init_file( + filename, lines, expected, line_offset) + def error(self, line_number, offset, text, check): - """ - Report an error, according to options. - """ + """Report an error, according to options.""" code = super(StandardReport, self).error(line_number, offset, text, check) if code and (self.counters[code] == 1 or self._repeat): + self._deferred_print.append( + (line_number, offset, code, text[5:], check.__doc__)) + return code + + def get_file_results(self): + """Print the result and return the overall count for this file.""" + self._deferred_print.sort() + for line_number, offset, code, text, doc in self._deferred_print: print(self._fmt % { 'path': self.filename, 'row': self.line_offset + line_number, 'col': offset + 1, - 'code': code, 'text': text[5:], + 'code': code, 'text': text, }) if self._show_source: if line_number > len(self.lines): @@ -1479,9 +1527,9 @@ def error(self, line_number, offset, text, check): line = self.lines[line_number - 1] print(line.rstrip()) print(' ' * offset + '^') - if self._show_pep8: - print(check.__doc__.lstrip('\n').rstrip()) - return code + if self._show_pep8 and doc: + print(doc.lstrip('\n').rstrip()) + return self.file_errors class DiffReport(StandardReport): @@ -1497,54 +1545,17 @@ def error(self, line_number, offset, text, check): return super(DiffReport, self).error(line_number, offset, text, check) -class TestReport(StandardReport): - """Collect the results for the tests.""" - - def __init__(self, options): - options.benchmark_keys += ['test cases', 'failed tests'] - super(TestReport, self).__init__(options) - self._verbose = options.verbose - - def get_file_results(self): - # Check if the expected errors were found - label = '%s:%s:1' % (self.filename, self.line_offset) - codes = sorted(self.expected) - for code in codes: - if not self.counters.get(code): - self.file_errors += 1 - self.total_errors += 1 - print('%s: error %s not found' % (label, code)) - if self._verbose and not self.file_errors: - print('%s: passed (%s)' % - (label, ' '.join(codes) or 'Okay')) - self.counters['test cases'] += 1 - if self.file_errors: - self.counters['failed tests'] += 1 - # Reset counters - for key in set(self.counters) - set(self._benchmark_keys): - del self.counters[key] - self.messages = {} - return self.file_errors - - def print_results(self): - results = ("%(physical lines)d lines tested: %(files)d files, " - "%(test cases)d test cases%%s." % self.counters) - if self.total_errors: - print(results % ", %s failures" % self.total_errors) - else: - print(results % "") - print("Test failed." if self.total_errors else "Test passed.") - - class StyleGuide(object): """Initialize a PEP-8 instance with few options.""" def __init__(self, *args, **kwargs): # build options from the command line + self.checker_class = kwargs.pop('checker_class', Checker) parse_argv = kwargs.pop('parse_argv', False) config_file = kwargs.pop('config_file', None) - options, self.paths = process_options(parse_argv=parse_argv, - config_file=config_file) + parser = kwargs.pop('parser', None) + options, self.paths = process_options( + parse_argv=parse_argv, config_file=config_file, parser=parser) if args or kwargs: # build options from dict options_dict = dict(*args, **kwargs) @@ -1560,13 +1571,19 @@ def __init__(self, *args, **kwargs): for index, value in enumerate(options.exclude): options.exclude[index] = value.rstrip('/') - # Ignore all checks which are not explicitly selected options.select = tuple(options.select or ()) - options.ignore = tuple(options.ignore or options.select and ('',)) + if not (options.select or options.ignore or + options.testsuite or options.doctest) and DEFAULT_IGNORE: + # The default choice: ignore controversial checks + options.ignore = tuple(DEFAULT_IGNORE.split(',')) + else: + # Ignore all checks which are not explicitly selected + options.ignore = tuple(options.ignore or options.select and ('',)) options.benchmark_keys = BENCHMARK_KEYS[:] options.ignore_code = self.ignore_code options.physical_checks = self.get_checks('physical_line') options.logical_checks = self.get_checks('logical_line') + options.ast_checks = self.get_checks('tree') self.init_report() def init_report(self, reporter=None): @@ -1581,11 +1598,14 @@ def check_files(self, paths=None): report = self.options.report runner = self.runner report.start() - for path in paths: - if os.path.isdir(path): - self.input_dir(path) - elif not self.excluded(path): - runner(path) + try: + for path in paths: + if os.path.isdir(path): + self.input_dir(path) + elif not self.excluded(path): + runner(path) + except KeyboardInterrupt: + print('... stopped') report.stop() return report @@ -1593,7 +1613,8 @@ def input_file(self, filename, lines=None, expected=None, line_offset=0): """Run all checks on a Python source file.""" if self.options.verbose: print('checking %s' % filename) - fchecker = Checker(filename, lines=lines, options=self.options) + fchecker = self.checker_class( + filename, lines=lines, options=self.options) return fchecker.check_all(expected=expected, line_offset=line_offset) def input_dir(self, dirname): @@ -1610,7 +1631,7 @@ def input_dir(self, dirname): print('directory ' + root) counters['directories'] += 1 for subdir in sorted(dirs): - if self.excluded(subdir): + if self.excluded(os.path.join(root, subdir)): dirs.remove(subdir) for filename in sorted(files): # contain a pattern that matches? @@ -1623,7 +1644,10 @@ def excluded(self, filename): Check if options.exclude contains a pattern that matches filename. """ basename = os.path.basename(filename) - return filename_match(basename, self.options.exclude, default=False) + return any((filename_match(filename, self.options.exclude, + default=False), + filename_match(basename, self.options.exclude, + default=False))) def ignore_code(self, code): """ @@ -1642,168 +1666,15 @@ def get_checks(self, argument_name): starts with argument_name and which contain selected tests. """ checks = [] - for name, codes, function, args in find_checks(argument_name): + for check, attrs in _checks[argument_name].items(): + (codes, args) = attrs if any(not (code and self.ignore_code(code)) for code in codes): - checks.append((name, function, args)) + checks.append((check.__name__, check, args)) return sorted(checks) -def init_tests(pep8style): - """ - Initialize testing framework. - - A test file can provide many tests. Each test starts with a - declaration. This declaration is a single line starting with '#:'. - It declares codes of expected failures, separated by spaces or 'Okay' - if no failure is expected. - If the file does not contain such declaration, it should pass all - tests. If the declaration is empty, following lines are not checked, - until next declaration. - - Examples: - - * Only E224 and W701 are expected: #: E224 W701 - * Following example is conform: #: Okay - * Don't check these lines: #: - """ - report = pep8style.init_report(TestReport) - runner = pep8style.input_file - - def run_tests(filename): - """Run all the tests from a file.""" - lines = readlines(filename) + ['#:\n'] - line_offset = 0 - codes = ['Okay'] - testcase = [] - count_files = report.counters['files'] - for index, line in enumerate(lines): - if not line.startswith('#:'): - if codes: - # Collect the lines of the test case - testcase.append(line) - continue - if codes and index: - codes = [c for c in codes if c != 'Okay'] - # Run the checker - runner(filename, testcase, expected=codes, - line_offset=line_offset) - # output the real line numbers - line_offset = index + 1 - # configure the expected errors - codes = line.split()[1:] - # empty the test case buffer - del testcase[:] - report.counters['files'] = count_files + 1 - return report.counters['failed tests'] - - pep8style.runner = run_tests - - -def selftest(options): - """ - Test all check functions with test cases in docstrings. - """ - count_failed = count_all = 0 - report = BaseReport(options) - counters = report.counters - checks = options.physical_checks + options.logical_checks - for name, check, argument_names in checks: - for line in check.__doc__.splitlines(): - line = line.lstrip() - match = SELFTEST_REGEX.match(line) - if match is None: - continue - code, source = match.groups() - checker = Checker(None, options=options, report=report) - for part in source.split(r'\n'): - part = part.replace(r'\t', '\t') - part = part.replace(r'\s', ' ') - checker.lines.append(part + '\n') - checker.check_all() - error = None - if code == 'Okay': - if len(counters) > len(options.benchmark_keys): - codes = [key for key in counters - if key not in options.benchmark_keys] - error = "incorrectly found %s" % ', '.join(codes) - elif not counters.get(code): - error = "failed to find %s" % code - # Keep showing errors for multiple tests - for key in set(counters) - set(options.benchmark_keys): - del counters[key] - report.messages = {} - count_all += 1 - if not error: - if options.verbose: - print("%s: %s" % (code, source)) - else: - count_failed += 1 - print("%s: %s:" % (__file__, error)) - for line in checker.lines: - print(line.rstrip()) - return count_failed, count_all - - -def read_config(options, args, arglist, parser): - """Read both user configuration and local configuration.""" - config = RawConfigParser() - - user_conf = options.config - if user_conf and os.path.isfile(user_conf): - if options.verbose: - print('user configuration: %s' % user_conf) - config.read(user_conf) - - parent = tail = args and os.path.abspath(os.path.commonprefix(args)) - while tail: - local_conf = os.path.join(parent, '.pep8') - if os.path.isfile(local_conf): - if options.verbose: - print('local configuration: %s' % local_conf) - config.read(local_conf) - break - parent, tail = os.path.split(parent) - - if config.has_section('pep8'): - option_list = dict([(o.dest, o.type or o.action) - for o in parser.option_list]) - - # First, read the default values - new_options, _ = parser.parse_args([]) - - # Second, parse the configuration - for opt in config.options('pep8'): - if options.verbose > 1: - print(' %s = %s' % (opt, config.get('pep8', opt))) - if opt.replace('_', '-') not in parser.config_options: - print('Unknown option: \'%s\'\n not in [%s]' % - (opt, ' '.join(parser.config_options))) - sys.exit(1) - normalized_opt = opt.replace('-', '_') - opt_type = option_list[normalized_opt] - if opt_type in ('int', 'count'): - value = config.getint('pep8', opt) - elif opt_type == 'string': - value = config.get('pep8', opt) - else: - assert opt_type in ('store_true', 'store_false') - value = config.getboolean('pep8', opt) - setattr(new_options, normalized_opt, value) - - # Third, overwrite with the command-line options - options, _ = parser.parse_args(arglist, values=new_options) - - return options - - -def process_options(arglist=None, parse_argv=False, config_file=None): - """Process options passed either via arglist or via command line args.""" - if not arglist and not parse_argv: - # Don't read the command line if the module is used as a library. - arglist = [] - if config_file is True: - config_file = DEFAULT_CONFIG - parser = OptionParser(version=__version__, +def get_parser(prog='pep8', version=__version__): + parser = OptionParser(prog=prog, version=version, usage="%prog [options] input ...") parser.config_options = [ 'exclude', 'filename', 'select', 'ignore', 'max-line-length', 'count', @@ -1848,27 +1719,100 @@ def process_options(arglist=None, parse_argv=False, config_file=None): help="report only lines changed according to the " "unified diff received on STDIN") group = parser.add_option_group("Testing Options") - group.add_option('--testsuite', metavar='dir', - help="run regression tests from dir") - group.add_option('--doctest', action='store_true', - help="run doctest on myself") + if os.path.exists(TESTSUITE_PATH): + group.add_option('--testsuite', metavar='dir', + help="run regression tests from dir") + group.add_option('--doctest', action='store_true', + help="run doctest on myself") group.add_option('--benchmark', action='store_true', help="measure processing speed") - group = parser.add_option_group("Configuration", description=( - "The project options are read from the [pep8] section of the .pep8 " - "file located in any parent folder of the path(s) being processed. " - "Allowed options are: %s." % ', '.join(parser.config_options))) - group.add_option('--config', metavar='path', default=config_file, - help="config file location (default: %default)") + return parser + + +def read_config(options, args, arglist, parser): + """Read both user configuration and local configuration.""" + config = RawConfigParser() + + user_conf = options.config + if user_conf and os.path.isfile(user_conf): + if options.verbose: + print('user configuration: %s' % user_conf) + config.read(user_conf) + + parent = tail = args and os.path.abspath(os.path.commonprefix(args)) + while tail: + for name in PROJECT_CONFIG: + local_conf = os.path.join(parent, name) + if os.path.isfile(local_conf): + break + else: + parent, tail = os.path.split(parent) + continue + if options.verbose: + print('local configuration: %s' % local_conf) + config.read(local_conf) + break + + pep8_section = parser.prog + if config.has_section(pep8_section): + option_list = dict([(o.dest, o.type or o.action) + for o in parser.option_list]) + # First, read the default values + new_options, _ = parser.parse_args([]) + + # Second, parse the configuration + for opt in config.options(pep8_section): + if options.verbose > 1: + print(" %s = %s" % (opt, config.get(pep8_section, opt))) + if opt.replace('_', '-') not in parser.config_options: + print("Unknown option: '%s'\n not in [%s]" % + (opt, ' '.join(parser.config_options))) + sys.exit(1) + normalized_opt = opt.replace('-', '_') + opt_type = option_list[normalized_opt] + if opt_type in ('int', 'count'): + value = config.getint(pep8_section, opt) + elif opt_type == 'string': + value = config.get(pep8_section, opt) + else: + assert opt_type in ('store_true', 'store_false') + value = config.getboolean(pep8_section, opt) + setattr(new_options, normalized_opt, value) + + # Third, overwrite with the command-line options + options, _ = parser.parse_args(arglist, values=new_options) + options.doctest = options.testsuite = False + return options + + +def process_options(arglist=None, parse_argv=False, config_file=None, + parser=None): + """Process options passed either via arglist or via command line args.""" + if not arglist and not parse_argv: + # Don't read the command line if the module is used as a library. + arglist = [] + if not parser: + parser = get_parser() + if not parser.has_option('--config'): + if config_file is True: + config_file = DEFAULT_CONFIG + group = parser.add_option_group("Configuration", description=( + "The project options are read from the [%s] section of the " + "tox.ini file or the setup.cfg file located in any parent folder " + "of the path(s) being processed. Allowed options are: %s." % + (parser.prog, ', '.join(parser.config_options)))) + group.add_option('--config', metavar='path', default=config_file, + help="user config file location (default: %default)") options, args = parser.parse_args(arglist) options.reporter = None - if options.testsuite: + if options.ensure_value('testsuite', False): args.append(options.testsuite) - elif not options.doctest: + elif not options.ensure_value('doctest', False): if parse_argv and not args: - if os.path.exists('.pep8') or options.diff: + if options.diff or any(os.path.exists(name) + for name in PROJECT_CONFIG): args = ['.'] else: parser.error('input not specified') @@ -1882,11 +1826,6 @@ def process_options(arglist=None, parse_argv=False, config_file=None): options.select = options.select.split(',') if options.ignore: options.ignore = options.ignore.split(',') - elif not (options.select or - options.testsuite or options.doctest) and DEFAULT_IGNORE: - # The default choice: ignore controversial checks - # (for doctest and testsuite, all checks are required) - options.ignore = DEFAULT_IGNORE.split(',') if options.diff: options.reporter = DiffReport @@ -1901,20 +1840,11 @@ def _main(): """Parse options and run checks on Python source.""" pep8style = StyleGuide(parse_argv=True, config_file=True) options = pep8style.options - if options.doctest: - import doctest - fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose) - fail_s, done_s = selftest(options) - count_failed = fail_s + fail_d - if not options.quiet: - count_passed = done_d + done_s - count_failed - print("%d passed and %d failed." % (count_passed, count_failed)) - print("Test failed." if count_failed else "Test passed.") - if count_failed: - sys.exit(1) - if options.testsuite: - init_tests(pep8style) - report = pep8style.check_files() + if options.doctest or options.testsuite: + from testsuite.support import run_tests + report = run_tests(pep8style) + else: + report = pep8style.check_files() if options.statistics: report.print_statistics() if options.benchmark: @@ -1926,6 +1856,7 @@ def _main(): sys.stderr.write(str(report.total_errors) + '\n') sys.exit(1) - if __name__ == '__main__': _main() + +# pymode:lint=0 diff --git a/pylibs/pyflakes/__init__.py b/pylibs/pylama/pyflakes/__init__.py similarity index 100% rename from pylibs/pyflakes/__init__.py rename to pylibs/pylama/pyflakes/__init__.py diff --git a/pylibs/pyflakes/checker.py b/pylibs/pylama/pyflakes/checker.py similarity index 88% rename from pylibs/pyflakes/checker.py rename to pylibs/pylama/pyflakes/checker.py index 643603cd..8b0bab19 100644 --- a/pylibs/pyflakes/checker.py +++ b/pylibs/pylama/pyflakes/checker.py @@ -131,6 +131,7 @@ def names(self): class Scope(dict): importStarred = False # set to True when import * is found + usesLocals = False def __repr__(self): @@ -194,6 +195,7 @@ def __init__(self, tree, filename=None): self.filename = filename self.scopeStack = [ModuleScope()] self.futuresAllowed = True + self.root = tree self.handleChildren(tree) self._runDeferred(self._deferredFunctions) # Set _deferredFunctions to None so that deferFunction will fail @@ -284,6 +286,53 @@ def pushClassScope(self): def report(self, messageClass, *args, **kwargs): self.messages.append(messageClass(self.filename, *args, **kwargs)) + def lowestCommonAncestor(self, lnode, rnode, stop=None): + if not stop: + stop = self.root + if lnode is stop: + return lnode + if rnode is stop: + return rnode + + if lnode is rnode: + return lnode + if (lnode.level > rnode.level): + return self.lowestCommonAncestor(lnode.parent, rnode, stop) + if (rnode.level > lnode.level): + return self.lowestCommonAncestor(lnode, rnode.parent, stop) + if lnode.parent is rnode.parent: + return lnode.parent + else: + return self.lowestCommonAncestor(lnode.parent, rnode.parent, stop) + + def descendantOf(self, node, ancestors, stop=None): + for a in ancestors: + try: + p = self.lowestCommonAncestor(node, a, stop) + if not p is stop: + return True + except AttributeError: + # Skip some bogus objects like <_ast.Pass> + pass + return False + + def onFork(self, parent, lnode, rnode, items): + return int(self.descendantOf(lnode, items, parent)) + \ + int(self.descendantOf(rnode, items, parent)) + + def differentForks(self, lnode, rnode): + "True, if lnode and rnode are located on different forks of IF/TRY" + ancestor = self.lowestCommonAncestor(lnode, rnode) + if isinstance(ancestor, _ast.If): + for fork in (ancestor.body, ancestor.orelse): + if self.onFork(ancestor, lnode, rnode, fork) == 1: + return True + if isinstance(ancestor, _ast.TryExcept): + for fork in (ancestor.body, ancestor.handlers, ancestor.orelse): + if self.onFork(ancestor, lnode, rnode, fork) == 1: + return True + return False + def handleChildren(self, tree): for node in iter_child_nodes(tree): self.handleNode(node, tree) @@ -306,6 +355,7 @@ def handleNode(self, node, parent): (isinstance(node, _ast.ImportFrom) or self.isDocstring(node)): self.futuresAllowed = False nodeType = node.__class__.__name__.upper() + node.level = self.nodeDepth try: handler = getattr(self, nodeType) handler(node) @@ -356,7 +406,8 @@ def addBinding(self, loc, value, reportRedef=True): if (isinstance(self.scope.get(value.name), FunctionDefinition) and isinstance(value, FunctionDefinition)): if not value._property_decorator: - self.report(messages.RedefinedFunction, + if not self.differentForks(loc, self.scope[value.name].source): + self.report(messages.RedefinedFunction, loc, value.name, self.scope[value.name].source) if not isinstance(self.scope, ClassScope): @@ -367,8 +418,9 @@ def addBinding(self, loc, value, reportRedef=True): and (not isinstance(value, Importation) or value.fullName == existing.fullName) and reportRedef): - self.report(messages.RedefinedWhileUnused, - loc, value.name, scope[value.name].source) + if not self.differentForks(loc, existing.source): + self.report(messages.RedefinedWhileUnused, + loc, value.name, existing.source) if isinstance(value, UnBinding): try: @@ -428,6 +480,10 @@ def NAME(self, node): """ Handle occurrence of Name (which can be a load/store/delete access.) """ + if node.id == 'locals' and isinstance(node.parent, _ast.Call): + # we are doing locals() call in current scope + self.scope.usesLocals = True + # Locate the name in locals / function / globals scopes. if isinstance(node.ctx, (_ast.Load, _ast.AugLoad)): # try local scope @@ -523,9 +579,15 @@ def FUNCTIONDEF(self, node): # Check for property decorator func_def = FunctionDefinition(node.name, node) - for decorator in node.decorator_list: - if getattr(decorator, 'attr', None) in ('setter', 'deleter'): - func_def._property_decorator = True + + if hasattr(node, 'decorators'): + for decorator in node.decorators: + if getattr(decorator, 'attr', None) in ('setter', 'deleter'): + func_def._property_decorator = True + else: + for decorator in node.decorator_list: + if getattr(decorator, 'attr', None) in ('setter', 'deleter'): + func_def._property_decorator = True self.addBinding(node, func_def) self.LAMBDA(node) @@ -569,6 +631,7 @@ def checkUnusedAssignments(): """ for name, binding in self.scope.iteritems(): if (not binding.used and not name in self.scope.globals + and not self.scope.usesLocals and isinstance(binding, Assignment)): self.report(messages.UnusedVariable, binding.source, name) @@ -633,3 +696,5 @@ def IMPORTFROM(self, node): if node.module == '__future__': importation.used = (self.scope, node) self.addBinding(node, importation) + +# lint=0 diff --git a/pylibs/pyflakes/messages.py b/pylibs/pylama/pyflakes/messages.py similarity index 100% rename from pylibs/pyflakes/messages.py rename to pylibs/pylama/pyflakes/messages.py diff --git a/pylibs/pylint/__init__.py b/pylibs/pylama/pylint/__init__.py similarity index 50% rename from pylibs/pylint/__init__.py rename to pylibs/pylama/pylint/__init__.py index 0c4bd134..697c8c05 100644 --- a/pylibs/pylint/__init__.py +++ b/pylibs/pylama/pylint/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# # 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 @@ -10,7 +13,33 @@ # 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., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -""" Copyright (c) 2002-2008 LOGILAB S.A. (Paris, FRANCE). -http://www.logilab.fr/ -- mailto:contact@logilab.fr -""" +import sys + +def run_pylint(): + """run pylint""" + from .lint import Run + Run(sys.argv[1:]) + +def run_pylint_gui(): + """run pylint-gui""" + try: + from pylint.gui import Run + Run(sys.argv[1:]) + except ImportError: + sys.exit('tkinter is not available') + +def run_epylint(): + """run pylint""" + from pylint.epylint import Run + Run() + +def run_pyreverse(): + """run pyreverse""" + from pylint.pyreverse.main import Run + Run(sys.argv[1:]) + +def run_symilar(): + """run symilar""" + from .checkers.similar import Run + Run(sys.argv[1:]) diff --git a/pylibs/pylint/__pkginfo__.py b/pylibs/pylama/pylint/__pkginfo__.py similarity index 93% rename from pylibs/pylint/__pkginfo__.py rename to pylibs/pylama/pylint/__pkginfo__.py index 3f7847b9..5604692c 100644 --- a/pylibs/pylint/__pkginfo__.py +++ b/pylibs/pylama/pylint/__pkginfo__.py @@ -1,5 +1,5 @@ # pylint: disable=W0622,C0103 -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -18,7 +18,7 @@ modname = distname = 'pylint' -numversion = (0, 25, 2) +numversion = (0, 27, 0) version = '.'.join([str(num) for num in numversion]) install_requires = ['logilab-common >= 0.53.0', 'logilab-astng >= 0.21.1'] @@ -38,6 +38,8 @@ 'License :: OSI Approved :: GNU General Public License (GPL)', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Debuggers', 'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Testing', diff --git a/pylibs/pylint/checkers/__init__.py b/pylibs/pylama/pylint/checkers/__init__.py similarity index 91% rename from pylibs/pylint/checkers/__init__.py rename to pylibs/pylama/pylint/checkers/__init__.py index 969066b5..6c98c9eb 100644 --- a/pylibs/pylint/checkers/__init__.py +++ b/pylibs/pylama/pylint/checkers/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -29,6 +29,7 @@ 11: typecheck 12: logging 13: string_format +14: string_constant 14-50: not yet used: reserved for future internal checkers. 51-99: perhaps used: reserved for external checkers @@ -41,10 +42,11 @@ from os import listdir from os.path import dirname, join, isdir, splitext -from logilab.astng.utils import ASTWalker -from logilab.common.configuration import OptionsProviderMixIn +from ..logilab.astng.utils import ASTWalker +from ..logilab.common.modutils import load_module_from_file +from ..logilab.common.configuration import OptionsProviderMixIn -from pylint.reporters import diff_string, EmptyReport +from ..reporters import diff_string, EmptyReport def table_lines_from_stats(stats, old_stats, columns): """get values listed in from and , @@ -100,7 +102,6 @@ def package_dir(self): """return the base directory for the analysed package""" return dirname(self.linter.base_file) - # dummy methods implementing the IChecker interface def open(self): @@ -109,6 +110,7 @@ def open(self): def close(self): """called after visiting project (i.e set of modules)""" + class BaseRawChecker(BaseChecker): """base class for raw checkers""" @@ -138,17 +140,15 @@ def package_load(linter, directory): """load all module and package in the given directory, looking for a 'register' function in each one, used to register pylint checkers """ - globs = globals() imported = {} for filename in listdir(directory): basename, extension = splitext(filename) if basename in imported or basename == '__pycache__': continue if extension in PY_EXTS and basename != '__init__' or ( - not extension and basename != 'CVS' and - isdir(join(directory, basename))): + not extension and isdir(join(directory, basename))): try: - module = __import__(basename, globs, globs, None) + module = load_module_from_file(join(directory, filename)) except ValueError: # empty module name (usually emacs auto-save files) continue @@ -160,4 +160,4 @@ def package_load(linter, directory): module.register(linter) imported[basename] = 1 -__all__ = ('CheckerHandler', 'BaseChecker', 'initialize', 'package_load') +__all__ = ('BaseChecker', 'initialize', 'package_load') diff --git a/pylibs/pylint/checkers/base.py b/pylibs/pylama/pylint/checkers/base.py similarity index 91% rename from pylibs/pylint/checkers/base.py rename to pylibs/pylama/pylint/checkers/base.py index 5de570ff..e85dba5a 100644 --- a/pylibs/pylint/checkers/base.py +++ b/pylibs/pylama/pylint/checkers/base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). # Copyright (c) 2009-2010 Arista Networks, Inc. # http://www.logilab.fr/ -- mailto:contact@logilab.fr # This program is free software; you can redistribute it and/or modify it under @@ -16,14 +16,19 @@ """basic checker for Python code""" -from logilab import astng -from logilab.common.ureports import Table -from logilab.astng import are_exclusive +from ..logilab import astng +from ..logilab.common.ureports import Table +from ..logilab.astng import are_exclusive -from pylint.interfaces import IASTNGChecker -from pylint.reporters import diff_string -from pylint.checkers import BaseChecker, EmptyReport -from pylint.checkers.utils import check_messages, clobber_in_except, is_inside_except +from ..interfaces import IASTNGChecker +from ..reporters import diff_string +from ..checkers import BaseChecker, EmptyReport +from ..checkers.utils import ( + check_messages, + clobber_in_except, + is_inside_except, + safe_infer, + ) import re @@ -124,27 +129,35 @@ class _BasicChecker(BaseChecker): class BasicErrorChecker(_BasicChecker): msgs = { 'E0100': ('__init__ method is a generator', + 'init-is-generator', 'Used when the special class method __init__ is turned into a ' 'generator by a yield in its body.'), 'E0101': ('Explicit return in __init__', + 'return-in-init', 'Used when the special class method __init__ has an explicit \ return value.'), 'E0102': ('%s already defined line %s', + 'function-redefined', 'Used when a function / class / method is redefined.'), 'E0103': ('%r not properly in loop', + 'not-in-loop', 'Used when break or continue keywords are used outside a loop.'), 'E0104': ('Return outside function', + 'return-outside-function', 'Used when a "return" statement is found outside a function or ' 'method.'), 'E0105': ('Yield outside function', + 'yield-outside-function', 'Used when a "yield" statement is found outside a function or ' 'method.'), 'E0106': ('Return with argument inside generator', + 'return-arg-in-generator', 'Used when a "return" statement with an argument is found ' 'outside in a generator function or method (e.g. with some ' '"yield" statements).'), 'E0107': ("Use of the non-existent %s operator", + 'nonexistent-operator', "Used when you attempt to use the C-style pre-increment or" "pre-decrement operator -- and ++, which doesn't exist in Python."), } @@ -242,54 +255,67 @@ class BasicChecker(_BasicChecker): name = 'basic' msgs = { 'W0101': ('Unreachable code', + 'unreachable', 'Used when there is some code behind a "return" or "raise" \ statement, which will never be accessed.'), 'W0102': ('Dangerous default value %s as argument', + 'dangerous-default-value', 'Used when a mutable value as list or dictionary is detected in \ a default value for an argument.'), 'W0104': ('Statement seems to have no effect', + 'pointless-statement', 'Used when a statement doesn\'t have (or at least seems to) \ any effect.'), 'W0105': ('String statement has no effect', + 'pointless-string-statement', 'Used when a string is used as a statement (which of course \ has no effect). This is a particular case of W0104 with its \ own message so you can easily disable it if you\'re using \ those strings as documentation, instead of comments.'), 'W0106': ('Expression "%s" is assigned to nothing', + 'expression-not-assigned', 'Used when an expression that is not a function call is assigned\ to nothing. Probably something else was intended.'), 'W0108': ('Lambda may not be necessary', + 'unnecessary-lambda', 'Used when the body of a lambda expression is a function call \ on the same argument list as the lambda itself; such lambda \ expressions are in all but a few cases replaceable with the \ function being called in the body of the lambda.'), 'W0109': ("Duplicate key %r in dictionary", + 'duplicate-key', "Used when a dictionary expression binds the same key multiple \ times."), 'W0122': ('Use of the exec statement', + 'exec-statement', 'Used when you use the "exec" statement, to discourage its \ usage. That doesn\'t mean you can not use it !'), 'W0141': ('Used builtin function %r', + 'bad-builtin', 'Used when a black listed builtin function is used (see the ' 'bad-function option). Usual black listed functions are the ones ' 'like map, or filter , where Python offers now some cleaner ' 'alternative like list comprehension.'), 'W0142': ('Used * or ** magic', + 'star-args', 'Used when a function or method is called using `*args` or ' '`**kwargs` to dispatch arguments. This doesn\'t improve ' 'readability and should be used with care.'), 'W0150': ("%s statement in finally block may swallow exception", + 'lost-exception', "Used when a break or a return statement is found inside the \ finally clause of a try...finally block: the exceptions raised \ in the try clause will be silently swallowed instead of being \ re-raised."), 'W0199': ('Assert called on a 2-uple. Did you mean \'assert x,y\'?', + 'assert-on-tuple', 'A call of assert on a tuple will always evaluate to true if ' 'the tuple is not empty, and will always evaluate to false if ' 'it is.'), 'C0121': ('Missing required attribute "%s"', # W0103 + 'missing-module-attribute', 'Used when an attribute required for modules is missing.'), } @@ -508,13 +534,13 @@ def visit_assert(self, node): """check the use of an assert statement on a tuple.""" if node.fail is None and isinstance(node.test, astng.Tuple) and \ len(node.test.elts) == 2: - self.add_message('W0199', line=node.fromlineno, node=node) + self.add_message('W0199', line=node.fromlineno, node=node) @check_messages('W0109') def visit_dict(self, node): """check duplicate key in dictionary""" keys = set() - for k, v in node.items: + for k, _ in node.items: if isinstance(k, astng.Const): key = k.value if key in keys: @@ -558,9 +584,11 @@ def _check_not_in_finally(self, node, node_name, breaker_classes=()): class NameChecker(_BasicChecker): msgs = { 'C0102': ('Black listed name "%s"', + 'blacklisted-name', 'Used when the name is listed in the black list (unauthorized \ names).'), - 'C0103': ('Invalid name "%s" (should match %s)', + 'C0103': ('Invalid name "%s" for type %s (should match %s)', + 'invalid-name', 'Used when the name doesn\'t match the regular expression \ associated to its type (constant, variable, class...).'), @@ -650,7 +678,7 @@ def visit_module(self, node): @check_messages('C0102', 'C0103') def visit_class(self, node): self._check_name('class', node.name, node) - for attr, anodes in node.instance_attrs.items(): + for attr, anodes in node.instance_attrs.iteritems(): self._check_name('attr', attr, anodes[0]) @check_messages('C0102', 'C0103') @@ -693,7 +721,6 @@ def _check_name(self, node_type, name, node): clobbering, _ = clobber_in_except(node) if clobbering: return - if name in self.config.good_names: return if name in self.config.bad_names: @@ -702,17 +729,23 @@ def _check_name(self, node_type, name, node): return regexp = getattr(self.config, node_type + '_rgx') if regexp.match(name) is None: - self.add_message('C0103', node=node, args=(name, regexp.pattern)) + type_label = {'inlinedvar': 'inlined variable', + 'const': 'constant', + 'attr': 'attribute', + }.get(node_type, node_type) + self.add_message('C0103', node=node, args=(name, type_label, regexp.pattern)) self.stats['badname_' + node_type] += 1 class DocStringChecker(_BasicChecker): msgs = { 'C0111': ('Missing docstring', # W0131 + 'missing-docstring', 'Used when a module, function, class or method has no docstring.\ Some special methods like __init__ doesn\'t necessary require a \ docstring.'), 'C0112': ('Empty docstring', # W0132 + 'empty-docstring', 'Used when a module, function, class or method has an empty \ docstring (it would be too easy ;).'), } @@ -768,8 +801,9 @@ def _check_docstring(self, node_type, node): class PassChecker(_BasicChecker): """check is the pass statement is really necessary""" msgs = {'W0107': ('Unnecessary pass statement', + 'unnecessary-pass', 'Used when a "pass" statement that can be avoided is ' - 'encountered.)'), + 'encountered.'), } def visit_pass(self, node): @@ -777,6 +811,36 @@ def visit_pass(self, node): self.add_message('W0107', node=node) +class LambdaForComprehensionChecker(_BasicChecker): + """check for using a lambda where a comprehension would do. + + See + where GvR says comprehensions would be clearer. + """ + + msgs = {'W0110': ('map/filter on lambda could be replaced by comprehension', + 'deprecated-lambda', + 'Used when a lambda is the first argument to "map" or ' + '"filter". It could be clearer as a list ' + 'comprehension or generator expression.'), + } + + @check_messages('W0110') + def visit_callfunc(self, node): + """visit a CallFunc node, check if map or filter are called with a + lambda + """ + if not node.args: + return + if not isinstance(node.args[0], astng.Lambda): + return + infered = safe_infer(node.func) + if (infered + and infered.parent.name == '__builtin__' + and infered.name in ['map', 'filter']): + self.add_message('W0110', node=node) + + def register(linter): """required method to auto register this checker""" linter.register_checker(BasicErrorChecker(linter)) @@ -784,3 +848,4 @@ def register(linter): linter.register_checker(NameChecker(linter)) linter.register_checker(DocStringChecker(linter)) linter.register_checker(PassChecker(linter)) + linter.register_checker(LambdaForComprehensionChecker(linter)) diff --git a/pylibs/pylint/checkers/classes.py b/pylibs/pylama/pylint/checkers/classes.py similarity index 82% rename from pylibs/pylint/checkers/classes.py rename to pylibs/pylama/pylint/checkers/classes.py index 716d0a92..8e3b836d 100644 --- a/pylibs/pylint/checkers/classes.py +++ b/pylibs/pylama/pylint/checkers/classes.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2011 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -17,12 +17,12 @@ """ from __future__ import generators -from logilab import astng -from logilab.astng import YES, Instance, are_exclusive, AssAttr +from ..logilab import astng +from ..logilab.astng import YES, Instance, are_exclusive, AssAttr -from pylint.interfaces import IASTNGChecker -from pylint.checkers import BaseChecker -from pylint.checkers.utils import (PYMETHODS, overrides_a_method, +from ..interfaces import IASTNGChecker +from ..checkers import BaseChecker +from ..checkers.utils import (PYMETHODS, overrides_a_method, check_messages, is_attr_private, is_attr_protected, node_frame_class) def class_is_abstract(node): @@ -38,77 +38,108 @@ def class_is_abstract(node): MSGS = { 'F0202': ('Unable to check methods signature (%s / %s)', + 'method-check-failed', 'Used when PyLint has been unable to check methods signature \ compatibility for an unexpected reason. Please report this kind \ if you don\'t make sense of it.'), 'E0202': ('An attribute affected in %s line %s hide this method', + 'method-hidden', 'Used when a class defines a method which is hidden by an ' 'instance attribute from an ancestor class or set by some ' 'client code.'), 'E0203': ('Access to member %r before its definition line %s', + 'access-member-before-definition', 'Used when an instance member is accessed before it\'s actually\ assigned.'), 'W0201': ('Attribute %r defined outside __init__', + 'attribute-defined-outside-init', 'Used when an instance attribute is defined outside the __init__\ method.'), 'W0212': ('Access to a protected member %s of a client class', # E0214 + 'protected-access', 'Used when a protected member (i.e. class member with a name \ beginning with an underscore) is access outside the class or a \ descendant of the class where it\'s defined.'), 'E0211': ('Method has no argument', + 'no-method-argument', 'Used when a method which should have the bound instance as \ first argument has no argument defined.'), 'E0213': ('Method should have "self" as first argument', + 'no-self-argument', 'Used when a method has an attribute different the "self" as\ first argument. This is considered as an error since this is\ a so common convention that you shouldn\'t break it!'), - 'C0202': ('Class method should have %s as first argument', # E0212 - 'Used when a class method has an attribute different than "cls"\ - as first argument, to easily differentiate them from regular \ - instance methods.'), - 'C0203': ('Metaclass method should have "mcs" as first argument', # E0214 - 'Used when a metaclass method has an attribute different the \ - "mcs" as first argument.'), + 'C0202': ('Class method %s should have %s as first argument', # E0212 + 'bad-classmethod-argument', + 'Used when a class method has a first argument named differently ' + 'than the value specified in valid-classmethod-first-arg option ' + '(default to "cls"), recommended to easily differentiate them ' + 'from regular instance methods.'), + 'C0203': ('Metaclass method %s should have %s as first argument', # E0214 + 'bad-mcs-method-argument', + 'Used when a metaclass method has a first agument named ' + 'differently than the value specified in valid-classmethod-first' + '-arg option (default to "cls"), recommended to easily ' + 'differentiate them from regular instance methods.'), + 'C0204': ('Metaclass class method %s should have %s as first argument', + 'bad-mcs-classmethod-argument', + 'Used when a metaclass class method has a first argument named ' + 'differently than the value specified in valid-metaclass-' + 'classmethod-first-arg option (default to "mcs"), recommended to ' + 'easily differentiate them from regular instance methods.'), 'W0211': ('Static method with %r as first argument', - 'Used when a static method has "self" or "cls" as first argument.' + 'bad-staticmethod-argument', + 'Used when a static method has "self" or a value specified in ' + 'valid-classmethod-first-arg option or ' + 'valid-metaclass-classmethod-first-arg option as first argument.' ), 'R0201': ('Method could be a function', + 'no-self-use', 'Used when a method doesn\'t use its bound instance, and so could\ be written as a function.' ), 'E0221': ('Interface resolved to %s is not a class', + 'interface-is-not-class', 'Used when a class claims to implement an interface which is not \ a class.'), 'E0222': ('Missing method %r from %s interface', + 'missing-interface-method', 'Used when a method declared in an interface is missing from a \ class implementing this interface'), 'W0221': ('Arguments number differs from %s method', + 'arguments-differ', 'Used when a method has a different number of arguments than in \ the implemented interface or in an overridden method.'), 'W0222': ('Signature differs from %s method', + 'signature-differs', 'Used when a method signature is different than in the \ implemented interface or in an overridden method.'), 'W0223': ('Method %r is abstract in class %r but is not overridden', + 'abstract-method', 'Used when an abstract method (i.e. raise NotImplementedError) is \ not overridden in concrete class.' ), 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224 + 'unresolved-interface', 'Used when a PyLint as failed to find interfaces implemented by \ a class'), 'W0231': ('__init__ method from base class %r is not called', + 'super-init-not-called', 'Used when an ancestor class method has an __init__ method \ which is not called by a derived class.'), 'W0232': ('Class has no __init__ method', + 'no-init', 'Used when a class has no __init__ method, neither its parent \ classes.'), 'W0233': ('__init__ method from a non direct base class %r is called', + 'non-parent-init-called', 'Used when an __init__ method is called on a class which is not \ in the direct ancestors for the analysed class.'), @@ -165,6 +196,13 @@ class ClassChecker(BaseChecker): 'help' : 'List of valid names for the first argument in \ a class method.'} ), + ('valid-metaclass-classmethod-first-arg', + {'default' : ('mcs',), + 'type' : 'csv', + 'metavar' : '', + 'help' : 'List of valid names for the first argument in \ +a metaclass class method.'} + ), ) @@ -201,7 +239,7 @@ def leave_class(self, cnode): if 'W0201' not in self.active_msgs: return defining_methods = self.config.defining_attr_methods - for attr, nodes in cnode.instance_attrs.items(): + for attr, nodes in cnode.instance_attrs.iteritems(): nodes = [n for n in nodes if not isinstance(n.statement(), (astng.Delete, astng.AugAssign))] if not nodes: @@ -348,10 +386,15 @@ def _check_protected_attribute_access(self, node): self.add_message('W0212', node=node, args=attrname) return + # If the expression begins with a call to super, that's ok. + if isinstance(node.expr, astng.CallFunc) and \ + isinstance(node.expr.func, astng.Name) and \ + node.expr.func.name == 'super': + return + # We are in a class, one remaining valid cases, Klass._attr inside # Klass if not (callee == klass.name or callee in klass.basenames): - self.add_message('W0212', node=node, args=attrname) def visit_name(self, node): @@ -365,7 +408,7 @@ def visit_name(self, node): def _check_accessed_members(self, node, accessed): """check that accessed members are defined""" # XXX refactor, probably much simpler now that E0201 is in type checker - for attr, nodes in accessed.items(): + for attr, nodes in accessed.iteritems(): # deactivate "except doesn't do anything", that's expected # pylint: disable=W0704 # is it a class attribute ? @@ -404,8 +447,10 @@ def _check_first_arg_for_type(self, node, metaclass=0): """check the name of first argument, expect: * 'self' for a regular method - * 'cls' for a class method - * 'mcs' for a metaclass + * 'cls' for a class method or a metaclass regular method (actually + valid-classmethod-first-arg value) + * 'mcs' for a metaclass class method (actually + valid-metaclass-classmethod-first-arg) * not one of the above for a static method """ # don't care about functions with unknown argument (builtins) @@ -416,31 +461,50 @@ def _check_first_arg_for_type(self, node, metaclass=0): first = self._first_attrs[-1] # static method if node.type == 'staticmethod': - if first_arg in ('self', 'cls', 'mcs'): + if (first_arg == 'self' or + first_arg in self.config.valid_classmethod_first_arg or + first_arg in self.config.valid_metaclass_classmethod_first_arg): self.add_message('W0211', args=first, node=node) + return self._first_attrs[-1] = None # class / regular method with no args elif not node.args.args: self.add_message('E0211', node=node) - # metaclass method + # metaclass elif metaclass: - if first != 'mcs': - self.add_message('C0203', node=node) - # class method - elif node.type == 'classmethod': - if first not in self.config.valid_classmethod_first_arg: - if len(self.config.valid_classmethod_first_arg) == 1: - valid = repr(self.config.valid_classmethod_first_arg[0]) - else: - valid = ', '.join( - repr(v) - for v in self.config.valid_classmethod_first_arg[:-1]) - valid = '%s or %r' % ( - valid, self.config.valid_classmethod_first_arg[-1]) - self.add_message('C0202', args=valid, node=node) - # regular method without self as argument - elif first != 'self': - self.add_message('E0213', node=node) + # metaclass __new__ or classmethod + if node.type == 'classmethod': + self._check_first_arg_config(first, + self.config.valid_metaclass_classmethod_first_arg, node, + 'C0204', node.name) + # metaclass regular method + else: + self._check_first_arg_config(first, + self.config.valid_classmethod_first_arg, node, 'C0203', + node.name) + # regular class + else: + # class method + if node.type == 'classmethod': + self._check_first_arg_config(first, + self.config.valid_classmethod_first_arg, node, 'C0202', + node.name) + # regular method without self as argument + elif first != 'self': + self.add_message('E0213', node=node) + + def _check_first_arg_config(self, first, config, node, message, + method_name): + if first not in config: + if len(config) == 1: + valid = repr(config[0]) + else: + valid = ', '.join( + repr(v) + for v in config[:-1]) + valid = '%s or %r' % ( + valid, config[-1]) + self.add_message(message, args=(method_name, valid), node=node) def _check_bases_classes(self, node): """check that the given class node implements abstract methods from @@ -537,8 +601,8 @@ def _check_init(self, node): self.add_message('W0233', node=expr, args=klass.name) except astng.InferenceError: continue - for klass in not_called_yet.keys(): - if klass.name == 'object': + for klass, method in not_called_yet.iteritems(): + if klass.name == 'object' or method.parent.name == 'object': continue self.add_message('W0231', args=klass.name, node=node) @@ -578,9 +642,8 @@ def _ancestors_to_call(klass_node, method='__init__'): to_call = {} for base_node in klass_node.ancestors(recurs=False): try: - base_node.local_attr(method) - to_call[base_node] = 1 - except astng.NotFoundError: + to_call[base_node] = base_node.igetattr(method).next() + except astng.InferenceError: continue return to_call diff --git a/pylibs/pylint/checkers/design_analysis.py b/pylibs/pylama/pylint/checkers/design_analysis.py similarity index 80% rename from pylibs/pylint/checkers/design_analysis.py rename to pylibs/pylama/pylint/checkers/design_analysis.py index 0deb6c79..789cf88f 100644 --- a/pylibs/pylint/checkers/design_analysis.py +++ b/pylibs/pylama/pylint/checkers/design_analysis.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -20,16 +20,54 @@ FIXME: missing 13, 15, 16 """ -from logilab.astng import Function, If, InferenceError +from ..logilab.astng import Function, If, InferenceError -from pylint.interfaces import IASTNGChecker -from pylint.checkers import BaseChecker +from ..interfaces import IASTNGChecker +from ..checkers import BaseChecker import re # regexp for ignored argument name IGNORED_ARGUMENT_NAMES = re.compile('_.*') +SPECIAL_METHODS = [('Context manager', set(('__enter__', + '__exit__',))), + ('Container', set(('__len__', + '__getitem__', + '__setitem__', + '__delitem__',))), + ('Callable', set(('__call__',))), + ] + +class SpecialMethodChecker(object): + """A functor that checks for consistency of a set of special methods""" + def __init__(self, methods_found, on_error): + """Stores the set of __x__ method names that were found in the + class and a callable that will be called with args to R0024 if + the check fails + """ + self.methods_found = methods_found + self.on_error = on_error + + def __call__(self, methods_required, protocol): + """Checks the set of method names given to __init__ against the set + required. + + If they are all present, returns true. + If they are all absent, returns false. + If some are present, reports the error and returns false. + """ + required_methods_found = methods_required & self.methods_found + if required_methods_found == methods_required: + return True + if required_methods_found != set(): + required_methods_missing = methods_required - self.methods_found + self.on_error((protocol, + ', '.join(sorted(required_methods_found)), + ', '.join(sorted(required_methods_missing)))) + return False + + def class_is_abstract(klass): """return true if the given class node should be considered as an abstract class @@ -43,48 +81,64 @@ def class_is_abstract(klass): MSGS = { 'R0901': ('Too many ancestors (%s/%s)', + 'too-many-ancestors', 'Used when class has too many parent classes, try to reduce \ this to get a more simple (and so easier to use) class.'), 'R0902': ('Too many instance attributes (%s/%s)', + 'too-many-instance-attributes', 'Used when class has too many instance attributes, try to reduce \ this to get a more simple (and so easier to use) class.'), 'R0903': ('Too few public methods (%s/%s)', + 'too-few-public-methods', 'Used when class has too few public methods, so be sure it\'s \ really worth it.'), 'R0904': ('Too many public methods (%s/%s)', + 'too-many-public-methods', 'Used when class has too many public methods, try to reduce \ this to get a more simple (and so easier to use) class.'), - + 'R0911': ('Too many return statements (%s/%s)', + 'too-many-return-statements', 'Used when a function or method has too many return statement, \ making it hard to follow.'), 'R0912': ('Too many branches (%s/%s)', + 'too-many-branches', 'Used when a function or method has too many branches, \ making it hard to follow.'), 'R0913': ('Too many arguments (%s/%s)', + 'too-many-arguments', 'Used when a function or method takes too many arguments.'), 'R0914': ('Too many local variables (%s/%s)', + 'too-many-locals', 'Used when a function or method has too many local variables.'), 'R0915': ('Too many statements (%s/%s)', + 'too-many-statements', 'Used when a function or method has too many statements. You \ should then split it in smaller functions / methods.'), - + 'R0921': ('Abstract class not referenced', + 'abstract-class-not-used', 'Used when an abstract class is not used as ancestor anywhere.'), 'R0922': ('Abstract class is only referenced %s times', + 'abstract-class-little-used', 'Used when an abstract class is used less than X times as \ ancestor.'), 'R0923': ('Interface not implemented', + 'interface-not-implemented', 'Used when an interface class is not implemented anywhere.'), + 'R0924': ('Badly implemented %s, implements %s but not %s', + 'incomplete-protocol', + 'A class implements some of the special methods for a particular \ + protocol, but not all of them') } class MisdesignChecker(BaseChecker): - """checks for sign of poor/misdesign: - * number of methods, attributes, local variables... - * size, complexity of functions, methods + """checks for sign of poor/misdesign: + * number of methods, attributes, local variables... + * size, complexity of functions, methods """ - + __implements__ = (IASTNGChecker,) # configuration section name @@ -160,7 +214,7 @@ def __init__(self, linter=None): self._abstracts = None self._ifaces = None self._stmts = 0 - + def open(self): """initialize visit variables""" self.stats = self.linter.add_stats() @@ -182,7 +236,7 @@ def close(self): for iface in self._ifaces: if not iface in self._used_ifaces: self.add_message('R0923', node=iface) - + def visit_class(self, node): """check size of inheritance hierarchy and number of instance attributes """ @@ -212,20 +266,23 @@ def visit_class(self, node): for iface in node.interfaces(): self._used_ifaces[iface] = 1 except InferenceError: - # XXX log ? + # XXX log ? pass for parent in node.ancestors(): try: self._used_abstracts[parent] += 1 except KeyError: self._used_abstracts[parent] = 1 - + def leave_class(self, node): """check number of public methods""" nb_public_methods = 0 + special_methods = set() for method in node.methods(): if not method.name.startswith('_'): nb_public_methods += 1 + if method.name.startswith("__"): + special_methods.add(method.name) # Does the class contain less than 20 public methods ? if nb_public_methods > self.config.max_public_methods: self.add_message('R0904', node=node, @@ -234,13 +291,19 @@ def leave_class(self, node): # stop here for exception, metaclass and interface classes if node.type != 'class': return + # Does the class implement special methods consitently? + # If so, don't enforce minimum public methods. + check_special = SpecialMethodChecker( + special_methods, lambda args: self.add_message('R0924', node=node, args=args)) + protocols = [check_special(pmethods, pname) for pname, pmethods in SPECIAL_METHODS] + if True in protocols: + return # Does the class contain more than 5 public methods ? if nb_public_methods < self.config.min_public_methods: self.add_message('R0903', node=node, args=(nb_public_methods, self.config.min_public_methods)) - def visit_function(self, node): """check function name, docstring, arguments, redefinition, variable names, max locals @@ -291,7 +354,7 @@ def visit_return(self, _): if not self._returns: return # return outside function, reported by the base checker self._returns[-1] += 1 - + def visit_default(self, node): """default visit method -> increments the statements counter if necessary @@ -306,12 +369,12 @@ def visit_tryexcept(self, node): branchs += 1 self._inc_branch(branchs) self._stmts += branchs - + def visit_tryfinally(self, _): """increments the branchs counter""" self._inc_branch(2) self._stmts += 2 - + def visit_if(self, node): """increments the branchs counter""" branchs = 1 @@ -321,14 +384,14 @@ def visit_if(self, node): branchs += 1 self._inc_branch(branchs) self._stmts += branchs - + def visit_while(self, node): """increments the branchs counter""" branchs = 1 if node.orelse: branchs += 1 self._inc_branch(branchs) - + visit_for = visit_while def _inc_branch(self, branchsnum=1): diff --git a/pylibs/pylint/checkers/exceptions.py b/pylibs/pylama/pylint/checkers/exceptions.py similarity index 84% rename from pylibs/pylint/checkers/exceptions.py rename to pylibs/pylama/pylint/checkers/exceptions.py index 08f4334c..b2006cef 100644 --- a/pylibs/pylint/checkers/exceptions.py +++ b/pylibs/pylama/pylint/checkers/exceptions.py @@ -16,48 +16,61 @@ """ import sys -from logilab.common.compat import builtins +from ..logilab.common.compat import builtins BUILTINS_NAME = builtins.__name__ -from logilab import astng -from logilab.astng import YES, Instance, unpack_infer +from ..logilab import astng +from ..logilab.astng import YES, Instance, unpack_infer -from pylint.checkers import BaseChecker -from pylint.checkers.utils import is_empty, is_raising -from pylint.interfaces import IASTNGChecker +from ..checkers import BaseChecker +from ..checkers.utils import is_empty, is_raising +from ..interfaces import IASTNGChecker OVERGENERAL_EXCEPTIONS = ('Exception',) MSGS = { - 'E0701': ( - 'Bad except clauses order (%s)', - 'Used when except clauses are not in the correct order (from the \ - more specific to the more generic). If you don\'t fix the order, \ - some exceptions may not be catched by the most specific handler.'), + 'E0701': ('Bad except clauses order (%s)', + 'bad-except-order', + 'Used when except clauses are not in the correct order (from the ' + 'more specific to the more generic). If you don\'t fix the order, ' + 'some exceptions may not be catched by the most specific handler.'), 'E0702': ('Raising %s while only classes, instances or string are allowed', + 'raising-bad-type', 'Used when something which is neither a class, an instance or a \ string is raised (i.e. a `TypeError` will be raised).'), 'E0710': ('Raising a new style class which doesn\'t inherit from BaseException', + 'raising-non-exception', 'Used when a new style class which doesn\'t inherit from \ BaseException is raised.'), 'E0711': ('NotImplemented raised - should raise NotImplementedError', + 'notimplemented-raised', 'Used when NotImplemented is raised instead of \ NotImplementedError'), - + 'W0701': ('Raising a string exception', + 'raising-string', 'Used when a string exception is raised.'), 'W0702': ('No exception type(s) specified', + 'bare-except', 'Used when an except clause doesn\'t specify exceptions type to \ catch.'), 'W0703': ('Catching too general exception %s', + 'broad-except', 'Used when an except catches a too general exception, \ possibly burying unrelated errors.'), 'W0704': ('Except doesn\'t do anything', + 'pointless-except', 'Used when an except clause does nothing but "pass" and there is\ no "else" clause.'), 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', + 'nonstandard-exception', 'Used when a custom exception class is raised but doesn\'t \ inherit from the builtin "Exception" class.'), + 'W0711': ('Exception to catch is the result of a binary "%s" operation', + 'binary-op-exception', + 'Used when the exception to catch is of the form \ + "except A or B:". If intending to catch multiple, \ + rewrite as "except (A, B):"'), } @@ -67,11 +80,11 @@ EXCEPTIONS_MODULE = "builtins" class ExceptionsChecker(BaseChecker): - """checks for - * excepts without exception filter + """checks for + * excepts without exception filter * type of raise argument : string, Exceptions, other values """ - + __implements__ = IASTNGChecker name = 'exceptions' @@ -114,7 +127,7 @@ def _check_raise_value(self, node, expr): args=value.__class__.__name__) elif (isinstance(expr, astng.Name) and \ expr.name in ('None', 'True', 'False')) or \ - isinstance(expr, (astng.List, astng.Dict, astng.Tuple, + isinstance(expr, (astng.List, astng.Dict, astng.Tuple, astng.Module, astng.Function)): self.add_message('E0702', node=node, args=expr.name) elif ( (isinstance(expr, astng.Name) and expr.name == 'NotImplemented') @@ -157,13 +170,16 @@ def visit_tryexcept(self, node): elif index < (nb_handlers - 1): msg = 'empty except clause should always appear last' self.add_message('E0701', node=node, args=msg) + + elif isinstance(handler.type, astng.BoolOp): + self.add_message('W0711', node=handler, args=handler.type.op) else: try: excs = list(unpack_infer(handler.type)) except astng.InferenceError: continue for exc in excs: - # XXX skip other non class nodes + # XXX skip other non class nodes if exc is YES or not isinstance(exc, astng.Class): continue exc_ancestors = [anc for anc in exc.ancestors() diff --git a/pylibs/pylint/checkers/format.py b/pylibs/pylama/pylint/checkers/format.py similarity index 71% rename from pylibs/pylint/checkers/format.py rename to pylibs/pylama/pylint/checkers/format.py index 0784e6af..8e165cb8 100644 --- a/pylibs/pylint/checkers/format.py +++ b/pylibs/pylama/pylint/checkers/format.py @@ -1,5 +1,7 @@ # Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). -# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright 2012 Google Inc. +# # 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 @@ -26,37 +28,46 @@ if not hasattr(tokenize, 'NL'): raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") -from logilab.common.textutils import pretty_match -from logilab.astng import nodes +from ..logilab.common.textutils import pretty_match +from ..logilab.astng import nodes -from pylint.interfaces import IRawChecker, IASTNGChecker -from pylint.checkers import BaseRawChecker -from pylint.checkers.utils import check_messages +from ..interfaces import IRawChecker, IASTNGChecker +from ..checkers import BaseRawChecker +from ..checkers.utils import check_messages MSGS = { 'C0301': ('Line too long (%s/%s)', + 'line-too-long', 'Used when a line is longer than a given number of characters.'), 'C0302': ('Too many lines in module (%s)', # was W0302 + 'too-many-lines', 'Used when a module has too much lines, reducing its readability.' ), 'W0311': ('Bad indentation. Found %s %s, expected %s', + 'bad-indentation', 'Used when an unexpected number of indentation\'s tabulations or ' 'spaces has been found.'), 'W0312': ('Found indentation with %ss instead of %ss', + 'mixed-indentation', 'Used when there are some mixed tabs and spaces in a module.'), 'W0301': ('Unnecessary semicolon', # was W0106 + 'unnecessary-semicolon', 'Used when a statement is ended by a semi-colon (";"), which \ isn\'t necessary (that\'s python, not C ;).'), 'C0321': ('More than one statement on a single line', + 'multiple-statements', 'Used when more than on statement are found on the same line.'), 'C0322': ('Operator not preceded by a space\n%s', + 'no-space-before-operator', 'Used when one of the following operator (!= | <= | == | >= | < ' - '| > | = | \+= | -= | \*= | /= | %) is not preceded by a space.'), + '| > | = | \\+= | -= | \\*= | /= | %) is not preceded by a space.'), 'C0323': ('Operator not followed by a space\n%s', + 'no-space-after-operator', 'Used when one of the following operator (!= | <= | == | >= | < ' - '| > | = | \+= | -= | \*= | /= | %) is not followed by a space.'), + '| > | = | \\+= | -= | \\*= | /= | %) is not followed by a space.'), 'C0324': ('Comma not followed by a space\n%s', + 'no-space-after-comma', 'Used when a comma (",") is not followed by a space.'), } @@ -64,13 +75,16 @@ MSGS.update({ 'W0331': ('Use of the <> operator', + 'old-ne-operator', 'Used when the deprecated "<>" operator is used instead \ of "!=".'), - 'W0332': ('Use l as long integer identifier', + 'W0332': ('Use of "l" as long integer identifier', + 'lowercase-l-suffix', 'Used when a lower case "l" is used to mark a long integer. You ' 'should use a upper case "L" since the letter "l" looks too much ' 'like the digit "1"'), 'W0333': ('Use of the `` operator', + 'backtick', 'Used when the deprecated "``" (backtick) operator is used ' 'instead of the str() function.'), }) @@ -81,7 +95,7 @@ SASTRING_RGX = r"'([^'\\]|\\.)*?'" # triple quoted string rgx TQSTRING_RGX = r'"""([^"]|("(?!"")))*?(""")' -# triple apostrophed string rgx # FIXME english please +# triple apostrophe'd string rgx TASTRING_RGX = r"'''([^']|('(?!'')))*?(''')" # finally, the string regular expression @@ -109,11 +123,12 @@ re.compile(OP_RGX_SEARCH_2, re.M), 'C0323'), - (re.compile(r'.*,[^(\s|\]|}|\))].*', re.M), + (re.compile(r'.*,[^(\s|\]|}|\))].*', re.M), re.compile(r',[^\s)]', re.M), 'C0324'), ) +_PY3K = sys.version_info >= (3, 0) def get_string_coords(line): """return a list of string positions (tuple (start, end)) in the line @@ -356,6 +371,106 @@ def check_indent_level(self, string, expected, line_num): expected * unit_size)) +class StringConstantChecker(BaseRawChecker): + """Check string literals""" + + msgs = { + 'W1401': ('Anomalous backslash in string: \'%s\'. ' + 'String constant might be missing an r prefix.', + 'anomalous-backslash-in-string', + 'Used when a backslash is in a literal string but not as an ' + 'escape.'), + 'W1402': ('Anomalous Unicode escape in byte string: \'%s\'. ' + 'String constant might be missing an r or u prefix.', + 'anomalous-unicode-escape-in-string', + 'Used when an escape like \\u is encountered in a byte ' + 'string where it has no effect.'), + } + name = 'string_constant' + __implements__ = (IRawChecker, IASTNGChecker) + + # Characters that have a special meaning after a backslash in either + # Unicode or byte strings. + ESCAPE_CHARACTERS = 'abfnrtvx\n\r\t\\\'\"01234567' + + # TODO(mbp): Octal characters are quite an edge case today; people may + # prefer a separate warning where they occur. \0 should be allowed. + + # Characters that have a special meaning after a backslash but only in + # Unicode strings. + UNICODE_ESCAPE_CHARACTERS = 'uUN' + + def process_tokens(self, tokens): + for (tok_type, token, (start_row, start_col), _, _) in tokens: + if tok_type == tokenize.STRING: + # 'token' is the whole un-parsed token; we can look at the start + # of it to see whether it's a raw or unicode string etc. + self.process_string_token(token, start_row, start_col) + + def process_string_token(self, token, start_row, start_col): + for i, c in enumerate(token): + if c in '\'\"': + quote_char = c + break + prefix = token[:i].lower() # markers like u, b, r. + after_prefix = token[i:] + if after_prefix[:3] == after_prefix[-3:] == 3 * quote_char: + string_body = after_prefix[3:-3] + else: + string_body = after_prefix[1:-1] # Chop off quotes + # No special checks on raw strings at the moment. + if 'r' not in prefix: + self.process_non_raw_string_token(prefix, string_body, + start_row, start_col) + + def process_non_raw_string_token(self, prefix, string_body, start_row, + start_col): + """check for bad escapes in a non-raw string. + + prefix: lowercase string of eg 'ur' string prefix markers. + string_body: the un-parsed body of the string, not including the quote + marks. + start_row: integer line number in the source. + start_col: integer column number in the source. + """ + # Walk through the string; if we see a backslash then escape the next + # character, and skip over it. If we see a non-escaped character, + # alert, and continue. + # + # Accept a backslash when it escapes a backslash, or a quote, or + # end-of-line, or one of the letters that introduce a special escape + # sequence + # + # TODO(mbp): Maybe give a separate warning about the rarely-used + # \a \b \v \f? + # + # TODO(mbp): We could give the column of the problem character, but + # add_message doesn't seem to have a way to pass it through at present. + i = 0 + while True: + i = string_body.find('\\', i) + if i == -1: + break + # There must be a next character; having a backslash at the end + # of the string would be a SyntaxError. + next_char = string_body[i+1] + match = string_body[i:i+2] + if next_char in self.UNICODE_ESCAPE_CHARACTERS: + if 'u' in prefix: + pass + elif _PY3K and 'b' not in prefix: + pass # unicode by default + else: + self.add_message('W1402', line=start_row, args=(match, )) + elif next_char not in self.ESCAPE_CHARACTERS: + self.add_message('W1401', line=start_row, args=(match, )) + # Whether it was a valid escape or not, backslash followed by + # another character can always be consumed whole: the second + # character can never be the start of a new backslash escape. + i += 2 + + def register(linter): """required method to auto register this checker """ linter.register_checker(FormatChecker(linter)) + linter.register_checker(StringConstantChecker(linter)) diff --git a/pylibs/pylint/checkers/imports.py b/pylibs/pylama/pylint/checkers/imports.py similarity index 90% rename from pylibs/pylint/checkers/imports.py rename to pylibs/pylama/pylint/checkers/imports.py index 7e6a4f88..b94e3685 100644 --- a/pylibs/pylint/checkers/imports.py +++ b/pylibs/pylama/pylint/checkers/imports.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -15,33 +15,39 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """imports checkers for Python code""" -from logilab.common.graph import get_cycles, DotBackend -from logilab.common.modutils import is_standard_module -from logilab.common.ureports import VerbatimText, Paragraph +from ..logilab.common.graph import get_cycles, DotBackend +from ..logilab.common.modutils import is_standard_module +from ..logilab.common.ureports import VerbatimText, Paragraph -from logilab import astng -from logilab.astng import are_exclusive +from ..logilab import astng +from ..logilab.astng import are_exclusive -from pylint.interfaces import IASTNGChecker -from pylint.checkers import BaseChecker, EmptyReport +from ..interfaces import IASTNGChecker +from ..checkers import BaseChecker, EmptyReport def get_first_import(node, context, name, base, level): """return the node where [base.] is imported or None if not found """ + fullname = '%s.%s' % (base, name) if base else name + first = None found = False - for first in context.values(): + for first in context.body: + if first is node: + continue + if first.scope() is node.scope() and first.fromlineno > node.fromlineno: + continue if isinstance(first, astng.Import): - if name in [iname[0] for iname in first.names]: + if any(fullname == iname[0] for iname in first.names): found = True break elif isinstance(first, astng.From): - if base == first.modname and level == first.level and \ - name in [iname[0] for iname in first.names]: + if level == first.level and any( + fullname == '%s.%s' % (first.modname, iname[0]) for iname in first.names): found = True break - if found and first is not node and not are_exclusive(first, node): + if found and not are_exclusive(first, node): return first # utilities to represents import dependencies as tree and dot graph ########### @@ -56,7 +62,7 @@ def filter_dependencies_info(dep_info, package_dir, mode='external'): assert mode == 'internal' filter_func = lambda x: is_standard_module(x, (package_dir,)) result = {} - for importee, importers in dep_info.items(): + for importee, importers in dep_info.iteritems(): if filter_func(importee): result[importee] = importers return result @@ -86,7 +92,7 @@ def repr_tree_defs(data, indent_str=None): lines.append('%s %s' % (mod, files)) sub_indent_str = ' ' else: - lines.append('%s\-%s %s' % (indent_str, mod, files)) + lines.append(r'%s\-%s %s' % (indent_str, mod, files)) if i == len(nodes)-1: sub_indent_str = '%s ' % indent_str else: @@ -102,14 +108,14 @@ def dependencies_graph(filename, dep_info): done = {} printer = DotBackend(filename[:-4], rankdir = "LR") printer.emit('URL="." node[shape="box"]') - for modname, dependencies in dep_info.items(): + for modname, dependencies in sorted(dep_info.iteritems()): done[modname] = 1 printer.emit_node(modname) for modname in dependencies: if modname not in done: done[modname] = 1 printer.emit_node(modname) - for depmodname, dependencies in dep_info.items(): + for depmodname, dependencies in sorted(dep_info.iteritems()): for modname in dependencies: printer.emit_edge(modname, depmodname) printer.generate(filename) @@ -128,24 +134,32 @@ def make_graph(filename, dep_info, sect, gtype): MSGS = { 'F0401': ('Unable to import %s', + 'import-error', 'Used when pylint has been unable to import a module.'), 'R0401': ('Cyclic import (%s)', + 'cyclic-import', 'Used when a cyclic import between two or more modules is \ detected.'), 'W0401': ('Wildcard import %s', + 'wildcard-import', 'Used when `from module import *` is detected.'), 'W0402': ('Uses of a deprecated module %r', + 'deprecated-module', 'Used a module marked as deprecated is imported.'), 'W0403': ('Relative import %r, should be %r', + 'relative-import', 'Used when an import relative to the package directory is \ detected.'), 'W0404': ('Reimport %r (imported line %s)', + 'reimported', 'Used when a module is reimported multiple times.'), 'W0406': ('Module import itself', + 'import-self', 'Used when a module is importing itself.'), 'W0410': ('__future__ import is not the first non docstring statement', + 'misplaced-future', 'Python 2.5 and greater require __future__ import to be the \ first non docstring statement in the module.'), } @@ -311,7 +325,7 @@ def _check_deprecated_module(self, node, mod_path): if mod_path == mod_name or mod_path.startswith(mod_name + '.'): self.add_message('W0402', node=node, args=mod_path) - def _check_reimport(self, node, name, basename=None, level=0): + def _check_reimport(self, node, name, basename=None, level=None): """check if the import is necessary (i.e. not already done)""" if 'W0404' not in self.active_msgs: return @@ -319,7 +333,7 @@ def _check_reimport(self, node, name, basename=None, level=0): root = node.root() contexts = [(frame, level)] if root is not frame: - contexts.append((root, 0)) + contexts.append((root, None)) for context, level in contexts: first = get_first_import(node, context, name, basename, level) if first is not None: @@ -329,7 +343,7 @@ def _check_reimport(self, node, name, basename=None, level=0): def report_external_dependencies(self, sect, _, dummy): """return a verbatim layout for displaying dependencies""" - dep_info = make_tree_defs(self._external_dependencies_info().items()) + dep_info = make_tree_defs(self._external_dependencies_info().iteritems()) if not dep_info: raise EmptyReport() tree_str = repr_tree_defs(dep_info) diff --git a/pylibs/pylint/checkers/logging.py b/pylibs/pylama/pylint/checkers/logging.py similarity index 88% rename from pylibs/pylint/checkers/logging.py rename to pylibs/pylama/pylint/checkers/logging.py index 89899b65..4c78d8f2 100644 --- a/pylibs/pylint/checkers/logging.py +++ b/pylibs/pylama/pylint/checkers/logging.py @@ -14,14 +14,15 @@ """checker for use of Python logging """ -from logilab import astng -from pylint import checkers -from pylint import interfaces -from pylint.checkers import utils +from ..logilab import astng +from .. import checkers +from .. import interfaces +from ..checkers import utils MSGS = { 'W1201': ('Specify string format arguments as logging function parameters', + 'logging-not-lazy', 'Used when a logging statement has a call form of ' '"logging.(format_string % (format_args...))". ' 'Such calls should leave string interpolation to the logging ' @@ -32,14 +33,18 @@ 'logged. For more, see ' 'http://www.python.org/dev/peps/pep-0282/.'), 'E1200': ('Unsupported logging format character %r (%#02x) at index %d', + 'logging-unsupported-format', 'Used when an unsupported format character is used in a logging\ statement format string.'), 'E1201': ('Logging format string ends in middle of conversion specifier', + 'logging-format-truncated', 'Used when a logging statement format string terminates before\ the end of a conversion specifier.'), 'E1205': ('Too many arguments for logging format string', + 'logging-too-many-args', 'Used when a logging format string is given too few arguments.'), 'E1206': ('Not enough arguments for logging format string', + 'logging-too-few-args', 'Used when a logging format string is given too many arguments'), } @@ -75,8 +80,17 @@ def visit_import(self, node): def visit_callfunc(self, node): """Checks calls to (simple forms of) logging methods.""" if (not isinstance(node.func, astng.Getattr) - or not isinstance(node.func.expr, astng.Name) - or node.func.expr.name != self._logging_name): + or not isinstance(node.func.expr, astng.Name)): + return + try: + logger_class = [inferred for inferred in node.func.expr.infer() if ( + isinstance(inferred, astng.Instance) + and [ancestor for ancestor in inferred._proxied.ancestors() if ( + ancestor.name == 'Logger' + and ancestor.parent.name == 'logging')])] + except astng.exceptions.InferenceError: + return + if (node.func.expr.name != self._logging_name and not logger_class): return self._check_convenience_methods(node) self._check_log_methods(node) diff --git a/pylibs/pylint/checkers/misc.py b/pylibs/pylama/pylint/checkers/misc.py similarity index 96% rename from pylibs/pylint/checkers/misc.py rename to pylibs/pylama/pylint/checkers/misc.py index 7f09d404..8830b8fd 100644 --- a/pylibs/pylint/checkers/misc.py +++ b/pylibs/pylama/pylint/checkers/misc.py @@ -17,14 +17,15 @@ Check source code is ascii only or has an encoding declaration (PEP 263) """ -import re, sys +import re -from pylint.interfaces import IRawChecker -from pylint.checkers import BaseChecker +from ..interfaces import IRawChecker +from ..checkers import BaseChecker MSGS = { 'W0511': ('%s', + 'fixme', 'Used when a warning note as FIXME or XXX is detected.'), } diff --git a/pylibs/pylint/checkers/newstyle.py b/pylibs/pylama/pylint/checkers/newstyle.py similarity index 90% rename from pylibs/pylint/checkers/newstyle.py rename to pylibs/pylama/pylint/checkers/newstyle.py index 7bb146da..c72f8d0d 100644 --- a/pylibs/pylint/checkers/newstyle.py +++ b/pylibs/pylama/pylint/checkers/newstyle.py @@ -16,21 +16,25 @@ """check for new / old style related problems """ -from logilab import astng +from ..logilab import astng -from pylint.interfaces import IASTNGChecker -from pylint.checkers import BaseChecker -from pylint.checkers.utils import check_messages +from ..interfaces import IASTNGChecker +from ..checkers import BaseChecker +from ..checkers.utils import check_messages MSGS = { 'E1001': ('Use of __slots__ on an old style class', + 'slots-on-old-class', 'Used when an old style class uses the __slots__ attribute.'), 'E1002': ('Use of super on an old style class', + 'super-on-old-class', 'Used when an old style class uses the super builtin.'), 'E1003': ('Bad first argument %r given to super class', + 'bad-super-call', 'Used when another argument than the current class is given as \ first argument of the super builtin.'), 'W1001': ('Use of "property" on an old style class', + 'property-on-old-class', 'Used when PyLint detect the use of the builtin "property" \ on an old style class while this is relying on new style \ classes features'), @@ -39,11 +43,11 @@ class NewStyleConflictChecker(BaseChecker): """checks for usage of new style capabilities on old style classes and - other new/old styles conflicts problems - * use of property, __slots__, super - * "super" usage + other new/old styles conflicts problems + * use of property, __slots__, super + * "super" usage """ - + __implements__ = (IASTNGChecker,) # configuration section name @@ -57,7 +61,7 @@ class NewStyleConflictChecker(BaseChecker): @check_messages('E1001') def visit_class(self, node): """check __slots__ usage - """ + """ if '__slots__' in node and not node.newstyle: self.add_message('E1001', node=node) diff --git a/pylibs/pylint/checkers/raw_metrics.py b/pylibs/pylama/pylint/checkers/raw_metrics.py similarity index 85% rename from pylibs/pylint/checkers/raw_metrics.py rename to pylibs/pylama/pylint/checkers/raw_metrics.py index 872ca7bc..9f270357 100644 --- a/pylibs/pylint/checkers/raw_metrics.py +++ b/pylibs/pylama/pylint/checkers/raw_metrics.py @@ -22,11 +22,11 @@ #if not hasattr(tokenize, 'NL'): # raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") -from logilab.common.ureports import Table +from ..logilab.common.ureports import Table -from pylint.interfaces import IRawChecker -from pylint.checkers import BaseRawChecker, EmptyReport -from pylint.reporters import diff_string +from ..interfaces import IRawChecker +from ..checkers import BaseRawChecker, EmptyReport +from ..reporters import diff_string def report_raw_stats(sect, stats, old_stats): """calculate percentage of code / doc / comment / empty @@ -51,12 +51,12 @@ def report_raw_stats(sect, stats, old_stats): class RawMetricsChecker(BaseRawChecker): - """does not check anything but gives some raw metrics : - * total number of lines - * total number of code lines - * total number of docstring lines - * total number of comments lines - * total number of empty lines + """does not check anything but gives some raw metrics : + * total number of lines + * total number of code lines + * total number of docstring lines + * total number of comments lines + * total number of empty lines """ __implements__ = (IRawChecker,) diff --git a/pylibs/pylint/checkers/similar.py b/pylibs/pylama/pylint/checkers/similar.py similarity index 86% rename from pylibs/pylint/checkers/similar.py rename to pylibs/pylama/pylint/checkers/similar.py index 1e38ed61..40d8a123 100644 --- a/pylibs/pylint/checkers/similar.py +++ b/pylibs/pylama/pylint/checkers/similar.py @@ -1,5 +1,5 @@ # pylint: disable=W0622 -# Copyright (c) 2004-2006 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2004-2012 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -16,25 +16,24 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """a similarities / code duplication command line tool and pylint checker """ -from __future__ import generators - import sys from itertools import izip -from logilab.common.ureports import Table +from ..logilab.common.ureports import Table -from pylint.interfaces import IRawChecker -from pylint.checkers import BaseChecker, table_lines_from_stats +from ..interfaces import IRawChecker +from ..checkers import BaseChecker, table_lines_from_stats class Similar: """finds copy-pasted lines of code in a project""" def __init__(self, min_lines=4, ignore_comments=False, - ignore_docstrings=False): + ignore_docstrings=False, ignore_imports=False): self.min_lines = min_lines self.ignore_comments = ignore_comments self.ignore_docstrings = ignore_docstrings + self.ignore_imports = ignore_imports self.linesets = [] def append_stream(self, streamid, stream): @@ -43,7 +42,8 @@ def append_stream(self, streamid, stream): self.linesets.append(LineSet(streamid, stream.readlines(), self.ignore_comments, - self.ignore_docstrings)) + self.ignore_docstrings, + self.ignore_imports)) def run(self): """start looking for similarities and display results on stdout""" @@ -125,7 +125,11 @@ def _iter_sims(self): for sim in self._find_common(lineset, lineset2): yield sim -def stripped_lines(lines, ignore_comments, ignore_docstrings): +def stripped_lines(lines, ignore_comments, ignore_docstrings, ignore_imports): + """return lines with leading/trailing whitespace and any ignored code + features removed + """ + strippedlines = [] docstring = None for line in lines: @@ -139,6 +143,9 @@ def stripped_lines(lines, ignore_comments, ignore_docstrings): if line.endswith(docstring): docstring = None line = '' + if ignore_imports: + if line.startswith("import ") or line.startswith("from "): + line = '' if ignore_comments: # XXX should use regex in checkers/format to avoid cutting # at a "#" in a string @@ -149,11 +156,12 @@ def stripped_lines(lines, ignore_comments, ignore_docstrings): class LineSet: """Holds and indexes all the lines of a single source file""" def __init__(self, name, lines, ignore_comments=False, - ignore_docstrings=False): + ignore_docstrings=False, ignore_imports=False): self.name = name self._real_lines = lines self._stripped_lines = stripped_lines(lines, ignore_comments, - ignore_docstrings) + ignore_docstrings, + ignore_imports) self._index = self._mk_index() def __str__(self): @@ -199,6 +207,7 @@ def _mk_index(self): MSGS = {'R0801': ('Similar lines in %s files\n%s', + 'duplicate-code', 'Indicates that a set of similar lines has been detected \ among multiple file. This usually means that the code should \ be refactored to avoid this duplication.')} @@ -237,9 +246,13 @@ class SimilarChecker(BaseChecker, Similar): {'default' : True, 'type' : 'yn', 'metavar' : '', 'help': 'Ignore docstrings when computing similarities.'} ), + ('ignore-imports', + {'default' : False, 'type' : 'yn', 'metavar' : '', + 'help': 'Ignore imports when computing similarities.'} + ), ) # reports - reports = ( ('R0801', 'Duplication', report_similarities), ) # XXX actually a Refactoring message + reports = ( ('RP0801', 'Duplication', report_similarities), ) def __init__(self, linter=None): BaseChecker.__init__(self, linter) @@ -259,6 +272,8 @@ def set_option(self, optname, value, action=None, optdict=None): self.ignore_comments = self.config.ignore_comments elif optname == 'ignore-docstrings': self.ignore_docstrings = self.config.ignore_docstrings + elif optname == 'ignore-imports': + self.ignore_imports = self.config.ignore_imports def open(self): """init the checkers: reset linesets and statistics information""" @@ -303,18 +318,21 @@ def usage(status=0): print "finds copy pasted blocks in a set of files" print print 'Usage: symilar [-d|--duplicates min_duplicated_lines] \ -[-i|--ignore-comments] file1...' +[-i|--ignore-comments] [--ignore-docstrings] [--ignore-imports] file1...' sys.exit(status) -def run(argv=None): +def Run(argv=None): """standalone command line access point""" if argv is None: argv = sys.argv[1:] from getopt import getopt s_opts = 'hdi' - l_opts = ('help', 'duplicates=', 'ignore-comments') + l_opts = ('help', 'duplicates=', 'ignore-comments', 'ignore-imports', + 'ignore-docstrings') min_lines = 4 ignore_comments = False + ignore_docstrings = False + ignore_imports = False opts, args = getopt(argv, s_opts, l_opts) for opt, val in opts: if opt in ('-d', '--duplicates'): @@ -323,12 +341,17 @@ def run(argv=None): usage() elif opt in ('-i', '--ignore-comments'): ignore_comments = True + elif opt in ('--ignore-docstrings'): + ignore_docstrings = True + elif opt in ('--ignore-imports'): + ignore_imports = True if not args: usage(1) - sim = Similar(min_lines, ignore_comments) + sim = Similar(min_lines, ignore_comments, ignore_docstrings, ignore_imports) for filename in args: sim.append_stream(filename, open(filename)) sim.run() + sys.exit(0) if __name__ == '__main__': - run() + Run() diff --git a/pylibs/pylint/checkers/string_format.py b/pylibs/pylama/pylint/checkers/string_format.py similarity index 93% rename from pylibs/pylint/checkers/string_format.py rename to pylibs/pylama/pylint/checkers/string_format.py index c420a604..fdec7022 100644 --- a/pylibs/pylint/checkers/string_format.py +++ b/pylibs/pylama/pylint/checkers/string_format.py @@ -13,48 +13,54 @@ # 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., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - """Checker for string formatting operations. """ -import string -from logilab import astng -from pylint.interfaces import IASTNGChecker -from pylint.checkers import BaseChecker -from pylint.checkers import utils +from ..logilab import astng +from ..interfaces import IASTNGChecker +from ..checkers import BaseChecker +from ..checkers import utils MSGS = { 'E1300': ("Unsupported format character %r (%#02x) at index %d", + "bad-format-character", "Used when a unsupported format character is used in a format\ string."), 'E1301': ("Format string ends in middle of conversion specifier", + "truncated-format-string", "Used when a format string terminates before the end of a \ conversion specifier."), 'E1302': ("Mixing named and unnamed conversion specifiers in format string", + "mixed-format-string", "Used when a format string contains both named (e.g. '%(foo)d') \ and unnamed (e.g. '%d') conversion specifiers. This is also \ used when a named conversion specifier contains * for the \ minimum field width and/or precision."), 'E1303': ("Expected mapping for format string, not %s", + "format-needs-mapping", "Used when a format string that uses named conversion specifiers \ is used with an argument that is not a mapping."), 'W1300': ("Format string dictionary key should be a string, not %s", + "bad-format-string-key", "Used when a format string that uses named conversion specifiers \ is used with a dictionary whose keys are not all strings."), 'W1301': ("Unused key %r in format string dictionary", + "unused-format-string-key", "Used when a format string that uses named conversion specifiers \ is used with a dictionary that conWtains keys not required by the \ format string."), 'E1304': ("Missing key %r in format string dictionary", + "missing-format-string-key", "Used when a format string that uses named conversion specifiers \ is used with a dictionary that doesn't contain all the keys \ required by the format string."), 'E1305': ("Too many arguments for format string", + "too-many-format-args", "Used when a format string that uses unnamed conversion \ specifiers is given too few arguments."), 'E1306': ("Not enough arguments for format string", + "too-few-format-args", "Used when a format string that uses unnamed conversion \ specifiers is given too many arguments"), } @@ -104,7 +110,7 @@ def visit_binop(self, node): if isinstance(args, astng.Dict): keys = set() unknown_keys = False - for k, v in args.items: + for k, _ in args.items: if isinstance(k, astng.Const): key = k.value if isinstance(key, basestring): diff --git a/pylibs/pylint/checkers/typecheck.py b/pylibs/pylama/pylint/checkers/typecheck.py similarity index 95% rename from pylibs/pylint/checkers/typecheck.py rename to pylibs/pylama/pylint/checkers/typecheck.py index db2f4937..c6309aea 100644 --- a/pylibs/pylint/checkers/typecheck.py +++ b/pylibs/pylama/pylint/checkers/typecheck.py @@ -19,42 +19,52 @@ import re import shlex -from logilab import astng -from logilab.astng import InferenceError, NotFoundError, YES, Instance +from ..logilab import astng +from ..logilab.astng import InferenceError, NotFoundError, YES, Instance -from pylint.interfaces import IASTNGChecker -from pylint.checkers import BaseChecker -from pylint.checkers.utils import safe_infer, is_super, check_messages +from ..interfaces import IASTNGChecker +from ..checkers import BaseChecker +from ..checkers.utils import safe_infer, is_super, check_messages MSGS = { 'E1101': ('%s %r has no %r member', + 'no-member', 'Used when a variable is accessed for an unexistent member.'), 'E1102': ('%s is not callable', + 'not-callable', 'Used when an object being called has been inferred to a non \ callable object'), 'E1103': ('%s %r has no %r member (but some types could not be inferred)', + 'maybe-no-member', 'Used when a variable is accessed for an unexistent member, but \ astng was not able to interpret all possible types of this \ variable.'), 'E1111': ('Assigning to function call which doesn\'t return', + 'assignment-from-no-return', 'Used when an assignment is done on a function call but the \ inferred function doesn\'t return anything.'), 'W1111': ('Assigning to function call which only returns None', + 'assignment-from-none', 'Used when an assignment is done on a function call but the \ inferred function returns nothing but None.'), 'E1120': ('No value passed for parameter %s in function call', + 'no-value-for-parameter', 'Used when a function call passes too few arguments.'), 'E1121': ('Too many positional arguments for function call', + 'too-many-function-args', 'Used when a function call passes too many positional \ arguments.'), 'E1122': ('Duplicate keyword argument %r in function call', + 'duplicate-keyword-arg', 'Used when a function call passes the same keyword argument \ multiple times.'), 'E1123': ('Passing unexpected keyword argument %r in function call', + 'unexpected-keyword-arg', 'Used when a function call passes a keyword argument that \ doesn\'t correspond to one of the function\'s parameter names.'), 'E1124': ('Multiple values passed for parameter %r in function call', + 'redundant-keyword-arg', 'Used when a function call would result in assigning multiple \ values to a function parameter, one value from a positional \ argument and one from a keyword argument.'), @@ -172,7 +182,7 @@ def visit_getattr(self, node): continue except NotFoundError: if isinstance(owner, astng.Function) and owner.decorators: - continue + continue if isinstance(owner, Instance) and owner.has_dynamic_getattr(): continue # explicit skipping of optparse'Values class @@ -367,7 +377,7 @@ def visit_callfunc(self, node): for [(name, defval), assigned] in parameters: if (defval is None) and not assigned: if name is None: - display = '' + display_name = '' else: display_name = repr(name) self.add_message('E1120', node=node, args=display_name) diff --git a/pylibs/pylint/checkers/utils.py b/pylibs/pylama/pylint/checkers/utils.py similarity index 98% rename from pylibs/pylint/checkers/utils.py rename to pylibs/pylama/pylint/checkers/utils.py index cff12fe3..193f6cd9 100644 --- a/pylibs/pylint/checkers/utils.py +++ b/pylibs/pylama/pylint/checkers/utils.py @@ -1,6 +1,6 @@ # pylint: disable=W0611 # -# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -20,9 +20,9 @@ import re import string -from logilab import astng -from logilab.astng import scoped_nodes -from logilab.common.compat import builtins +from ..logilab import astng +from ..logilab.astng import scoped_nodes +from ..logilab.common.compat import builtins BUILTINS_NAME = builtins.__name__ COMP_NODE_TYPES = astng.ListComp, astng.SetComp, astng.DictComp, astng.GenExpr @@ -65,7 +65,7 @@ def clobber_in_except(node): if (stmts and not isinstance(stmts[0].ass_type(), (astng.Assign, astng.AugAssign, astng.ExceptHandler))): - return (True, (name, 'outer scope (line %i)' % (stmts[0].lineno,))) + return (True, (name, 'outer scope (line %s)' % (stmts[0].fromlineno,))) return (False, None) diff --git a/pylibs/pylint/checkers/variables.py b/pylibs/pylama/pylint/checkers/variables.py similarity index 89% rename from pylibs/pylint/checkers/variables.py rename to pylibs/pylama/pylint/checkers/variables.py index f0abd1ba..88bd2a51 100644 --- a/pylibs/pylint/checkers/variables.py +++ b/pylibs/pylama/pylint/checkers/variables.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2011 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -19,21 +19,21 @@ import sys from copy import copy -from logilab import astng -from logilab.astng import are_exclusive, builtin_lookup, ASTNGBuildingException +from ..logilab import astng +from ..logilab.astng import are_exclusive, builtin_lookup, ASTNGBuildingException -from pylint.interfaces import IASTNGChecker -from pylint.checkers import BaseChecker -from pylint.checkers.utils import (PYMETHODS, is_ancestor_name, is_builtin, +from ..interfaces import IASTNGChecker +from ..checkers import BaseChecker +from ..checkers.utils import (PYMETHODS, is_ancestor_name, is_builtin, is_defined_before, is_error, is_func_default, is_func_decorator, assign_parent, check_messages, is_inside_except, clobber_in_except, get_all_elements) def in_for_else_branch(parent, stmt): - """Returns True if stmt in inside the else branch for a parent For stmt.""" - return (isinstance(parent, astng.For) and - any(else_stmt.parent_of(stmt) for else_stmt in parent.orelse)) + """Returns True if stmt in inside the else branch for a parent For stmt.""" + return (isinstance(parent, astng.For) and + any(else_stmt.parent_of(stmt) for else_stmt in parent.orelse)) def overridden_method(klass, name): """get overridden method if any""" @@ -54,46 +54,67 @@ def overridden_method(klass, name): MSGS = { 'E0601': ('Using variable %r before assignment', + 'used-before-assignment', 'Used when a local variable is accessed before it\'s \ assignment.'), 'E0602': ('Undefined variable %r', + 'undefined-variable', 'Used when an undefined variable is accessed.'), + 'E0603': ('Undefined variable name %r in __all__', + 'undefined-all-variable', + 'Used when an undefined variable name is referenced in __all__.'), + 'E0604': ('Invalid object %r in __all__, must contain only strings', + 'invalid-all-object', + 'Used when an invalid (non-string) object occurs in __all__.'), 'E0611': ('No name %r in module %r', + 'no-name-in-module', 'Used when a name cannot be found in a module.'), 'W0601': ('Global variable %r undefined at the module level', + 'global-variable-undefined', 'Used when a variable is defined through the "global" statement \ but the variable is not defined in the module scope.'), 'W0602': ('Using global for %r but no assignment is done', + 'global-variable-not-assigned', 'Used when a variable is defined through the "global" statement \ but no assignment to this variable is done.'), 'W0603': ('Using the global statement', # W0121 + 'global-statement', 'Used when you use the "global" statement to update a global \ variable. PyLint just try to discourage this \ usage. That doesn\'t mean you can not use it !'), 'W0604': ('Using the global statement at the module level', # W0103 + 'global-at-module-level', 'Used when you use the "global" statement at the module level \ since it has no effect'), 'W0611': ('Unused import %s', + 'unused-import', 'Used when an imported module or variable is not used.'), 'W0612': ('Unused variable %r', + 'unused-variable', 'Used when a variable is defined but not used.'), 'W0613': ('Unused argument %r', + 'unused-argument', 'Used when a function or method argument is not used.'), 'W0614': ('Unused import %s from wildcard import', + 'unused-wildcard-import', 'Used when an imported module or variable is not used from a \ \'from X import *\' style import.'), 'W0621': ('Redefining name %r from outer scope (line %s)', + 'redefined-outer-name', 'Used when a variable\'s name hide a name defined in the outer \ scope.'), 'W0622': ('Redefining built-in %r', + 'redefined-builtin', 'Used when a variable or function override a built-in.'), 'W0623': ('Redefining name %r from %s in exception handler', + 'redefine-in-handler', 'Used when an exception handler assigns the exception \ to an existing name'), 'W0631': ('Using possibly undefined loop variable %r', + 'undefined-loop-variable', 'Used when an loop variable (i.e. defined by a for loop or \ a list comprehension or a generator expression) is used outside \ the loop.'), @@ -105,6 +126,7 @@ class VariablesChecker(BaseChecker): * undefined variables * redefinition of variable from builtins or from an outer scope * use of variable before assignment + * __all__ consistency """ __implements__ = IASTNGChecker @@ -133,15 +155,13 @@ def __init__(self, linter=None): BaseChecker.__init__(self, linter) self._to_consume = None self._checking_mod_attr = None - self._vars = None def visit_module(self, node): """visit module : update consumption analysis variable checks globals doesn't overrides builtins """ self._to_consume = [(copy(node.locals), {}, 'module')] - self._vars = [] - for name, stmts in node.locals.items(): + for name, stmts in node.locals.iteritems(): if is_builtin(name) and not is_inside_except(stmts[0]): # do not print Redefining builtin for additional builtins self.add_message('W0622', args=name, node=stmts[0]) @@ -152,10 +172,30 @@ def leave_module(self, node): """ assert len(self._to_consume) == 1 not_consumed = self._to_consume.pop()[0] + # attempt to check for __all__ if defined + if '__all__' in node.locals: + assigned = node.igetattr('__all__').next() + if assigned is not astng.YES: + for elt in getattr(assigned, 'elts', ()): + try: + elt_name = elt.infer().next() + except astng.InferenceError: + continue + + if not isinstance(elt_name, astng.Const) or not isinstance(elt_name.value, basestring): + self.add_message('E0604', args=elt.as_string(), node=elt) + continue + elt_name = elt.value + # If elt is in not_consumed, remove it from not_consumed + if elt_name in not_consumed: + del not_consumed[elt_name] + continue + if elt_name not in node.locals: + self.add_message('E0603', args=elt_name, node=elt) # don't check unused imports in __init__ files if not self.config.init_import and node.package: return - for name, stmts in not_consumed.items(): + for name, stmts in not_consumed.iteritems(): stmt = stmts[0] if isinstance(stmt, astng.Import): self.add_message('W0611', args=name, node=stmt) @@ -165,7 +205,6 @@ def leave_module(self, node): else: self.add_message('W0611', args=name, node=stmt) del self._to_consume - del self._vars def visit_class(self, node): """visit class: update consumption analysis variable @@ -226,7 +265,6 @@ def visit_function(self, node): """visit function: update consumption analysis variable and check locals """ self._to_consume.append((copy(node.locals), {}, 'function')) - self._vars.append({}) if not set(('W0621', 'W0622')) & self.active_msgs: return globs = node.root().globals @@ -234,7 +272,7 @@ def visit_function(self, node): if is_inside_except(stmt): continue if name in globs and not isinstance(stmt, astng.Global): - line = globs[name][0].lineno + line = globs[name][0].fromlineno self.add_message('W0621', args=(name, line), node=stmt) elif is_builtin(name): # do not print Redefining builtin for additional builtins @@ -243,7 +281,6 @@ def visit_function(self, node): def leave_function(self, node): """leave function: check function's locals are consumed""" not_consumed = self._to_consume.pop()[0] - self._vars.pop(0) if not set(('W0612', 'W0613')) & self.active_msgs: return # don't check arguments of function which are only raising an exception @@ -375,7 +412,7 @@ def visit_excepthandler(self, node): def visit_assname(self, node): if isinstance(node.ass_type(), astng.AugAssign): self.visit_name(node) - + def visit_delname(self, node): self.visit_name(node) diff --git a/pylibs/pylint/config.py b/pylibs/pylama/pylint/config.py similarity index 89% rename from pylibs/pylint/config.py rename to pylibs/pylama/pylint/config.py index 60b51ee9..94d44b8f 100644 --- a/pylibs/pylint/config.py +++ b/pylibs/pylama/pylint/config.py @@ -1,3 +1,4 @@ +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). # 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 @@ -10,12 +11,10 @@ # 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., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -""" Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE). - http://www.logilab.fr/ -- mailto:contact@logilab.fr +"""utilities for PyLint configuration : - utilities for PyLint configuration : - _ pylintrc - _ pylint.d (PYLINT_HOME) +* pylintrc +* pylint.d (PYLINTHOME) """ import pickle @@ -34,7 +33,7 @@ PYLINT_HOME = ".pylint.d" else: PYLINT_HOME = join(USER_HOME, '.pylint.d') - + if not exists(PYLINT_HOME): try: os.mkdir(PYLINT_HOME) @@ -47,14 +46,14 @@ def get_pdata_path(base_name, recurs): """ base_name = base_name.replace(os.sep, '_') return join(PYLINT_HOME, "%s%s%s"%(base_name, recurs, '.stats')) - + def load_results(base): """try to unpickle and return data from file if it exists and is not corrupted - + return an empty dictionary if it doesn't exists """ - data_file = get_pdata_path(base, 1) + data_file = get_pdata_path(base, 1) try: return pickle.load(open(data_file)) except: @@ -106,14 +105,14 @@ def find_pylintrc(): PYLINTRC = find_pylintrc() ENV_HELP = ''' -The following environment variables are used : - * PYLINTHOME +The following environment variables are used: + * PYLINTHOME path to the directory where data of persistent run will be stored. If not found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working directory). - * PYLINTRC - path to the configuration file. If not found, it will use the first -existent file in ~/.pylintrc, /etc/pylintrc. + * PYLINTRC + path to the configuration file. If not found, it will use the first +existing file among (~/.pylintrc, /etc/pylintrc). ''' % globals() # evaluation messages ######################################################### diff --git a/pylibs/pylint/interfaces.py b/pylibs/pylama/pylint/interfaces.py similarity index 96% rename from pylibs/pylint/interfaces.py rename to pylibs/pylama/pylint/interfaces.py index 3d7bdad6..2d2432df 100644 --- a/pylibs/pylint/interfaces.py +++ b/pylibs/pylama/pylint/interfaces.py @@ -18,7 +18,7 @@ __revision__ = "$Id: interfaces.py,v 1.9 2004-04-24 12:14:53 syt Exp $" -from logilab.common.interface import Interface +from .logilab.common.interface import Interface class IChecker(Interface): @@ -95,4 +95,4 @@ def display_results(self, layout): """ -__all__ = ('IRawChecker', 'IStatable', 'ILinter', 'IReporter') +__all__ = ('IRawChecker', 'ILinter', 'IReporter') diff --git a/pylibs/pylint/lint.py b/pylibs/pylama/pylint/lint.py similarity index 80% rename from pylibs/pylint/lint.py rename to pylibs/pylama/pylint/lint.py index e39a880f..e1cc8b6f 100644 --- a/pylibs/pylint/lint.py +++ b/pylibs/pylama/pylint/lint.py @@ -16,7 +16,7 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ %prog [options] module_or_package - Check that a module satisfy a coding standard (and more !). + Check that a module satisfies a coding standard (and more !). %prog --help @@ -28,7 +28,7 @@ """ # import this first to avoid builtin namespace pollution -from pylint.checkers import utils +from .checkers import utils import sys import os @@ -36,31 +36,31 @@ import tokenize from warnings import warn -from logilab.common.configuration import UnsupportedAction, OptionsManagerMixIn -from logilab.common.optik_ext import check_csv -from logilab.common.modutils import load_module_from_name -from logilab.common.interface import implements -from logilab.common.textutils import splitstrip -from logilab.common.ureports import Table, Text, Section -from logilab.common.__pkginfo__ import version as common_version +from .logilab.common.configuration import UnsupportedAction, OptionsManagerMixIn +from .logilab.common.optik_ext import check_csv +from .logilab.common.modutils import load_module_from_name, get_module_part +from .logilab.common.interface import implements +from .logilab.common.textutils import splitstrip +from .logilab.common.ureports import Table, Text, Section +from .logilab.common.__pkginfo__ import version as common_version -from logilab.astng import MANAGER, nodes, ASTNGBuildingException -from logilab.astng.__pkginfo__ import version as astng_version +from .logilab.astng import MANAGER, nodes, ASTNGBuildingException +from .logilab.astng.__pkginfo__ import version as astng_version -from pylint.utils import PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn,\ - ReportsHandlerMixIn, MSG_TYPES, expand_modules -from pylint.interfaces import ILinter, IRawChecker, IASTNGChecker -from pylint.checkers import BaseRawChecker, EmptyReport, \ - table_lines_from_stats -from pylint.reporters.text import TextReporter, ParseableTextReporter, \ - VSTextReporter, ColorizedTextReporter -from pylint.reporters.html import HTMLReporter -from pylint import config +from .utils import (PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, + ReportsHandlerMixIn, MSG_TYPES, expand_modules) +from .interfaces import ILinter, IRawChecker, IASTNGChecker +from .checkers import (BaseRawChecker, EmptyReport, + table_lines_from_stats) +from .reporters.text import (TextReporter, ParseableTextReporter, + VSTextReporter, ColorizedTextReporter) +from .reporters.html import HTMLReporter +from . import config -from pylint.__pkginfo__ import version +from .__pkginfo__ import version -OPTION_RGX = re.compile('\s*#*\s*pylint:(.*)') +OPTION_RGX = re.compile(r'\s*#.*\bpylint:(.*)') REPORTER_OPT_MAP = {'text': TextReporter, 'parseable': ParseableTextReporter, 'msvs': VSTextReporter, @@ -84,47 +84,74 @@ def _get_python_path(filepath): MSGS = { 'F0001': ('%s', + 'fatal', 'Used when an error occurred preventing the analysis of a \ module (unable to find it for instance).'), 'F0002': ('%s: %s', + 'astng-error', 'Used when an unexpected error occurred while building the ASTNG \ representation. This is usually accompanied by a traceback. \ Please report such errors !'), 'F0003': ('ignored builtin module %s', - 'Used to indicate that the user asked to analyze a builtin module\ + 'ignored-builtin-module', + 'Used to indicate that the user asked to analyze a builtin module \ which has been skipped.'), 'F0004': ('unexpected inferred value %s', + 'unexpected-inferred-value', 'Used to indicate that some value of an unexpected type has been \ inferred.'), 'F0010': ('error while code parsing: %s', + 'parse-error', 'Used when an exception occured while building the ASTNG \ representation which could be handled by astng.'), - 'I0001': ('Unable to run raw checkers on built-in module %s', + 'raw-checker-failed', 'Used to inform that a built-in module has not been checked \ using the raw checkers.'), 'I0010': ('Unable to consider inline option %r', + 'bad-inline-option', 'Used when an inline option is either badly formatted or can\'t \ be used inside modules.'), 'I0011': ('Locally disabling %s', + 'locally-disabled', 'Used when an inline option disables a message or a messages \ category.'), 'I0012': ('Locally enabling %s', + 'locally-enabled', 'Used when an inline option enables a message or a messages \ category.'), 'I0013': ('Ignoring entire file', + 'file-ignored', 'Used to inform that the file will not be checked'), + 'I0014': ('Used deprecated directive "pylint:disable-all" or "pylint:disable=all"', + 'deprecated-disable-all', + 'You should preferably use "pylint:skip-file" as this directive ' + 'has a less confusing name. Do this only if you are sure that all ' + 'people running Pylint on your code have version >= 0.26'), + 'I0020': ('Suppressed %s (from line %d)', + 'suppressed-message', + 'A message was triggered on a line, but suppressed explicitly ' + 'by a disable= comment in the file. This message is not ' + 'generated for messages that are ignored due to configuration ' + 'settings.'), + 'I0021': ('Useless suppression of %s', + 'useless-suppression', + 'Reported when a message is explicitly disabled for a line or ' + 'a block of code, but never triggered.'), 'E0001': ('%s', + 'syntax-error', 'Used when a syntax error is raised for a module.'), 'E0011': ('Unrecognized file option %r', + 'unrecognized-inline-option', 'Used when an unknown inline option is encountered.'), 'E0012': ('Bad option value %r', + 'bad-option-value', 'Used when a bad value for an inline option is encountered.'), } @@ -171,12 +198,13 @@ def make_options(): python modules names) to load, usually to register additional checkers.'}), ('output-format', - {'default': 'text', 'type': 'choice', 'metavar' : '', - 'choices': REPORTER_OPT_MAP.keys(), + {'default': 'text', 'type': 'string', 'metavar' : '', 'short': 'f', 'group': 'Reports', - 'help' : 'Set the output format. Available formats are text,\ - parseable, colorized, msvs (visual studio) and html'}), + 'help' : 'Set the output format. Available formats are text,' + ' parseable, colorized, msvs (visual studio) and html. You ' + 'can also give a reporter class, eg mypackage.mymodule.' + 'MyReporterClass.'}), ('include-ids', {'type' : 'yn', 'metavar' : '', 'default' : 0, @@ -184,6 +212,12 @@ def make_options(): 'group': 'Reports', 'help' : 'Include message\'s id in output'}), + ('symbols', + {'type' : 'yn', 'metavar' : '', 'default' : 0, + 'short': 's', + 'group': 'Reports', + 'help' : 'Include symbolic ids of messages in output'}), + ('files-output', {'default': 0, 'type' : 'yn', 'metavar' : '', 'group': 'Reports', 'level': 1, @@ -221,17 +255,25 @@ def make_options(): 'group': 'Messages control', 'help' : 'Enable the message, report, category or checker with the ' 'given id(s). You can either give multiple identifier ' - 'separated by comma (,) or put this option multiple time.'}), + 'separated by comma (,) or put this option multiple time. ' + 'See also the "--disable" option for examples. '}), ('disable', {'type' : 'csv', 'metavar': '', 'short': 'd', 'group': 'Messages control', 'help' : 'Disable the message, report, category or checker ' - 'with the given id(s). You can either give multiple identifier' - ' separated by comma (,) or put this option multiple time ' + 'with the given id(s). You can either give multiple identifiers' + ' separated by comma (,) or put this option multiple times ' '(only on the command line, not in the configuration file ' - 'where it should appear only once).'}), + 'where it should appear only once).' + 'You can also use "--disable=all" to disable everything first ' + 'and then reenable specific checks. For example, if you want ' + 'to run only the similarities checker, you can use ' + '"--disable=all --enable=similarities". ' + 'If you want to run only the classes checker, but have no ' + 'Warning level messages displayed, use' + '"--disable=all --enable=classes --disable=W"'}), ) option_groups = ( @@ -285,9 +327,20 @@ def __init__(self, options=(), reporter=None, option_groups=(), self.set_reporter(reporter or TextReporter(sys.stdout)) def load_default_plugins(self): - from pylint import checkers + from . import checkers checkers.initialize(self) + def prepare_import_path(self, args): + """Prepare sys.path for running the linter checks.""" + if len(args) == 1: + sys.path.insert(0, _get_python_path(args[0])) + else: + sys.path.insert(0, os.getcwd()) + + def cleanup_import_path(self): + """Revert any changes made to sys.path in prepare_import_path.""" + sys.path.pop(0) + def load_plugin_modules(self, modnames): """take a list of module names which are pylint plugins and load and register them @@ -323,7 +376,14 @@ def set_option(self, optname, value, action=None, optdict=None): else : meth(value) elif optname == 'output-format': - self.set_reporter(REPORTER_OPT_MAP[value.lower()]()) + if value.lower() in REPORTER_OPT_MAP: + self.set_reporter(REPORTER_OPT_MAP[value.lower()]()) + else: + module = load_module_from_name(get_module_part(value)) + class_name = value.split('.')[-1] + reporter_class = getattr(module, class_name) + self.set_reporter(reporter_class()) + try: BaseRawChecker.set_option(self, optname, value, action, optdict) except UnsupportedAction: @@ -357,7 +417,7 @@ def disable_noerror_messages(self): def disable_reporters(self): """disable all reporters""" - for reporters in self._reports.values(): + for reporters in self._reports.itervalues(): for report_id, _title, _cb in reporters: self.disable_report(report_id) @@ -384,7 +444,9 @@ def process_tokens(self, tokens): match = OPTION_RGX.search(line) if match is None: continue - if match.group(1).strip() == "disable-all": + if match.group(1).strip() == "disable-all" or match.group(1).strip() == 'skip-file': + if match.group(1).strip() == "disable-all": + self.add_message('I0014', line=start[0]) self.add_message('I0013', line=start[0]) self._ignore_file = True return @@ -400,11 +462,16 @@ def process_tokens(self, tokens): meth = self._options_methods[opt] except KeyError: meth = self._bw_options_methods[opt] - warn('%s is deprecated, replace it by %s (%s, line %s)' % ( + warn('%s is deprecated, replace it with %s (%s, line %s)' % ( opt, opt.split('-')[0], self.current_file, line), DeprecationWarning) for msgid in splitstrip(value): try: + if (opt, msgid) == ('disable', 'all'): + self.add_message('I0014', line=start[0]) + self.add_message('I0013', line=start[0]) + self._ignore_file = True + return meth(msgid, 'module', start[0]) except UnknownMessage: self.add_message('E0012', args=msgid, line=start[0]) @@ -439,6 +506,7 @@ def collect_block_lines(self, node, msg_state): firstchildlineno = last for msgid, lines in msg_state.iteritems(): for lineno, state in lines.items(): + original_lineno = lineno if first <= lineno <= last: if lineno > firstchildlineno: state = True @@ -449,6 +517,9 @@ def collect_block_lines(self, node, msg_state): if not line in self._module_msgs_state.get(msgid, ()): if line in lines: # state change in the same block state = lines[line] + original_lineno = line + if not state: + self._suppression_mapping[(msgid, line)] = original_lineno try: self._module_msgs_state[msgid][line] = state except KeyError: @@ -460,7 +531,7 @@ def collect_block_lines(self, node, msg_state): def get_checkers(self): """return all available checkers as a list""" - return [self] + [c for checkers in self._checkers.values() + return [self] + [c for checkers in self._checkers.itervalues() for c in checkers if c is not self] def prepare_checkers(self): @@ -470,8 +541,9 @@ def prepare_checkers(self): # get needed checkers neededcheckers = [self] for checker in self.get_checkers()[1:]: + # fatal errors should not trigger enable / disabling a checker messages = set(msg for msg in checker.msgs - if self.is_message_enabled(msg)) + if msg[0] != 'F' and self.is_message_enabled(msg)) if (messages or any(self.report_is_enabled(r[0]) for r in checker.reports)): neededcheckers.append(checker) @@ -483,6 +555,7 @@ def check(self, files_or_modules): name. """ self.reporter.include_ids = self.config.include_ids + self.reporter.symbols = self.config.symbols if not isinstance(files_or_modules, (list, tuple)): files_or_modules = (files_or_modules,) walker = PyLintASTWalker(self) @@ -497,6 +570,9 @@ def check(self, files_or_modules): # build ast and check modules or packages for descr in self.expand_files(files_or_modules): modname, filepath = descr['name'], descr['path'] + if self.config.files_output: + reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension) + self.reporter.set_output(open(reportfile, 'w')) self.set_current_module(modname, filepath) # get the module representation astng = self.get_astng(filepath, modname) @@ -504,14 +580,12 @@ def check(self, files_or_modules): continue self.base_name = descr['basename'] self.base_file = descr['basepath'] - if self.config.files_output: - reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension) - self.reporter.set_output(open(reportfile, 'w')) self._ignore_file = False # fix the current file (if the source file was not available or # if it's actually a c extension) self.current_file = astng.file self.check_astng_module(astng, walker, rawcheckers) + self._add_suppression_messages() # notify global end self.set_current_module('') self.stats['statement'] = walker.nbstatements @@ -543,7 +617,7 @@ def set_current_module(self, modname, filepath=None): self.current_file = filepath or modname self.stats['by_module'][modname] = {} self.stats['by_module'][modname]['statement'] = 0 - for msg_cat in MSG_TYPES.values(): + for msg_cat in MSG_TYPES.itervalues(): self.stats['by_module'][modname][msg_cat] = 0 # XXX hack, to be correct we need to keep module_msgs_state # for every analyzed module (the problem stands with localized @@ -551,6 +625,8 @@ def set_current_module(self, modname, filepath=None): if modname: self._module_msgs_state = {} self._module_msg_cats_state = {} + self._raw_module_msgs_state = {} + self._ignored_msgs = {} def get_astng(self, filepath, modname): """return a astng representation for a module""" @@ -578,8 +654,11 @@ def check_astng_module(self, astng, walker, rawcheckers): if self._ignore_file: return False # walk ast to collect line numbers + for msg, lines in self._module_msgs_state.iteritems(): + self._raw_module_msgs_state[msg] = lines.copy() orig_state = self._module_msgs_state.copy() self._module_msgs_state = {} + self._suppression_mapping = {} self.collect_block_lines(astng, orig_state) for checker in rawcheckers: checker.process_module(astng) @@ -594,7 +673,7 @@ def open(self): self.stats = { 'by_module' : {}, 'by_msg' : {}, } - for msg_cat in MSG_TYPES.values(): + for msg_cat in MSG_TYPES.itervalues(): self.stats[msg_cat] = 0 def close(self): @@ -622,6 +701,18 @@ def close(self): # specific reports ######################################################## + def _add_suppression_messages(self): + for warning, lines in self._raw_module_msgs_state.iteritems(): + for line, enable in lines.iteritems(): + if not enable and (warning, line) not in self._ignored_msgs: + self.add_message('I0021', line, None, + (self.get_msg_display_string(warning),)) + + for (warning, from_), lines in self._ignored_msgs.iteritems(): + for line in lines: + self.add_message('I0020', line, None, + (self.get_msg_display_string(warning), from_)) + def report_evaluation(self, sect, stats, previous_stats): """make the global evaluation report""" # check with at least check 1 statements (usually 0 when there is a @@ -659,7 +750,7 @@ def report_messages_stats(sect, stats, _): # don't print this report when we didn't detected any errors raise EmptyReport() in_order = sorted([(value, msg_id) - for msg_id, value in stats['by_msg'].items() + for msg_id, value in stats['by_msg'].iteritems() if not msg_id.startswith('I')]) in_order.reverse() lines = ('message id', 'occurrences') @@ -675,7 +766,7 @@ def report_messages_by_module_stats(sect, stats, _): by_mod = {} for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'): total = stats[m_type] - for module in stats['by_module'].keys(): + for module in stats['by_module'].iterkeys(): mod_total = stats['by_module'][module][m_type] if total == 0: percent = 0 @@ -683,7 +774,7 @@ def report_messages_by_module_stats(sect, stats, _): percent = float((mod_total)*100) / total by_mod.setdefault(module, {})[m_type] = percent sorted_result = [] - for module, mod_info in by_mod.items(): + for module, mod_info in by_mod.iteritems(): sorted_result.append((mod_info['error'], mod_info['warning'], mod_info['refactor'], @@ -706,11 +797,10 @@ def report_messages_by_module_stats(sect, stats, _): # utilities ################################################################### # this may help to import modules using gettext +# XXX syt, actually needed since we don't import code? -try: - __builtins__._ = str -except AttributeError: - __builtins__['_'] = str +from .logilab.common.compat import builtins +builtins._ = str class ArgumentPreprocessingError(Exception): @@ -833,34 +923,36 @@ def __init__(self, args, reporter=None, exit=True): linter.load_plugin_modules(self._plugins) # add some help section linter.add_help_section('Environment variables', config.ENV_HELP, level=1) - linter.add_help_section('Output', ''' -Using the default text output, the message format is : - - MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE - -There are 5 kind of message types : - * (C) convention, for programming standard violation - * (R) refactor, for bad code smell - * (W) warning, for python specific problems - * (E) error, for probable bugs in the code - * (F) fatal, if an error occurred which prevented pylint from doing further -processing. - ''', level=1) - linter.add_help_section('Output status code', ''' -Pylint should leave with following status code: - * 0 if everything went fine - * 1 if a fatal message was issued - * 2 if an error message was issued - * 4 if a warning message was issued - * 8 if a refactor message was issued - * 16 if a convention message was issued - * 32 on usage error - -status 1 to 16 will be bit-ORed so you can know which different categories has -been issued by analysing pylint output status code - ''', level=1) + linter.add_help_section('Output', +'Using the default text output, the message format is : \n' +' \n' +' MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE \n' +' \n' +'There are 5 kind of message types : \n' +' * (C) convention, for programming standard violation \n' +' * (R) refactor, for bad code smell \n' +' * (W) warning, for python specific problems \n' +' * (E) error, for probable bugs in the code \n' +' * (F) fatal, if an error occurred which prevented pylint from doing further\n' +'processing.\n' + , level=1) + linter.add_help_section('Output status code', +'Pylint should leave with following status code: \n' +' * 0 if everything went fine \n' +' * 1 if a fatal message was issued \n' +' * 2 if an error message was issued \n' +' * 4 if a warning message was issued \n' +' * 8 if a refactor message was issued \n' +' * 16 if a convention message was issued \n' +' * 32 on usage error \n' +' \n' +'status 1 to 16 will be bit-ORed so you can know which different categories has\n' +'been issued by analysing pylint output status code\n', + level=1) # read configuration linter.disable('W0704') + linter.disable('I0020') + linter.disable('I0021') linter.read_config_file() # is there some additional plugins in the file configuration, in config_parser = linter.cfgfile_parser @@ -886,10 +978,7 @@ def __init__(self, args, reporter=None, exit=True): sys.exit(32) # insert current working directory to the python path to have a correct # behaviour - if len(args) == 1: - sys.path.insert(0, _get_python_path(args[0])) - else: - sys.path.insert(0, os.getcwd()) + linter.prepare_import_path(args) if self.linter.config.profile: print >> sys.stderr, '** profiled run' import cProfile, pstats @@ -900,7 +989,7 @@ def __init__(self, args, reporter=None, exit=True): data.print_stats(30) else: linter.check(args) - sys.path.pop(0) + linter.cleanup_import_path() if exit: sys.exit(self.linter.msg_status) @@ -929,7 +1018,7 @@ def cb_generate_config(self, *args, **kwargs): def cb_generate_manpage(self, *args, **kwargs): """optik callback for sample config file generation""" - from pylint import __pkginfo__ + from . import __pkginfo__ self.linter.generate_manpage(__pkginfo__) sys.exit(0) diff --git a/pylibs/logilab/__init__.py b/pylibs/pylama/pylint/logilab/__init__.py similarity index 100% rename from pylibs/logilab/__init__.py rename to pylibs/pylama/pylint/logilab/__init__.py diff --git a/pylibs/logilab/astng/__init__.py b/pylibs/pylama/pylint/logilab/astng/__init__.py similarity index 75% rename from pylibs/logilab/astng/__init__.py rename to pylibs/pylama/pylint/logilab/astng/__init__.py index 12ffce33..6c4a87f1 100644 --- a/pylibs/logilab/astng/__init__.py +++ b/pylibs/pylama/pylint/logilab/astng/__init__.py @@ -52,34 +52,27 @@ # WARNING: internal imports order matters ! # make all exception classes accessible from astng package -from logilab.astng.exceptions import * +from .exceptions import * # make all node classes accessible from astng package -from logilab.astng.nodes import * +from .nodes import * # trigger extra monkey-patching -from logilab.astng import inference +from . import inference # more stuff available -from logilab.astng import raw_building -from logilab.astng.bases import YES, Instance, BoundMethod, UnboundMethod -from logilab.astng.node_classes import are_exclusive, unpack_infer -from logilab.astng.scoped_nodes import builtin_lookup +from . import raw_building +from .bases import YES, Instance, BoundMethod, UnboundMethod +from .node_classes import are_exclusive, unpack_infer +from .scoped_nodes import builtin_lookup # make a manager instance (borg) as well as Project and Package classes # accessible from astng package -from logilab.astng.manager import ASTNGManager, Project +from .manager import ASTNGManager, Project MANAGER = ASTNGManager() del ASTNGManager # load brain plugins -from os import listdir -from os.path import join, dirname -BRAIN_MODULES_DIR = join(dirname(__file__), 'brain') -if BRAIN_MODULES_DIR not in sys.path: - # add it to the end of the list so user path take precedence - sys.path.append(BRAIN_MODULES_DIR) -# load modules in this directory -for module in listdir(BRAIN_MODULES_DIR): - if module.endswith('.py'): - __import__(module[:-3]) +from .brain import py2mechanize +from .brain import py2qt4 +from .brain import py2stdlib diff --git a/pylibs/logilab/astng/__pkginfo__.py b/pylibs/pylama/pylint/logilab/astng/__pkginfo__.py similarity index 82% rename from pylibs/logilab/astng/__pkginfo__.py rename to pylibs/pylama/pylint/logilab/astng/__pkginfo__.py index c21926d9..f5097f48 100644 --- a/pylibs/logilab/astng/__pkginfo__.py +++ b/pylibs/pylama/pylint/logilab/astng/__pkginfo__.py @@ -24,7 +24,7 @@ modname = 'astng' subpackage_of = 'logilab' -numversion = (0, 24, 0) +numversion = (0, 24, 2) version = '.'.join([str(num) for num in numversion]) install_requires = ['logilab-common >= 0.53.0'] @@ -43,3 +43,10 @@ include_dirs = ['brain', join('test', 'regrtest_data'), join('test', 'data'), join('test', 'data2')] + +classifiers = ["Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: Quality Assurance", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + ] diff --git a/pylibs/logilab/astng/as_string.py b/pylibs/pylama/pylint/logilab/astng/as_string.py similarity index 100% rename from pylibs/logilab/astng/as_string.py rename to pylibs/pylama/pylint/logilab/astng/as_string.py diff --git a/pylibs/logilab/astng/bases.py b/pylibs/pylama/pylint/logilab/astng/bases.py similarity index 98% rename from pylibs/logilab/astng/bases.py rename to pylibs/pylama/pylint/logilab/astng/bases.py index 92f12aad..42d34438 100644 --- a/pylibs/logilab/astng/bases.py +++ b/pylibs/pylama/pylint/logilab/astng/bases.py @@ -26,12 +26,12 @@ from contextlib import contextmanager -from logilab.common.compat import builtins +from ..common.compat import builtins -from logilab.astng import BUILTINS_MODULE -from logilab.astng.exceptions import InferenceError, ASTNGError, \ +from . import BUILTINS_MODULE +from .exceptions import InferenceError, ASTNGError, \ NotFoundError, UnresolvableName -from logilab.astng.as_string import as_string +from .as_string import as_string BUILTINS_NAME = builtins.__name__ @@ -131,6 +131,8 @@ class _Yes(object): def __repr__(self): return 'YES' def __getattribute__(self, name): + if name == 'next': + raise AttributeError('next method should not be called') if name.startswith('__') and name.endswith('__'): # to avoid inspection pb return super(_Yes, self).__getattribute__(name) diff --git a/pylibs/pylama/pylint/logilab/astng/brain/__init__.py b/pylibs/pylama/pylint/logilab/astng/brain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pylibs/pylama/pylint/logilab/astng/brain/py2mechanize.py b/pylibs/pylama/pylint/logilab/astng/brain/py2mechanize.py new file mode 100644 index 00000000..b697de46 --- /dev/null +++ b/pylibs/pylama/pylint/logilab/astng/brain/py2mechanize.py @@ -0,0 +1,20 @@ +from .. import MANAGER +from ..builder import ASTNGBuilder + +def mechanize_transform(module): + fake = ASTNGBuilder(MANAGER).string_build(''' + +class Browser(object): + def open(self, url, data=None, timeout=None): + return None + def open_novisit(self, url, data=None, timeout=None): + return None + def open_local_file(self, filename): + return None + +''') + module.locals['Browser'] = fake.locals['Browser'] + +import py2stdlib +py2stdlib.MODULE_TRANSFORMS['mechanize'] = mechanize_transform + diff --git a/pylibs/pylama/pylint/logilab/astng/brain/py2qt4.py b/pylibs/pylama/pylint/logilab/astng/brain/py2qt4.py new file mode 100644 index 00000000..b4e302cb --- /dev/null +++ b/pylibs/pylama/pylint/logilab/astng/brain/py2qt4.py @@ -0,0 +1,25 @@ +"""ASTNG hooks for the Python 2 qt4 module. + +Currently help understanding of : + +* PyQT4.QtCore +""" + +from .. import MANAGER +from ..builder import ASTNGBuilder + + +def pyqt4_qtcore_transform(module): + fake = ASTNGBuilder(MANAGER).string_build(''' + +def SIGNAL(signal_name): pass + +class QObject(object): + def emit(self, signal): pass +''') + for klass in ('QObject',): + module.locals[klass] = fake.locals[klass] + + +import py2stdlib +py2stdlib.MODULE_TRANSFORMS['PyQt4.QtCore'] = pyqt4_qtcore_transform diff --git a/pylibs/logilab/astng/brain/py2stdlib.py b/pylibs/pylama/pylint/logilab/astng/brain/py2stdlib.py similarity index 58% rename from pylibs/logilab/astng/brain/py2stdlib.py rename to pylibs/pylama/pylint/logilab/astng/brain/py2stdlib.py index 4c1c3b7e..fb3ccdc1 100644 --- a/pylibs/logilab/astng/brain/py2stdlib.py +++ b/pylibs/pylama/pylint/logilab/astng/brain/py2stdlib.py @@ -5,8 +5,8 @@ * hashlib.md5 and hashlib.sha1 """ -from logilab.astng import MANAGER -from logilab.astng.builder import ASTNGBuilder +from .. import MANAGER +from ..builder import ASTNGBuilder MODULE_TRANSFORMS = {} @@ -95,16 +95,71 @@ def cleanup_resources(force=False): for func_name, func in fake.locals.items(): module.locals[func_name] = func - # for func in ('resource_exists', 'resource_isdir', 'resource_filename', - # 'resource_stream', 'resource_string', 'resource_listdir', - # 'extraction_error', 'get_cache_path', 'postprocess', - # 'set_extraction_path', 'cleanup_resources'): - # module.locals[func] = fake.locals[func] +def urlparse_transform(module): + fake = ASTNGBuilder(MANAGER).string_build(''' + +def urlparse(urlstring, default_scheme='', allow_fragments=True): + return ParseResult() + +class ParseResult(object): + def __init__(self): + self.scheme = '' + self.netloc = '' + self.path = '' + self.params = '' + self.query = '' + self.fragment = '' + self.username = None + self.password = None + self.hostname = None + self.port = None + + def geturl(self): + return '' +''') + + for func_name, func in fake.locals.items(): + module.locals[func_name] = func + +def subprocess_transform(module): + fake = ASTNGBuilder(MANAGER).string_build(''' + +class Popen(object): + returncode = pid = 0 + stdin = stdout = stderr = file() + + def __init__(self, args, bufsize=0, executable=None, + stdin=None, stdout=None, stderr=None, + preexec_fn=None, close_fds=False, shell=False, + cwd=None, env=None, universal_newlines=False, + startupinfo=None, creationflags=0): + pass + + def communicate(self, input=None): + return ('string', 'string') + def wait(self): + return self.returncode + def poll(self): + return self.returncode + def send_signal(self, signal): + pass + def terminate(self): + pass + def kill(self): + pass + ''') + + for func_name, func in fake.locals.items(): + module.locals[func_name] = func + + MODULE_TRANSFORMS['hashlib'] = hashlib_transform MODULE_TRANSFORMS['collections'] = collections_transform MODULE_TRANSFORMS['pkg_resources'] = pkg_resources_transform +MODULE_TRANSFORMS['urlparse'] = urlparse_transform +MODULE_TRANSFORMS['subprocess'] = subprocess_transform def transform(module): @@ -115,5 +170,7 @@ def transform(module): else: tr(module) -from logilab.astng import MANAGER +from .. import MANAGER MANAGER.register_transformer(transform) + + diff --git a/pylibs/logilab/astng/builder.py b/pylibs/pylama/pylint/logilab/astng/builder.py similarity index 96% rename from pylibs/logilab/astng/builder.py rename to pylibs/pylama/pylint/logilab/astng/builder.py index 9309793d..190d5886 100644 --- a/pylibs/logilab/astng/builder.py +++ b/pylibs/pylama/pylint/logilab/astng/builder.py @@ -28,13 +28,13 @@ import sys, re from os.path import splitext, basename, dirname, exists, abspath -from logilab.common.modutils import modpath_from_file +from ..common.modutils import modpath_from_file -from logilab.astng.exceptions import ASTNGBuildingException, InferenceError -from logilab.astng.raw_building import InspectBuilder -from logilab.astng.rebuilder import TreeRebuilder -from logilab.astng.manager import ASTNGManager -from logilab.astng.bases import YES, Instance +from .exceptions import ASTNGBuildingException, InferenceError +from .raw_building import InspectBuilder +from .rebuilder import TreeRebuilder +from .manager import ASTNGManager +from .bases import YES, Instance from _ast import PyCF_ONLY_AST def parse(string): diff --git a/pylibs/logilab/astng/exceptions.py b/pylibs/pylama/pylint/logilab/astng/exceptions.py similarity index 100% rename from pylibs/logilab/astng/exceptions.py rename to pylibs/pylama/pylint/logilab/astng/exceptions.py diff --git a/pylibs/logilab/astng/inference.py b/pylibs/pylama/pylint/logilab/astng/inference.py similarity index 97% rename from pylibs/logilab/astng/inference.py rename to pylibs/pylama/pylint/logilab/astng/inference.py index e33ea492..faf8bfb2 100644 --- a/pylibs/logilab/astng/inference.py +++ b/pylibs/pylama/pylint/logilab/astng/inference.py @@ -25,14 +25,14 @@ from itertools import chain import sys -from logilab.astng import nodes +from . import nodes -from logilab.astng.manager import ASTNGManager -from logilab.astng.exceptions import (ASTNGBuildingException, ASTNGError, +from .manager import ASTNGManager +from .exceptions import (ASTNGBuildingException, ASTNGError, InferenceError, NoDefault, NotFoundError, UnresolvableName) -from logilab.astng.bases import YES, Instance, InferenceContext, Generator, \ +from .bases import YES, Instance, InferenceContext, Generator, \ _infer_stmts, copy_context, path_wrapper, raise_if_nothing_infered -from logilab.astng.protocols import _arguments_infer_argname +from .protocols import _arguments_infer_argname MANAGER = ASTNGManager() diff --git a/pylibs/logilab/astng/manager.py b/pylibs/pylama/pylint/logilab/astng/manager.py similarity index 96% rename from pylibs/logilab/astng/manager.py rename to pylibs/pylama/pylint/logilab/astng/manager.py index 8a4f02bb..513ad708 100644 --- a/pylibs/logilab/astng/manager.py +++ b/pylibs/pylama/pylint/logilab/astng/manager.py @@ -28,12 +28,12 @@ import os from os.path import dirname, basename, abspath, join, isdir, exists -from logilab.common.modutils import NoSourceFile, is_python_source, \ +from ..common.modutils import NoSourceFile, is_python_source, \ file_from_modpath, load_module_from_name, modpath_from_file, \ get_module_files, get_source_file, zipimport -from logilab.common.configuration import OptionsProviderMixIn +from ..common.configuration import OptionsProviderMixIn -from logilab.astng.exceptions import ASTNGBuildingException +from .exceptions import ASTNGBuildingException def astng_wrapper(func, modname): """wrapper to give to ASTNGManager.project_from_files""" @@ -103,7 +103,7 @@ def astng_from_file(self, filepath, modname=None, fallback=True, source=False): if modname in self.astng_cache: return self.astng_cache[modname] if source: - from logilab.astng.builder import ASTNGBuilder + from .builder import ASTNGBuilder return ASTNGBuilder(self).file_build(filepath, modname) elif fallback and modname: return self.astng_from_module_name(modname) @@ -115,7 +115,7 @@ def astng_from_module_name(self, modname, context_file=None): if modname in self.astng_cache: return self.astng_cache[modname] if modname == '__main__': - from logilab.astng.builder import ASTNGBuilder + from .builder import ASTNGBuilder return ASTNGBuilder(self).string_build('', modname) old_cwd = os.getcwd() if context_file: @@ -140,7 +140,7 @@ def astng_from_module_name(self, modname, context_file=None): def zip_import_data(self, filepath): if zipimport is None: return None - from logilab.astng.builder import ASTNGBuilder + from .builder import ASTNGBuilder builder = ASTNGBuilder(self) for ext in ('.zip', '.egg'): try: @@ -186,7 +186,7 @@ def astng_from_module(self, module, modname=None): return self.astng_from_file(filepath, modname) except AttributeError: pass - from logilab.astng.builder import ASTNGBuilder + from .builder import ASTNGBuilder return ASTNGBuilder(self).module_build(module, modname) def astng_from_class(self, klass, modname=None): diff --git a/pylibs/logilab/astng/mixins.py b/pylibs/pylama/pylint/logilab/astng/mixins.py similarity index 98% rename from pylibs/logilab/astng/mixins.py rename to pylibs/pylama/pylint/logilab/astng/mixins.py index 869a25ad..207dc70b 100644 --- a/pylibs/logilab/astng/mixins.py +++ b/pylibs/pylama/pylint/logilab/astng/mixins.py @@ -32,7 +32,7 @@ """This module contains some mixins for the different nodes. """ -from logilab.astng.exceptions import (ASTNGBuildingException, InferenceError, +from .exceptions import (ASTNGBuildingException, InferenceError, NotFoundError) diff --git a/pylibs/logilab/astng/node_classes.py b/pylibs/pylama/pylint/logilab/astng/node_classes.py similarity index 97% rename from pylibs/logilab/astng/node_classes.py rename to pylibs/pylama/pylint/logilab/astng/node_classes.py index 607ad907..ef8c5e18 100644 --- a/pylibs/logilab/astng/node_classes.py +++ b/pylibs/pylama/pylint/logilab/astng/node_classes.py @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # copyright 2003-2010 Sylvain Thenault, all rights reserved. # contact mailto:thenault@gmail.com @@ -22,11 +22,11 @@ import sys -from logilab.astng import BUILTINS_MODULE -from logilab.astng.exceptions import NoDefault -from logilab.astng.bases import (NodeNG, Statement, Instance, InferenceContext, +from . import BUILTINS_MODULE +from .exceptions import NoDefault +from .bases import (NodeNG, Statement, Instance, InferenceContext, _infer_stmts, YES) -from logilab.astng.mixins import BlockRangeMixIn, AssignTypeMixin, \ +from .mixins import BlockRangeMixIn, AssignTypeMixin, \ ParentAssignTypeMixin, FromImportMixIn @@ -39,13 +39,18 @@ def unpack_infer(stmt, context=None): for infered_elt in unpack_infer(elt, context): yield infered_elt return + # if infered is a final node, return it and stop infered = stmt.infer(context).next() - if infered is stmt or infered is YES: + if infered is stmt: yield infered return + # else, infer recursivly, except YES object that should be returned as is for infered in stmt.infer(context): - for inf_inf in unpack_infer(infered, context): - yield inf_inf + if infered is YES: + yield infered + else: + for inf_inf in unpack_infer(infered, context): + yield inf_inf def are_exclusive(stmt1, stmt2, exceptions=None): @@ -745,7 +750,7 @@ class Slice(NodeNG): upper = None step = None -class Starred(NodeNG): +class Starred(NodeNG, ParentAssignTypeMixin): """class representing a Starred node""" _astng_fields = ('value',) value = None diff --git a/pylibs/logilab/astng/nodes.py b/pylibs/pylama/pylint/logilab/astng/nodes.py similarity index 94% rename from pylibs/logilab/astng/nodes.py rename to pylibs/pylama/pylint/logilab/astng/nodes.py index 56b9980f..39ab0412 100644 --- a/pylibs/logilab/astng/nodes.py +++ b/pylibs/pylama/pylint/logilab/astng/nodes.py @@ -39,7 +39,7 @@ __docformat__ = "restructuredtext en" -from logilab.astng.node_classes import Arguments, AssAttr, Assert, Assign, \ +from .node_classes import Arguments, AssAttr, Assert, Assign, \ AssName, AugAssign, Backquote, BinOp, BoolOp, Break, CallFunc, Compare, \ Comprehension, Const, Continue, Decorators, DelAttr, DelName, Delete, \ Dict, Discard, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, For, \ @@ -47,7 +47,7 @@ List, Name, Nonlocal, Pass, Print, Raise, Return, Set, Slice, Starred, Subscript, \ TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, \ const_factory -from logilab.astng.scoped_nodes import Module, GenExpr, Lambda, DictComp, \ +from .scoped_nodes import Module, GenExpr, Lambda, DictComp, \ ListComp, SetComp, Function, Class ALL_NODE_CLASSES = ( diff --git a/pylibs/logilab/astng/protocols.py b/pylibs/pylama/pylint/logilab/astng/protocols.py similarity index 98% rename from pylibs/logilab/astng/protocols.py rename to pylibs/pylama/pylint/logilab/astng/protocols.py index d8c02e38..00d6be81 100644 --- a/pylibs/logilab/astng/protocols.py +++ b/pylibs/pylama/pylint/logilab/astng/protocols.py @@ -23,12 +23,12 @@ __doctype__ = "restructuredtext en" -from logilab.astng.exceptions import InferenceError, NoDefault -from logilab.astng.node_classes import unpack_infer -from logilab.astng.bases import copy_context, \ +from .exceptions import InferenceError, NoDefault +from .node_classes import unpack_infer +from .bases import copy_context, \ raise_if_nothing_infered, yes_if_nothing_infered, Instance, Generator, YES -from logilab.astng.nodes import const_factory -from logilab.astng import nodes +from .nodes import const_factory +from . import nodes # unary operations ############################################################ diff --git a/pylibs/logilab/astng/raw_building.py b/pylibs/pylama/pylint/logilab/astng/raw_building.py similarity index 96% rename from pylibs/logilab/astng/raw_building.py rename to pylibs/pylama/pylint/logilab/astng/raw_building.py index 6a098c24..5f308cae 100644 --- a/pylibs/logilab/astng/raw_building.py +++ b/pylibs/pylama/pylint/logilab/astng/raw_building.py @@ -28,12 +28,12 @@ from inspect import (getargspec, isdatadescriptor, isfunction, ismethod, ismethoddescriptor, isclass, isbuiltin) -from logilab.astng import BUILTINS_MODULE -from logilab.astng.node_classes import CONST_CLS -from logilab.astng.nodes import (Module, Class, Const, const_factory, From, +from . import BUILTINS_MODULE +from .node_classes import CONST_CLS +from .nodes import (Module, Class, Const, const_factory, From, Function, EmptyNode, Name, Arguments, Dict, List, Set, Tuple) -from logilab.astng.bases import Generator -from logilab.astng.manager import ASTNGManager +from .bases import Generator +from .manager import ASTNGManager MANAGER = ASTNGManager() _CONSTANTS = tuple(CONST_CLS) # the keys of CONST_CLS eg python builtin types @@ -214,7 +214,11 @@ def inspect_build(self, module, modname=None, path=None): self._module = module if modname is None: modname = module.__name__ - node = build_module(modname, module.__doc__) + try: + node = build_module(modname, module.__doc__) + except AttributeError: + # in jython, java modules have no __doc__ (see #109562) + node = build_module(modname) node.file = node.path = path and abspath(path) or path MANAGER.astng_cache[modname] = node node.package = hasattr(module, '__path__') @@ -322,7 +326,7 @@ def astng_boot_strapping(): # this boot strapping is necessary since we need the Const nodes to # inspect_build builtins, and then we can proxy Const builder = InspectBuilder() - from logilab.common.compat import builtins + from ..common.compat import builtins astng_builtin = builder.inspect_build(builtins) for cls, node_cls in CONST_CLS.items(): if cls is type(None): diff --git a/pylibs/logilab/astng/rebuilder.py b/pylibs/pylama/pylint/logilab/astng/rebuilder.py similarity index 95% rename from pylibs/logilab/astng/rebuilder.py rename to pylibs/pylama/pylint/logilab/astng/rebuilder.py index bac7a095..f007bc90 100644 --- a/pylibs/logilab/astng/rebuilder.py +++ b/pylibs/pylama/pylint/logilab/astng/rebuilder.py @@ -1,7 +1,5 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -34,8 +32,8 @@ Eq, Gt, GtE, In, Is, IsNot, Lt, LtE, NotEq, NotIn, ) -from logilab.astng.exceptions import ASTNGBuildingException -from logilab.astng import nodes as new +from .exceptions import ASTNGBuildingException +from . import nodes as new _BIN_OP_CLASSES = {Add: '+', @@ -457,7 +455,7 @@ def visit_for(self, node, parent): def visit_from(self, node, parent): """visit a From node by returning a fresh instance of it""" names = [(alias.name, alias.asname) for alias in node.names] - newnode = new.From(node.module or '', names, node.level) + newnode = new.From(node.module or '', names, node.level or None) _set_infos(node, newnode, parent) # store From names to add them to locals after building self._from_nodes.append(newnode) @@ -788,13 +786,13 @@ def visit_while(self, node, parent): return newnode def visit_with(self, node, parent): - """visit a With node by returning a fresh instance of it""" newnode = new.With() _lineno_parent(node, newnode, parent) - newnode.expr = self.visit(node.context_expr, newnode) + _node = getattr(node, 'items', [node])[0] # python 3.3 XXX + newnode.expr = self.visit(_node.context_expr, newnode) self.asscontext = "Ass" - if node.optional_vars is not None: - newnode.vars = self.visit(node.optional_vars, newnode) + if _node.optional_vars is not None: + newnode.vars = self.visit(_node.optional_vars, newnode) self.asscontext = None newnode.body = [self.visit(child, newnode) for child in node.body] newnode.set_line_info(newnode.last_child()) @@ -857,6 +855,30 @@ def visit_starred(self, node, parent): newnode.set_line_info(newnode.last_child()) return newnode + def visit_try(self, node, parent): + # python 3.3 introduce a new Try node replacing TryFinally/TryExcept nodes + if node.finalbody: + newnode = new.TryFinally() + _lineno_parent(node, newnode, parent) + newnode.finalbody = [self.visit(n, newnode) for n in node.finalbody] + if node.handlers: + excnode = new.TryExcept() + _lineno_parent(node, excnode, parent) + excnode.body = [self.visit(child, newnode) for child in node.body] + excnode.handlers = [self.visit(child, newnode) for child in node.handlers] + excnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.body = [excnode] + else: + newnode.body = [self.visit(child, newnode) for child in node.body] + elif node.handlers: + newnode = new.TryExcept() + _lineno_parent(node, newnode, parent) + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.handlers = [self.visit(child, newnode) for child in node.handlers] + newnode.orelse = [self.visit(child, newnode) for child in node.orelse] + newnode.set_line_info(newnode.last_child()) + return newnode + if sys.version_info >= (3, 0): TreeRebuilder = TreeRebuilder3k diff --git a/pylibs/logilab/astng/scoped_nodes.py b/pylibs/pylama/pylint/logilab/astng/scoped_nodes.py similarity index 98% rename from pylibs/logilab/astng/scoped_nodes.py rename to pylibs/pylama/pylint/logilab/astng/scoped_nodes.py index 1a1242c5..5f61511b 100644 --- a/pylibs/logilab/astng/scoped_nodes.py +++ b/pylibs/pylama/pylint/logilab/astng/scoped_nodes.py @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # copyright 2003-2010 Sylvain Thenault, all rights reserved. # contact mailto:thenault@gmail.com @@ -28,21 +28,21 @@ import sys from itertools import chain -from logilab.common.compat import builtins -from logilab.common.decorators import cached +from ..common.compat import builtins +from ..common.decorators import cached -from logilab.astng import BUILTINS_MODULE -from logilab.astng.exceptions import NotFoundError, NoDefault, \ +from . import BUILTINS_MODULE +from .exceptions import NotFoundError, NoDefault, \ ASTNGBuildingException, InferenceError -from logilab.astng.node_classes import Const, DelName, DelAttr, \ +from .node_classes import Const, DelName, DelAttr, \ Dict, From, List, Name, Pass, Raise, Return, Tuple, Yield, \ are_exclusive, LookupMixIn, const_factory as cf, unpack_infer -from logilab.astng.bases import NodeNG, InferenceContext, Instance,\ +from .bases import NodeNG, InferenceContext, Instance,\ YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, copy_context, \ BUILTINS_NAME -from logilab.astng.mixins import FilterStmtsMixin -from logilab.astng.bases import Statement -from logilab.astng.manager import ASTNGManager +from .mixins import FilterStmtsMixin +from .bases import Statement +from .manager import ASTNGManager def remove_nodes(func, cls): @@ -220,6 +220,9 @@ class Module(LocalsDictNodeNG): # the file from which as been extracted the astng representation. It may # be None if the representation has been built from a built-in module file = None + # encoding of python source file, so we can get unicode out of it (python2 + # only) + file_encoding = None # the module name name = None # boolean for astng built from source (i.e. ast) @@ -540,7 +543,8 @@ def set_line_info(self, lastchild): self.fromlineno = self.lineno # lineno is the line number of the first decorator, we want the def statement lineno if self.decorators is not None: - self.fromlineno += len(self.decorators.nodes) + self.fromlineno += sum(node.tolineno - node.lineno + 1 + for node in self.decorators.nodes) self.tolineno = lastchild.tolineno self.blockstart_tolineno = self.args.tolineno diff --git a/pylibs/logilab/astng/utils.py b/pylibs/pylama/pylint/logilab/astng/utils.py similarity index 99% rename from pylibs/logilab/astng/utils.py rename to pylibs/pylama/pylint/logilab/astng/utils.py index ba317c8e..73203299 100644 --- a/pylibs/logilab/astng/utils.py +++ b/pylibs/pylama/pylint/logilab/astng/utils.py @@ -23,7 +23,7 @@ __docformat__ = "restructuredtext en" -from logilab.astng.exceptions import ASTNGBuildingException +from .exceptions import ASTNGBuildingException class ASTWalker: @@ -150,7 +150,7 @@ class TreeTester(object): Module() body = [ Print() - dest = + dest = values = [ ] ] diff --git a/pylibs/logilab/common/__init__.py b/pylibs/pylama/pylint/logilab/common/__init__.py similarity index 98% rename from pylibs/logilab/common/__init__.py rename to pylibs/pylama/pylint/logilab/common/__init__.py index 8d063e2c..b48ab3cd 100644 --- a/pylibs/logilab/common/__init__.py +++ b/pylibs/pylama/pylint/logilab/common/__init__.py @@ -25,7 +25,7 @@ :var IGNORED_EXTENSIONS: file extensions that may usually be ignored """ __docformat__ = "restructuredtext en" -from logilab.common.__pkginfo__ import version as __version__ +from .__pkginfo__ import version as __version__ STD_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build') diff --git a/pylibs/logilab/common/__pkginfo__.py b/pylibs/pylama/pylint/logilab/common/__pkginfo__.py similarity index 91% rename from pylibs/logilab/common/__pkginfo__.py rename to pylibs/pylama/pylint/logilab/common/__pkginfo__.py index d6c63323..fc3cb69a 100644 --- a/pylibs/logilab/common/__pkginfo__.py +++ b/pylibs/pylama/pylint/logilab/common/__pkginfo__.py @@ -18,19 +18,19 @@ """logilab.common packaging information""" __docformat__ = "restructuredtext en" import sys +import os distname = 'logilab-common' modname = 'common' subpackage_of = 'logilab' subpackage_master = True -numversion = (0, 58, 0) +numversion = (0, 58, 3) version = '.'.join([str(num) for num in numversion]) license = 'LGPL' # 2.1 or later description = "collection of low-level Python packages and modules used by Logilab projects" web = "http://www.logilab.org/project/%s" % distname -ftp = "ftp://ftp.logilab.org/pub/%s" % modname mailinglist = "mailto://python-projects@lists.logilab.org" author = "Logilab" author_email = "contact@logilab.fr" @@ -40,8 +40,11 @@ scripts = [join('bin', 'pytest')] include_dirs = [join('test', 'data')] +install_requires = [] if sys.version_info < (2, 7): - install_requires = ['unittest2 >= 0.5.1'] + install_requires.append('unittest2 >= 0.5.1') +if os.name == 'nt': + install_requires.append('colorama') classifiers = ["Topic :: Utilities", "Programming Language :: Python", diff --git a/pylibs/logilab/common/changelog.py b/pylibs/pylama/pylint/logilab/common/changelog.py similarity index 100% rename from pylibs/logilab/common/changelog.py rename to pylibs/pylama/pylint/logilab/common/changelog.py diff --git a/pylibs/logilab/common/compat.py b/pylibs/pylama/pylint/logilab/common/compat.py similarity index 99% rename from pylibs/logilab/common/compat.py rename to pylibs/pylama/pylint/logilab/common/compat.py index 8983ece9..c8a628a9 100644 --- a/pylibs/logilab/common/compat.py +++ b/pylibs/pylama/pylint/logilab/common/compat.py @@ -90,7 +90,7 @@ def method_type(callable, instance, klass): except ImportError: import pickle -from logilab.common.deprecation import deprecated +from .deprecation import deprecated from itertools import izip, chain, imap if sys.version_info < (3, 0):# 2to3 will remove the imports diff --git a/pylibs/logilab/common/configuration.py b/pylibs/pylama/pylint/logilab/common/configuration.py similarity index 98% rename from pylibs/logilab/common/configuration.py rename to pylibs/pylama/pylint/logilab/common/configuration.py index 0eafa10a..9aa5404c 100644 --- a/pylibs/logilab/common/configuration.py +++ b/pylibs/pylama/pylint/logilab/common/configuration.py @@ -1,4 +1,4 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of logilab-common. @@ -113,10 +113,10 @@ DuplicateSectionError from warnings import warn -from logilab.common.compat import callable, raw_input, str_encode as _encode +from .compat import callable, raw_input, str_encode as _encode -from logilab.common.textutils import normalize_text, unquote -from logilab.common import optik_ext as optparse +from .textutils import normalize_text, unquote +from . import optik_ext as optparse OptionError = optparse.OptionError @@ -1055,8 +1055,13 @@ def read_old_config(newconfig, changes, configfile): newconfig.set_option(optname, oldconfig[optname], optdict=optdef) -def merge_options(options): - """preprocess options to remove duplicate""" +def merge_options(options, optgroup=None): + """preprocess a list of options and remove duplicates, returning a new list + (tuple actually) of options. + + Options dictionaries are copied to avoid later side-effect. Also, if + `otpgroup` argument is specified, ensure all options are in the given group. + """ alloptions = {} options = list(options) for i in range(len(options)-1, -1, -1): @@ -1065,5 +1070,9 @@ def merge_options(options): options.pop(i) alloptions[optname].update(optdict) else: + optdict = optdict.copy() + options[i] = (optname, optdict) alloptions[optname] = optdict + if optgroup is not None: + alloptions[optname]['group'] = optgroup return tuple(options) diff --git a/pylibs/logilab/common/decorators.py b/pylibs/pylama/pylint/logilab/common/decorators.py similarity index 99% rename from pylibs/logilab/common/decorators.py rename to pylibs/pylama/pylint/logilab/common/decorators.py index 43c36521..84ea90ba 100644 --- a/pylibs/logilab/common/decorators.py +++ b/pylibs/pylama/pylint/logilab/common/decorators.py @@ -21,7 +21,7 @@ import sys from time import clock, time -from logilab.common.compat import callable, method_type +from .compat import callable, method_type # XXX rewrite so we can use the decorator syntax when keyarg has to be specified diff --git a/pylibs/pylama/pylint/logilab/common/deprecation.py b/pylibs/pylama/pylint/logilab/common/deprecation.py new file mode 100644 index 00000000..d9b6f547 --- /dev/null +++ b/pylibs/pylama/pylint/logilab/common/deprecation.py @@ -0,0 +1,188 @@ +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of logilab-common. +# +# logilab-common is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation, either version 2.1 of the License, or (at your option) any +# later version. +# +# logilab-common 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 Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with logilab-common. If not, see . +"""Deprecation utilities.""" + +__docformat__ = "restructuredtext en" + +import sys +from warnings import warn + +from .changelog import Version + + +class DeprecationWrapper(object): + """proxy to print a warning on access to any attribute of the wrapped object + """ + def __init__(self, proxied, msg=None): + self._proxied = proxied + self._msg = msg + + def __getattr__(self, attr): + warn(self._msg, DeprecationWarning, stacklevel=2) + return getattr(self._proxied, attr) + + def __setattr__(self, attr, value): + if attr in ('_proxied', '_msg'): + self.__dict__[attr] = value + else: + warn(self._msg, DeprecationWarning, stacklevel=2) + setattr(self._proxied, attr, value) + + +class DeprecationManager(object): + """Manage the deprecation message handling. Messages are dropped for + versions more recent than the 'compatible' version. Example:: + + deprecator = deprecation.DeprecationManager("module_name") + deprecator.compatibility('1.3') + + deprecator.warn('1.2', "message.") + + @deprecator.deprecated('1.2', 'Message') + def any_func(): + pass + + class AnyClass(object): + __metaclass__ = deprecator.class_deprecated('1.2') + """ + def __init__(self, module_name=None): + """ + """ + self.module_name = module_name + self.compatible_version = None + + def compatibility(self, compatible_version): + """Set the compatible version. + """ + self.compatible_version = compatible_version + + def deprecated(self, version=None, reason=None, stacklevel=2, name=None, doc=None): + """Display a deprecation message only if the version is older than the + compatible version. + """ + def decorator(func): + message = reason or 'The function "%s" is deprecated' + if '%s' in message: + message %= func.func_name + def wrapped(*args, **kwargs): + self.warn(version, message, stacklevel) + return func(*args, **kwargs) + return wrapped + return decorator + + def class_deprecated(self, version=None): + class metaclass(type): + """metaclass to print a warning on instantiation of a deprecated class""" + + def __call__(cls, *args, **kwargs): + msg = getattr(cls, "__deprecation_warning__", + "%(cls)s is deprecated") % {'cls': cls.__name__} + self.warn(version, msg) + return type.__call__(cls, *args, **kwargs) + return metaclass + + def moved(self, version, modpath, objname): + """use to tell that a callable has been moved to a new module. + + It returns a callable wrapper, so that when its called a warning is printed + telling where the object can be found, import is done (and not before) and + the actual object is called. + + NOTE: the usage is somewhat limited on classes since it will fail if the + wrapper is use in a class ancestors list, use the `class_moved` function + instead (which has no lazy import feature though). + """ + def callnew(*args, **kwargs): + from logilab.common.modutils import load_module_from_name + message = "object %s has been moved to module %s" % (objname, modpath) + self.warn(version, message) + m = load_module_from_name(modpath) + return getattr(m, objname)(*args, **kwargs) + return callnew + + def class_renamed(self, version, old_name, new_class, message=None): + clsdict = {} + if message is None: + message = '%s is deprecated, use %s' % (old_name, new_class.__name__) + clsdict['__deprecation_warning__'] = message + try: + # new-style class + return self.class_deprecated(version)(old_name, (new_class,), clsdict) + except (NameError, TypeError): + # old-style class + class DeprecatedClass(new_class): + """FIXME: There might be a better way to handle old/new-style class + """ + def __init__(self, *args, **kwargs): + self.warn(version, message) + new_class.__init__(self, *args, **kwargs) + return DeprecatedClass + + def class_moved(self, version, new_class, old_name=None, message=None): + """nice wrapper around class_renamed when a class has been moved into + another module + """ + if old_name is None: + old_name = new_class.__name__ + if message is None: + message = 'class %s is now available as %s.%s' % ( + old_name, new_class.__module__, new_class.__name__) + return self.class_renamed(version, old_name, new_class, message) + + def warn(self, version=None, reason="", stacklevel=2): + """Display a deprecation message only if the version is older than the + compatible version. + """ + if self.module_name and version: + reason = '[%s %s] %s' % (self.module_name, version, reason) + elif self.module_name: + reason = '[%s] %s' % (self.module_name, reason) + elif version: + reason = '[%s] %s' % (version, reason) + if (self.compatible_version is None + or version is None + or Version(version) < Version(self.compatible_version)): + warn(reason, DeprecationWarning, stacklevel=stacklevel) + +_defaultdeprecator = DeprecationManager() + +def deprecated(reason=None, stacklevel=2, name=None, doc=None): + return _defaultdeprecator.deprecated(None, reason, stacklevel, name, doc) + +class_deprecated = _defaultdeprecator.class_deprecated() + +def moved(modpath, objname): + return _defaultdeprecator.moved(None, modpath, objname) +moved.__doc__ = _defaultdeprecator.moved.__doc__ + +def class_renamed(old_name, new_class, message=None): + """automatically creates a class which fires a DeprecationWarning + when instantiated. + + >>> Set = class_renamed('Set', set, 'Set is now replaced by set') + >>> s = Set() + sample.py:57: DeprecationWarning: Set is now replaced by set + s = Set() + >>> + """ + return _defaultdeprecator.class_renamed(None, old_name, new_class, message) + +def class_moved(new_class, old_name=None, message=None): + return _defaultdeprecator.class_moved(None, new_class, old_name, message) +class_moved.__doc__ = _defaultdeprecator.class_moved.__doc__ + diff --git a/pylibs/logilab/common/graph.py b/pylibs/pylama/pylint/logilab/common/graph.py similarity index 99% rename from pylibs/logilab/common/graph.py rename to pylibs/pylama/pylint/logilab/common/graph.py index 75a2ee7a..0bb9f38e 100644 --- a/pylibs/logilab/common/graph.py +++ b/pylibs/pylama/pylint/logilab/common/graph.py @@ -28,7 +28,7 @@ import os import sys import tempfile -from logilab.common.compat import str_encode +from .compat import str_encode def escape(value): """Make usable in a dot file.""" diff --git a/pylibs/logilab/common/interface.py b/pylibs/pylama/pylint/logilab/common/interface.py similarity index 100% rename from pylibs/logilab/common/interface.py rename to pylibs/pylama/pylint/logilab/common/interface.py diff --git a/pylibs/logilab/common/modutils.py b/pylibs/pylama/pylint/logilab/common/modutils.py similarity index 96% rename from pylibs/logilab/common/modutils.py rename to pylibs/pylama/pylint/logilab/common/modutils.py index 95c400a6..caf14781 100644 --- a/pylibs/logilab/common/modutils.py +++ b/pylibs/pylama/pylint/logilab/common/modutils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of logilab-common. @@ -34,6 +34,7 @@ from os.path import splitext, join, abspath, isdir, dirname, exists, basename from imp import find_module, load_module, C_BUILTIN, PY_COMPILED, PKG_DIRECTORY from distutils.sysconfig import get_config_var, get_python_lib, get_python_version +from distutils.errors import DistutilsPlatformError try: import zipimport @@ -42,7 +43,7 @@ ZIPFILE = object() -from logilab.common import STD_BLACKLIST, _handle_blacklist +from . import STD_BLACKLIST, _handle_blacklist # Notes about STD_LIB_DIR # Consider arch-specific installation for STD_LIB_DIR definition @@ -53,12 +54,18 @@ if sys.platform.startswith('win'): PY_SOURCE_EXTS = ('py', 'pyw') PY_COMPILED_EXTS = ('dll', 'pyd') - STD_LIB_DIR = get_python_lib(standard_lib=1) else: PY_SOURCE_EXTS = ('py',) PY_COMPILED_EXTS = ('so',) - # extend lib dir with some arch-dependant paths - STD_LIB_DIR = join(get_config_var("LIBDIR"), "python%s" % get_python_version()) + +try: + STD_LIB_DIR = get_python_lib(standard_lib=1) +# get_python_lib(standard_lib=1) is not available on pypy, set STD_LIB_DIR to +# non-valid path, see https://bugs.pypy.org/issue1164 +except DistutilsPlatformError: + STD_LIB_DIR = '//' + +EXT_LIB_DIR = get_python_lib() BUILTIN_MODULES = dict(zip(sys.builtin_module_names, [1]*len(sys.builtin_module_names))) @@ -151,6 +158,9 @@ def load_module_from_modpath(parts, path=None, use_sys=1): if len(modpath) != len(parts): # even with use_sys=False, should try to get outer packages from sys.modules module = sys.modules.get(curname) + elif use_sys: + # because it may have been indirectly loaded through a parent + module = sys.modules.get(curname) if module is None: mp_file, mp_filename, mp_desc = find_module(part, path) module = load_module(curname, mp_file, mp_filename, mp_desc) @@ -230,10 +240,7 @@ def modpath_from_file(filename, extrapath=None): return extrapath[path_].split('.') + submodpath for path in sys.path: path = abspath(path) - if path and base[:len(path)] == path: - if filename.find('site-packages') != -1 and \ - path.find('site-packages') == -1: - continue + if path and base.startswith(path): modpath = [pkg for pkg in base[len(path):].split(os.sep) if pkg] if _check_init(path, modpath[:-1]): return modpath @@ -493,13 +500,11 @@ def is_standard_module(modname, std_path=(STD_LIB_DIR,)): if filename is None: return 1 filename = abspath(filename) + if filename.startswith(EXT_LIB_DIR): + return 0 for path in std_path: - path = abspath(path) - if filename.startswith(path): - pfx_len = len(path) - if filename[pfx_len+1:pfx_len+14] != 'site-packages': - return 1 - return 0 + if filename.startswith(abspath(path)): + return 1 return False diff --git a/pylibs/logilab/common/optik_ext.py b/pylibs/pylama/pylint/logilab/common/optik_ext.py similarity index 99% rename from pylibs/logilab/common/optik_ext.py rename to pylibs/pylama/pylint/logilab/common/optik_ext.py index 39bbe18d..8a735e0a 100644 --- a/pylibs/logilab/common/optik_ext.py +++ b/pylibs/pylama/pylint/logilab/common/optik_ext.py @@ -68,7 +68,7 @@ OPTPARSE_FORMAT_DEFAULT = sys.version_info >= (2, 4) -from logilab.common.textutils import splitstrip +from .textutils import splitstrip def check_regexp(option, opt, value): """check a regexp value by trying to compile it diff --git a/pylibs/logilab/common/textutils.py b/pylibs/pylama/pylint/logilab/common/textutils.py similarity index 99% rename from pylibs/logilab/common/textutils.py rename to pylibs/pylama/pylint/logilab/common/textutils.py index f55c0040..1d77a4ce 100644 --- a/pylibs/logilab/common/textutils.py +++ b/pylibs/pylama/pylint/logilab/common/textutils.py @@ -53,7 +53,7 @@ except ImportError: linesep = '\n' # gae -from logilab.common.deprecation import deprecated +from .deprecation import deprecated MANUAL_UNICODE_MAP = { u'\xa1': u'!', # INVERTED EXCLAMATION MARK diff --git a/pylibs/logilab/common/tree.py b/pylibs/pylama/pylint/logilab/common/tree.py similarity index 98% rename from pylibs/logilab/common/tree.py rename to pylibs/pylama/pylint/logilab/common/tree.py index 885eb0fa..60698d62 100644 --- a/pylibs/logilab/common/tree.py +++ b/pylibs/pylama/pylint/logilab/common/tree.py @@ -25,8 +25,8 @@ import sys -from logilab.common import flatten -from logilab.common.visitor import VisitedMixIn, FilteredIterator, no_filter +from . import flatten +from .visitor import VisitedMixIn, FilteredIterator, no_filter ## Exceptions ################################################################# diff --git a/pylibs/logilab/common/ureports/__init__.py b/pylibs/pylama/pylint/logilab/common/ureports/__init__.py similarity index 96% rename from pylibs/logilab/common/ureports/__init__.py rename to pylibs/pylama/pylint/logilab/common/ureports/__init__.py index dcffcfa3..120d0911 100644 --- a/pylibs/logilab/common/ureports/__init__.py +++ b/pylibs/pylama/pylint/logilab/common/ureports/__init__.py @@ -27,7 +27,7 @@ from cStringIO import StringIO from StringIO import StringIO as UStringIO -from logilab.common.textutils import linesep +from ..textutils import linesep def get_nodes(node, klass): @@ -169,6 +169,6 @@ def writeln(data=''): del self.writeln -from logilab.common.ureports.nodes import * -from logilab.common.ureports.text_writer import TextWriter -from logilab.common.ureports.html_writer import HTMLWriter +from .nodes import * +from .text_writer import TextWriter +from .html_writer import HTMLWriter diff --git a/pylibs/logilab/common/ureports/docbook_writer.py b/pylibs/pylama/pylint/logilab/common/ureports/docbook_writer.py similarity index 99% rename from pylibs/logilab/common/ureports/docbook_writer.py rename to pylibs/pylama/pylint/logilab/common/ureports/docbook_writer.py index e75cbe09..04c66dbd 100644 --- a/pylibs/logilab/common/ureports/docbook_writer.py +++ b/pylibs/pylama/pylint/logilab/common/ureports/docbook_writer.py @@ -19,7 +19,7 @@ from __future__ import generators __docformat__ = "restructuredtext en" -from logilab.common.ureports import HTMLWriter +from ..ureports import HTMLWriter class DocbookWriter(HTMLWriter): """format layouts as HTML""" diff --git a/pylibs/logilab/common/ureports/html_writer.py b/pylibs/pylama/pylint/logilab/common/ureports/html_writer.py similarity index 99% rename from pylibs/logilab/common/ureports/html_writer.py rename to pylibs/pylama/pylint/logilab/common/ureports/html_writer.py index 1d095034..4e7a3ba6 100644 --- a/pylibs/logilab/common/ureports/html_writer.py +++ b/pylibs/pylama/pylint/logilab/common/ureports/html_writer.py @@ -20,7 +20,7 @@ from cgi import escape -from logilab.common.ureports import BaseWriter +from ..ureports import BaseWriter class HTMLWriter(BaseWriter): diff --git a/pylibs/logilab/common/ureports/nodes.py b/pylibs/pylama/pylint/logilab/common/ureports/nodes.py similarity index 99% rename from pylibs/logilab/common/ureports/nodes.py rename to pylibs/pylama/pylint/logilab/common/ureports/nodes.py index d63b5828..a4470dc4 100644 --- a/pylibs/logilab/common/ureports/nodes.py +++ b/pylibs/pylama/pylint/logilab/common/ureports/nodes.py @@ -21,7 +21,7 @@ """ __docformat__ = "restructuredtext en" -from logilab.common.tree import VNode +from ..tree import VNode class BaseComponent(VNode): """base report component diff --git a/pylibs/logilab/common/ureports/text_writer.py b/pylibs/pylama/pylint/logilab/common/ureports/text_writer.py similarity index 98% rename from pylibs/logilab/common/ureports/text_writer.py rename to pylibs/pylama/pylint/logilab/common/ureports/text_writer.py index 04c8f263..a6810957 100644 --- a/pylibs/logilab/common/ureports/text_writer.py +++ b/pylibs/pylama/pylint/logilab/common/ureports/text_writer.py @@ -18,8 +18,8 @@ """Text formatting drivers for ureports""" __docformat__ = "restructuredtext en" -from logilab.common.textutils import linesep -from logilab.common.ureports import BaseWriter +from ..textutils import linesep +from ..ureports import BaseWriter TITLE_UNDERLINES = ['', '=', '-', '`', '.', '~', '^'] diff --git a/pylibs/logilab/common/visitor.py b/pylibs/pylama/pylint/logilab/common/visitor.py similarity index 100% rename from pylibs/logilab/common/visitor.py rename to pylibs/pylama/pylint/logilab/common/visitor.py diff --git a/pylibs/pylint/reporters/__init__.py b/pylibs/pylama/pylint/reporters/__init__.py similarity index 78% rename from pylibs/pylint/reporters/__init__.py rename to pylibs/pylama/pylint/reporters/__init__.py index 8f54820f..99cf2b7c 100644 --- a/pylibs/pylint/reporters/__init__.py +++ b/pylibs/pylama/pylint/reporters/__init__.py @@ -1,5 +1,5 @@ # Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # 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 @@ -18,6 +18,11 @@ CMPS = ['=', '-', '+'] +# py3k has no more cmp builtin +if sys.version_info >= (3, 0): + def cmp(a, b): + return (a > b) - (a < b) + def diff_string(old, new): """given a old and new int value, return a string representing the difference @@ -31,18 +36,38 @@ class EmptyReport(Exception): """raised when a report is empty and so should not be displayed""" class BaseReporter: - """base class for reporters""" + """base class for reporters + + symbols: show short symbolic names for messages. + """ extension = '' def __init__(self, output=None): self.linter = None self.include_ids = None + self.symbols = None self.section = 0 self.out = None self.out_encoding = None self.set_output(output) + def make_sigle(self, msg_id): + """generate a short prefix for a message. + + The sigle can include the id, the symbol, or both, or it can just be + the message class. + """ + if self.include_ids: + sigle = msg_id + else: + sigle = msg_id[0] + if self.symbols: + symbol = self.linter.check_message_id(msg_id).symbol + if symbol: + sigle += '(%s)' % symbol + return sigle + def set_output(self, output=None): """set output stream""" self.out = output or sys.stdout diff --git a/pylibs/pylint/reporters/guireporter.py b/pylibs/pylama/pylint/reporters/guireporter.py similarity index 76% rename from pylibs/pylint/reporters/guireporter.py rename to pylibs/pylama/pylint/reporters/guireporter.py index 4e98fefe..8a51c08a 100644 --- a/pylibs/pylint/reporters/guireporter.py +++ b/pylibs/pylama/pylint/reporters/guireporter.py @@ -2,9 +2,9 @@ import sys -from pylint.interfaces import IReporter -from pylint.reporters import BaseReporter -from logilab.common.ureports import TextWriter +from ..interfaces import IReporter +from . import BaseReporter +from ..logilab.common.ureports import TextWriter class GUIReporter(BaseReporter): @@ -22,11 +22,7 @@ def __init__(self, gui, output=sys.stdout): def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" module, obj, line, col_offset = location[1:] - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] - + sigle = self.make_sigle(msg_id) full_msg = [sigle, module, obj, str(line), msg] self.msgs += [[sigle, module, obj, str(line)]] self.gui.msg_queue.put(full_msg) diff --git a/pylibs/pylint/reporters/html.py b/pylibs/pylama/pylint/reporters/html.py similarity index 90% rename from pylibs/pylint/reporters/html.py rename to pylibs/pylama/pylint/reporters/html.py index 56efcd6d..ef4db748 100644 --- a/pylibs/pylint/reporters/html.py +++ b/pylibs/pylama/pylint/reporters/html.py @@ -17,10 +17,10 @@ import sys from cgi import escape -from logilab.common.ureports import HTMLWriter, Section, Table +from ..logilab.common.ureports import HTMLWriter, Section, Table -from pylint.interfaces import IReporter -from pylint.reporters import BaseReporter +from ..interfaces import IReporter +from . import BaseReporter class HTMLReporter(BaseReporter): @@ -36,10 +36,7 @@ def __init__(self, output=sys.stdout): def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" module, obj, line, col_offset = location[1:] - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] + sigle = self.make_sigle(msg_id) self.msgs += [sigle, module, obj, str(line), str(col_offset), escape(msg)] def set_output(self, output=None): diff --git a/pylibs/pylint/reporters/text.py b/pylibs/pylama/pylint/reporters/text.py similarity index 87% rename from pylibs/pylint/reporters/text.py rename to pylibs/pylama/pylint/reporters/text.py index dd4d536b..5b94dd69 100644 --- a/pylibs/pylint/reporters/text.py +++ b/pylibs/pylama/pylint/reporters/text.py @@ -25,11 +25,11 @@ import os import sys -from logilab.common.ureports import TextWriter -from logilab.common.textutils import colorize_ansi +from ..logilab.common.ureports import TextWriter +from ..logilab.common.textutils import colorize_ansi -from pylint.interfaces import IReporter -from pylint.reporters import BaseReporter +from ..interfaces import IReporter +from . import BaseReporter TITLE_UNDERLINES = ['', '=', '-', '.'] @@ -41,7 +41,7 @@ class TextReporter(BaseReporter): __implements__ = IReporter extension = 'txt' - def __init__(self, output=sys.stdout): + def __init__(self, output=None): BaseReporter.__init__(self, output) self._modules = {} @@ -56,10 +56,7 @@ def add_message(self, msg_id, location, msg): self.writeln('************* %s' % module) if obj: obj = ':%s' % obj - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] + sigle = self.make_sigle(msg_id) self.writeln('%s:%3s,%s%s: %s' % (sigle, line, col_offset, obj, msg)) def _display(self, layout): @@ -76,7 +73,7 @@ class ParseableTextReporter(TextReporter): """ line_format = '%(path)s:%(line)s: [%(sigle)s%(obj)s] %(msg)s' - def __init__(self, output=sys.stdout, relative=True): + def __init__(self, output=None, relative=True): TextReporter.__init__(self, output) if relative: self._prefix = os.getcwd() + os.sep @@ -88,14 +85,12 @@ def add_message(self, msg_id, location, msg): path, _, obj, line, _ = location if obj: obj = ', %s' % obj - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] + sigle = self.make_sigle(msg_id) if self._prefix: path = path.replace(self._prefix, '') self.writeln(self.line_format % locals()) + class VSTextReporter(ParseableTextReporter): """Visual studio text reporter""" line_format = '%(path)s(%(line)s): [%(sigle)s%(obj)s] %(msg)s' @@ -113,7 +108,7 @@ class ColorizedTextReporter(TextReporter): 'S' : ("yellow", "inverse"), # S stands for module Separator } - def __init__(self, output=sys.stdout, color_mapping = None): + def __init__(self, output=None, color_mapping=None): TextReporter.__init__(self, output) self.color_mapping = color_mapping or \ dict(ColorizedTextReporter.COLOR_MAPPING) @@ -145,10 +140,7 @@ def add_message(self, msg_id, location, msg): self._modules[module] = 1 if obj: obj = ':%s' % obj - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] + sigle = self.make_sigle(msg_id) color, style = self._get_decoration(sigle) msg = colorize_ansi(msg, color, style) sigle = colorize_ansi(sigle, color, style) diff --git a/pylibs/pylint/utils.py b/pylibs/pylama/pylint/utils.py similarity index 80% rename from pylibs/pylint/utils.py rename to pylibs/pylama/pylint/utils.py index b09b3e91..532b686f 100644 --- a/pylibs/pylint/utils.py +++ b/pylibs/pylama/pylint/utils.py @@ -19,18 +19,18 @@ """ import sys -from os import linesep +from warnings import warn from os.path import dirname, basename, splitext, exists, isdir, join, normpath -from logilab.common.modutils import modpath_from_file, get_module_files, \ +from .logilab.common.modutils import modpath_from_file, get_module_files, \ file_from_modpath -from logilab.common.textutils import normalize_text -from logilab.common.configuration import rest_format_section -from logilab.common.ureports import Section +from .logilab.common.textutils import normalize_text +from .logilab.common.configuration import rest_format_section +from .logilab.common.ureports import Section -from logilab.astng import nodes, Module +from .logilab.astng import nodes, Module -from pylint.checkers import EmptyReport +from .checkers import EmptyReport class UnknownMessage(Exception): @@ -57,6 +57,8 @@ class UnknownMessage(Exception): } _MSG_ORDER = 'EWRCIF' +MSG_STATE_SCOPE_CONFIG = 0 +MSG_STATE_SCOPE_MODULE = 1 def sort_msgs(msgids): """sort message identifiers according to their category first""" @@ -93,7 +95,7 @@ def category_id(id): class Message: - def __init__(self, checker, msgid, msg, descr): + def __init__(self, checker, msgid, msg, descr, symbol): assert len(msgid) == 5, 'Invalid message id %s' % msgid assert msgid[0] in MSG_TYPES, \ 'Bad message type %s in %r' % (msgid[0], msgid) @@ -101,6 +103,7 @@ def __init__(self, checker, msgid, msg, descr): self.msg = msg self.descr = descr self.checker = checker + self.symbol = symbol class MessagesHandlerMixIn: """a mix-in class containing all the messages related methods for the main @@ -110,10 +113,14 @@ class MessagesHandlerMixIn: def __init__(self): # dictionary of registered messages self._messages = {} + # dictionary from string symbolic id to Message object. + self._messages_by_symbol = {} self._msgs_state = {} self._module_msgs_state = {} # None + self._raw_module_msgs_state = {} self._msgs_by_category = {} self.msg_status = 0 + self._ignored_msgs = {} def register_messages(self, checker): """register a dictionary of messages @@ -126,14 +133,27 @@ def register_messages(self, checker): """ msgs_dict = checker.msgs chkid = None - for msgid, (msg, msgdescr) in msgs_dict.items(): + for msgid, msg_tuple in msgs_dict.iteritems(): + if len(msg_tuple) == 3: + (msg, msgsymbol, msgdescr) = msg_tuple + assert msgsymbol not in self._messages_by_symbol, \ + 'Message symbol %r is already defined' % msgsymbol + else: + # messages should have a symbol, but for backward compatibility + # they may not. + (msg, msgdescr) = msg_tuple + warn("[pylint 0.26] description of message %s doesn't include " + "a symbolic name" % msgid, DeprecationWarning) + msgsymbol = None # avoid duplicate / malformed ids assert msgid not in self._messages, \ 'Message id %r is already defined' % msgid assert chkid is None or chkid == msgid[1:3], \ 'Inconsistent checker part in message id %r' % msgid chkid = msgid[1:3] - self._messages[msgid] = Message(checker, msgid, msg, msgdescr) + msg = Message(checker, msgid, msg, msgdescr, msgsymbol) + self._messages[msgid] = msg + self._messages_by_symbol[msgsymbol] = msg self._msgs_by_category.setdefault(msgid[0], []).append(msgid) def get_message_help(self, msgid, checkerref=False): @@ -144,31 +164,40 @@ def get_message_help(self, msgid, checkerref=False): desc += ' This message belongs to the %s checker.' % \ msg.checker.name title = msg.msg + if msg.symbol: + symbol_part = ' (%s)' % msg.symbol + else: + symbol_part = '' if title != '%s': title = title.splitlines()[0] - return ':%s: *%s*\n%s' % (msg.msgid, title, desc) - return ':%s:\n%s' % (msg.msgid, desc) + return ':%s%s: *%s*\n%s' % (msg.msgid, symbol_part, title, desc) + return ':%s%s:\n%s' % (msg.msgid, symbol_part, desc) def disable(self, msgid, scope='package', line=None): """don't output message of the given id""" assert scope in ('package', 'module') + # handle disable=all by disabling all categories + if msgid == 'all': + for msgid in MSG_TYPES: + self.disable(msgid, scope, line) + return # msgid is a category? catid = category_id(msgid) if catid is not None: - for msgid in self._msgs_by_category.get(catid): - self.disable(msgid, scope, line) + for _msgid in self._msgs_by_category.get(catid): + self.disable(_msgid, scope, line) return # msgid is a checker name? if msgid.lower() in self._checkers: for checker in self._checkers[msgid.lower()]: - for msgid in checker.msgs: - self.disable(msgid, scope, line) + for _msgid in checker.msgs: + self.disable(_msgid, scope, line) return # msgid is report id? if msgid.lower().startswith('rp'): self.disable_report(msgid) return - # msgid is a msgid. + # msgid is a symbolic or numeric msgid. msg = self.check_message_id(msgid) if scope == 'module': assert line > 0 @@ -183,7 +212,7 @@ def disable(self, msgid, scope='package', line=None): msgs = self._msgs_state msgs[msg.msgid] = False # sync configuration object - self.config.disable_msg = [mid for mid, val in msgs.items() + self.config.disable_msg = [mid for mid, val in msgs.iteritems() if not val] def enable(self, msgid, scope='package', line=None): @@ -205,7 +234,7 @@ def enable(self, msgid, scope='package', line=None): if msgid.lower().startswith('rp'): self.enable_report(msgid) return - # msgid is a msgid. + # msgid is a symbolic or numeric msgid. msg = self.check_message_id(msgid) if scope == 'module': assert line > 0 @@ -218,20 +247,50 @@ def enable(self, msgid, scope='package', line=None): msgs = self._msgs_state msgs[msg.msgid] = True # sync configuration object - self.config.enable = [mid for mid, val in msgs.items() if val] + self.config.enable = [mid for mid, val in msgs.iteritems() if val] def check_message_id(self, msgid): - """raise UnknownMessage if the message id is not defined""" + """returns the Message object for this message. + + msgid may be either a numeric or symbolic id. + + Raises UnknownMessage if the message id is not defined. + """ + if msgid in self._messages_by_symbol: + return self._messages_by_symbol[msgid] msgid = msgid.upper() try: return self._messages[msgid] except KeyError: raise UnknownMessage('No such message id %s' % msgid) + def get_msg_display_string(self, msgid): + """Generates a user-consumable representation of a message. + + Can be just the message ID or the ID and the symbol. + """ + if self.config.symbols: + symbol = self.check_message_id(msgid).symbol + if symbol: + msgid += '(%s)' % symbol + return msgid + + def get_message_state_scope(self, msgid, line=None): + """Returns the scope at which a message was enabled/disabled.""" + try: + if line in self._module_msgs_state[msgid]: + return MSG_STATE_SCOPE_MODULE + except (KeyError, TypeError): + return MSG_STATE_SCOPE_CONFIG + def is_message_enabled(self, msgid, line=None): """return true if the message associated to the given message id is enabled + + msgid may be either a numeric or symbolic message id. """ + if msgid in self._messages_by_symbol: + msgid = self._messages_by_symbol[msgid].msgid if line is None: return self._msgs_state.get(msgid, True) try: @@ -239,6 +298,20 @@ def is_message_enabled(self, msgid, line=None): except (KeyError, TypeError): return self._msgs_state.get(msgid, True) + def handle_ignored_message(self, state_scope, msgid, line, node, args): + """Report an ignored message. + + state_scope is either MSG_STATE_SCOPE_MODULE or MSG_STATE_SCOPE_CONFIG, + depending on whether the message was disabled locally in the module, + or globally. The other arguments are the same as for add_message. + """ + if state_scope == MSG_STATE_SCOPE_MODULE: + try: + orig_line = self._suppression_mapping[(msgid, line)] + self._ignored_msgs.setdefault((msgid, orig_line), set()).add(line) + except KeyError: + pass + def add_message(self, msgid, line=None, node=None, args=None): """add the message corresponding to the given id. @@ -255,6 +328,8 @@ def add_message(self, msgid, line=None, node=None, args=None): col_offset = None # should this message be displayed if not self.is_message_enabled(msgid, line): + self.handle_ignored_message( + self.get_message_state_scope(msgid, line), msgid, line, node, args) return # update stats msg_cat = MSG_TYPES[msgid[0]] @@ -317,7 +392,7 @@ def print_full_documentation(self): by_checker[checker.name] = [list(checker.options_and_values()), dict(checker.msgs), list(checker.reports)] - for checker, (options, msgs, reports) in by_checker.items(): + for checker, (options, msgs, reports) in by_checker.iteritems(): prefix = '' title = '%s checker' % checker print title @@ -333,7 +408,7 @@ def print_full_documentation(self): title = ('%smessages' % prefix).capitalize() print title print '~' * len(title) - for msgid in sort_msgs(msgs.keys()): + for msgid in sort_msgs(msgs.iterkeys()): print self.get_message_help(msgid, False) print if reports: @@ -349,7 +424,7 @@ def list_messages(self): """output full messages list documentation in ReST format""" msgids = [] for checker in self.get_checkers(): - for msgid in checker.msgs.keys(): + for msgid in checker.msgs.iterkeys(): msgids.append(msgid) msgids.sort() for msgid in msgids: @@ -413,7 +488,7 @@ def add_stats(self, **kwargs): """add some stats entries to the statistic dictionary raise an AssertionError if there is a key conflict """ - for key, value in kwargs.items(): + for key, value in kwargs.iteritems(): if key[-1] == '_': key = key[:-1] assert key not in self.stats @@ -525,4 +600,3 @@ def walk(self, astng): self.walk(child) for cb in self.leave_events.get(cid, ()): cb(astng) - diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py new file mode 100644 index 00000000..628195f7 --- /dev/null +++ b/pylibs/pylama/utils.py @@ -0,0 +1,96 @@ +import _ast + +from .mccabe import get_code_complexity +from .pep8 import BaseReport, StyleGuide +from .pyflakes import checker + + +__all__ = 'pep8', 'mccabe', 'pyflakes', 'pylint' + + +class PEP8Report(BaseReport): + + def __init__(self, *args, **kwargs): + super(PEP8Report, self).__init__(*args, **kwargs) + self.errors = [] + + def init_file(self, filename, lines, expected, line_offset): + super(PEP8Report, self).init_file( + filename, lines, expected, line_offset) + self.errors = [] + + def error(self, line_number, offset, text, check): + code = super(PEP8Report, self).error( + line_number, offset, text, check) + + self.errors.append(dict( + text=text, + type=code, + col=offset + 1, + lnum=line_number, + )) + + def get_file_results(self): + return self.errors + +P8Style = StyleGuide(reporter=PEP8Report) + + +def pep8(path, **meta): + " PEP8 code checking. " + + return P8Style.input_file(path) + + +def mccabe(path, code=None, complexity=8, **meta): + " MCCabe code checking. " + + return get_code_complexity(code, complexity, filename=path) + + +def pyflakes(path, code=None, **meta): + " PyFlakes code checking. " + + errors = [] + tree = compile(code, path, "exec", _ast.PyCF_ONLY_AST) + w = checker.Checker(tree, path) + w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) + for w in w.messages: + errors.append(dict( + lnum=w.lineno, + col=w.col, + text=w.message % w.message_args, + type='E' + )) + return errors + + +def pylint(path, **meta): + from pylama.pylint.lint import Run + from pylama.pylint.reporters import BaseReporter + + class Reporter(BaseReporter): + + def __init__(self): + self.errors = [] + BaseReporter.__init__(self) + + def _display(self, layout): + pass + + def add_message(self, msg_id, location, msg): + _, _, line, col = location[1:] + self.errors.append(dict( + lnum=line, + col=col, + text="%s %s" % (msg_id, msg), + type=msg_id[0] + )) + + attrs = meta.get('pylint', []) + + runner = Run( + [path] + attrs, reporter=Reporter(), exit=False) + return runner.linter.reporter.errors + +# pymode:lint_ignore=W0231 diff --git a/pylibs/pylint/README b/pylibs/pylint/README deleted file mode 100644 index 5f89aef8..00000000 --- a/pylibs/pylint/README +++ /dev/null @@ -1,70 +0,0 @@ -README for PyLint -================= - -Dependencies ------------- -Pylint requires the logilab-astng (version >= 0.21.0), logilab-common -(version >= 0.53). - -* http://www.logilab.org/projects/astng -* http://www.logilab.org/projects/common - -Install -------- -From the source distribution, extract the tarball and run :: - - python setup.py install - -You'll have to install dependencies in a similar way. For debian and -rpm packages, use your usual tools according to your Linux distribution. - -More information about installation and available distribution format -may be found in the user manual in the *doc* subdirectory. - -Documentation -------------- -Look in the doc/ subdirectory or at the project home page -http://www.logilab.org/project/pylint - -Pylint is shipped with following additional commands: - -* pyreverse: an UML diagram generator -* symilar: an independent similarities checker -* epylint: Emacs and Flymake compatible Pylint -* pylint-gui: a graphical interface - -Comments, support, bug reports ------------------------------- - -Project page and tracker on : -http://www.logilab.org/project/pylint - -Use the python-projects@logilab.org mailing list. -You can subscribe to this mailing list at -http://lists.logilab.org/mailman/listinfo/python-projects - -Archives are available at -http://lists.logilab.org/pipermail/python-projects/ - -Contributors ------------- - -order doesn't matter... - -* Sylvain Thenault: main author / maintainer -* Alexandre Fayolle: TkInter gui, documentation, debian support -* Emile Anclin: used to maintain, py3k support -* Mads Kiilerich: various patches -* Torsten Marek, various patches -* Boris Feld, various patches -* Brian van den Broek: windows installation documentation -* Amaury Forgeot d'Arc: patch to check names imported from a module - exists in the module -* Benjamin Niemann: patch to allow block level enabling/disabling of messages -* Nathaniel Manista: suspicious lambda checking -* Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, - Maarten ter Huurne, Mirko Friedenhagen (among others): - bug reports, feedback, feature requests... -* All the Logilab's team: daily use, bug reports, feature requests -* Other people have contributed by their feedback, if I've forgotten - you, send me a note ! diff --git a/pylibs/pylint/README.Python3 b/pylibs/pylint/README.Python3 deleted file mode 100644 index ccc92972..00000000 --- a/pylibs/pylint/README.Python3 +++ /dev/null @@ -1,37 +0,0 @@ -Python3 -======= - -Compatibility -------------- - -Please, consider python3 >= 3.2 only. - - -Approach --------- - -We maintain a Python 2 base and use 2to3 to generate Python 3 code. - -2to3 is integrated into the distutils installation process and will be run as a -build step when invoked by the python3 interpreter:: - - NO_SETUPTOOLS=1 python3 setup.py install --no-compile - -In order to run pylint locally, you have to install the dependencies:: - - easy_install-3.2 logilab-common - easy_install-3.2 logilab-astng - - -Debian ------- - -For the Debian packaging, you can use the debian.py3k/ content against -the debian/ folder:: - - cp debian.py3k/* debian/ - - -Resources ---------- -http://wiki.python.org/moin/PortingPythonToPy3k diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index 8d1eaddb..5d93f018 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -1,6 +1,7 @@ -import StringIO import locale +from pylama.main import run + from .interface import get_option, get_var, get_current_buffer, command from .queue import add_task @@ -27,42 +28,10 @@ def run_checkers(task=None, checkers=None, ignore=None, buffer=None, select=None buffer = (task and task.buffer) or buffer filename = buffer.name result = [] - part = 100 / len(checkers) - - for c in checkers: - - checker = globals().get(c) - if not checker: - continue - - try: - for e in checker(filename): - e.update( - col=e.get('col') or 0, - text="%s [%s]" % (e.get('text', '') - .strip().replace("'", "\"").split('\n')[0], c), - filename=filename, - bufnr=buffer.number, - ) - result.append(e) - - except SyntaxError, e: - result.append(dict( - lnum=e.lineno, - col=e.offset or 0, - text=e.args[0], - bufnr=buffer.number, - )) - break - - except Exception, e: - assert True - - if task: - task.done += part - - result = filter(lambda e: _ignore_error(e, select, ignore), result) - result = sorted(result, key=lambda x: x['lnum']) + + pylint_options = '--rcfile={0} -r n'.format(get_var('lint_config')).split() + + result = run(filename, ignore=ignore, select=select, linters=checkers, pylint=pylint_options) if task: task.result = result @@ -73,124 +42,3 @@ def run_checkers(task=None, checkers=None, ignore=None, buffer=None, select=None def parse_result(result): command(('let g:qf_list = %s' % repr(result)).replace('\': u', '\': ')) command('call pymode#lint#Parse()') - - -def mccabe(filename): - from pylibs.mccabe import get_code_complexity - - complexity = int(get_option('lint_mccabe_complexity')) - return mc.get_module_complexity(filename, min=complexity) - - -def pep8(filename): - PEP8 or _init_pep8() - style = PEP8['style'] - return style.input_file(filename) - - -def pylint(filename): - from pylibs.logilab.astng.builder import MANAGER - - PYLINT or _init_pylint() - linter = PYLINT['lint'] - - MANAGER.astng_cache.clear() - linter.reporter.out = StringIO.StringIO() - linter.check(filename) - errors, linter.reporter.errors = linter.reporter.errors, [] - return errors - - -def pyflakes(filename): - from pylibs.pyflakes import checker - import _ast - - codeString = file(filename, 'U').read() + '\n' - errors = [] - tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST) - w = checker.Checker(tree, filename) - w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) - for w in w.messages: - errors.append(dict( - lnum=w.lineno, - col=w.col, - text=w.message % w.message_args, - type='E' - )) - return errors - - -PYLINT = dict() - - -def _init_pylint(): - - from pylibs.pylint import lint, checkers, reporters - import re - - class VimReporter(reporters.BaseReporter): - - def __init__(self): - reporters.BaseReporter.__init__(self) - self.errors = [] - - def add_message(self, msg_id, location, msg): - _, _, line, col = location[1:] - self.errors.append(dict( - lnum=line, - col=col, - text="%s %s" % (msg_id, msg), - type=msg_id[0] - )) - - PYLINT['lint'] = lint.PyLinter() - PYLINT['re'] = re.compile( - '^(?:.:)?[^:]+:(\d+): \[([EWRCI]+)[^\]]*\] (.*)$') - - checkers.initialize(PYLINT['lint']) - PYLINT['lint'].load_file_configuration(get_var('lint_config')) - PYLINT['lint'].set_option("output-format", "parseable") - PYLINT['lint'].set_option("include-ids", 1) - PYLINT['lint'].set_option("reports", 0) - PYLINT['lint'].reporter = VimReporter() - - -PEP8 = dict() - - -def _init_pep8(): - - from pylibs import pep8 as p8 - - class _PEP8Report(p8.BaseReport): - - def init_file(self, filename, lines, expected, line_offset): - super(_PEP8Report, self).init_file( - filename, lines, expected, line_offset) - self.errors = [] - - def error(self, line_number, offset, text, check): - code = super(_PEP8Report, self).error( - line_number, offset, text, check) - - self.errors.append(dict( - text=text, - type=code, - col=offset + 1, - lnum=line_number, - )) - - def get_file_results(self): - return self.errors - - PEP8['style'] = p8.StyleGuide(reporter=_PEP8Report) - - -def _ignore_error(e, select, ignore): - for s in select: - if e['text'].startswith(s): - return True - for i in ignore: - if e['text'].startswith(i): - return False - return True From 460e1e5b0277cbb8425063484c811106f9b9453c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 14 Mar 2013 19:43:22 +0800 Subject: [PATCH 139/513] Fix breakpoints --- plugin/pymode.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index e13133b5..3d3820ce 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -151,13 +151,13 @@ endif if !pymode#Default("g:pymode_breakpoint", 1) || g:pymode_breakpoint - if !pymode#Default("g:pymode_breakpoint_cmd", "import ipdb; ipdb.set_trace() ### XXX BREAKPOINT") && has("python") + if !pymode#Default("g:pymode_breakpoint_cmd", "import ipdb; ipdb.set_trace() # XXX BREAKPOINT") && has("python") python << EOF from imp import find_module try: find_module('ipdb') except ImportError: - vim.command('let g:pymode_breakpoint_cmd = "import pdb; pdb.set_trace() ### XXX BREAKPOINT"') + vim.command('let g:pymode_breakpoint_cmd = "import pdb; pdb.set_trace() # XXX BREAKPOINT"') EOF endif From 366b92be447005ae47988b4e845c766d8bc6f7e4 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 14 Mar 2013 19:47:15 +0800 Subject: [PATCH 140/513] Update autopep8 to version 0.8.7 --- Changelog.rst | 7 + pylibs/autopep8.py | 1161 ++++++++++++++++++++++++++++------------- pylibs/pymode/auto.py | 8 +- 3 files changed, 810 insertions(+), 366 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 7b2739f1..61834d57 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,13 @@ Changelog ========= +## 2013-03-** 0.6.11 +-------------------- +* Update `PEP8` to version 1.4.5; +* Update `Pylint` to version 0.27.0; +* Update `autopep8` to version 0.8.7; +* Fix breakpoint definition; + ## 2012-09-07 0.6.10 -------------------- * Dont raise an exception when Logger has no message handler (c) nixon diff --git a/pylibs/autopep8.py b/pylibs/autopep8.py index fe7e75c3..1c4d23e7 100644 --- a/pylibs/autopep8.py +++ b/pylibs/autopep8.py @@ -1,5 +1,8 @@ #!/usr/bin/env python # +# Copyright (C) 2010-2011 Hideo Hattori +# Copyright (C) 2011-2013 Hideo Hattori, Steven Myint +# # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including @@ -18,19 +21,19 @@ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -""" -A tool that automatically formats Python code to conform to the PEP 8 style -guide. -""" + +"""Automatically formats Python code to conform to the PEP 8 style guide.""" + from __future__ import print_function +from __future__ import division +import codecs import copy +import fnmatch +import inspect import os import re import sys -import inspect -import codecs -import locale try: from StringIO import StringIO except ImportError: @@ -38,72 +41,64 @@ import token import tokenize from optparse import OptionParser -from subprocess import Popen, PIPE -from difflib import unified_diff +import difflib import tempfile -from distutils.version import StrictVersion +from .pylama import pep8 + + try: - import pep8 - if StrictVersion(pep8.__version__) < StrictVersion('1.3a2'): - pep8 = None -except ImportError: - pep8 = None + unicode +except NameError: + unicode = str -__version__ = '0.8.1' +__version__ = '0.8.7' -PEP8_BIN = 'pep8' CR = '\r' LF = '\n' CRLF = '\r\n' -MAX_LINE_WIDTH = 79 -def open_with_encoding(filename, encoding, mode='r'): +# For generating line shortening candidates. +SHORTEN_OPERATOR_GROUPS = frozenset([ + frozenset([',']), + frozenset(['%']), + frozenset([',', '(', '[', '{']), + frozenset([',', '(', '[', '{', '%', '+', '-', '*', '/', '//']), +]) + + +def open_with_encoding(filename, encoding=None, mode='r'): """Return opened file with a specific encoding.""" - try: - # Python 3 - return open(filename, mode=mode, encoding=encoding) - except TypeError: - # Python 2 - return codecs.open(filename, mode=mode, encoding=encoding) + if not encoding: + encoding = detect_encoding(filename) + + import io + return io.open(filename, mode=mode, encoding=encoding, + newline='') # Preserve line endings def detect_encoding(filename): """Return file encoding.""" try: - # Python 3 - try: - with open(filename, 'rb') as input_file: - encoding = tokenize.detect_encoding(input_file.readline)[0] - - # Check for correctness of encoding - import io - with io.TextIOWrapper(input_file, encoding) as wrapper: - wrapper.read() - - return encoding - except (SyntaxError, LookupError, UnicodeDecodeError): - return 'latin-1' - except AttributeError: - # Python 2 - encoding = 'utf-8' - try: - # Check for correctness of encoding - with open_with_encoding(filename, encoding) as input_file: - input_file.read() - except UnicodeDecodeError: - encoding = 'latin-1' + with open(filename, 'rb') as input_file: + from lib2to3.pgen2 import tokenize as lib2to3_tokenize + encoding = lib2to3_tokenize.detect_encoding(input_file.readline)[0] + + # Check for correctness of encoding + with open_with_encoding(filename, encoding) as test_file: + test_file.read() return encoding + except (SyntaxError, LookupError, UnicodeDecodeError): + return 'latin-1' def read_from_filename(filename, readlines=False): """Return contents of file.""" - with open_with_encoding(filename, - encoding=detect_encoding(filename)) as input_file: + with open_with_encoding(filename) as input_file: return input_file.readlines() if readlines else input_file.read() @@ -154,10 +149,9 @@ def __init__(self, filename, options, contents=None): else: sio = StringIO(contents) self.source = sio.readlines() - self.original_source = copy.copy(self.source) self.newline = find_newline(self.source) self.options = options - self.indent_word = _get_indentword(''.join(self.source)) + self.indent_word = _get_indentword(unicode().join(self.source)) self.logical_start = None self.logical_end = None # method definition @@ -169,12 +163,16 @@ def __init__(self, filename, options, contents=None): self.fix_e221 = self.fix_e271 self.fix_e222 = self.fix_e271 self.fix_e223 = self.fix_e271 + self.fix_e226 = self.fix_e225 + self.fix_e227 = self.fix_e225 + self.fix_e228 = self.fix_e225 self.fix_e241 = self.fix_e271 self.fix_e242 = self.fix_e224 self.fix_e261 = self.fix_e262 self.fix_e272 = self.fix_e271 self.fix_e273 = self.fix_e271 self.fix_e274 = self.fix_e271 + self.fix_e703 = self.fix_e702 self.fix_w191 = self.fix_e101 def _fix_source(self, results): @@ -206,50 +204,44 @@ def _fix_source(self, results): elif modified_lines == []: # Empty list means no fix if self.options.verbose >= 2: print( - 'Not fixing {f} on line {l}'.format( + '---> Not fixing {f} on line {l}'.format( f=result['id'], l=result['line']), file=sys.stderr) else: # We assume one-line fix when None completed_lines.add(result['line']) else: if self.options.verbose >= 3: - print("'%s' is not defined." % fixed_methodname, + print("---> '%s' is not defined." % fixed_methodname, file=sys.stderr) info = result['info'].strip() - print('%s:%s:%s:%s' % (self.filename, - result['line'], - result['column'], - info), + print('---> %s:%s:%s:%s' % (self.filename, + result['line'], + result['column'], + info), file=sys.stderr) def fix(self): """Return a version of the source code with PEP 8 violations fixed.""" - if pep8: - pep8_options = { - 'ignore': - self.options.ignore and self.options.ignore.split(','), - 'select': - self.options.select and self.options.select.split(','), - } - results = _execute_pep8(pep8_options, self.source) - else: - if self.options.verbose: - print('Running in compatibility mode. Consider ' - 'upgrading to the latest pep8.', - file=sys.stderr) - results = _spawn_pep8((['--ignore=' + self.options.ignore] - if self.options.ignore else []) + - (['--select=' + self.options.select] - if self.options.select else []) + - [self.filename]) + pep8_options = { + 'ignore': self.options.ignore, + 'select': self.options.select, + 'max_line_length': self.options.max_line_length, + } + results = _execute_pep8(pep8_options, self.source) if self.options.verbose: - print('{n} issues to fix'.format( - n=len(results)), file=sys.stderr) - - self._fix_source(filter_results(source=''.join(self.source), - results=results)) - return ''.join(self.source) + progress = {} + for r in results: + if r['id'] not in progress: + progress[r['id']] = set() + progress[r['id']].add(r['line']) + print('---> {n} issue(s) to fix {progress}'.format( + n=len(results), progress=progress), file=sys.stderr) + + self._fix_source(filter_results(source=unicode().join(self.source), + results=results, + aggressive=self.options.aggressive)) + return unicode().join(self.source) def fix_e101(self, _): """Reindent all lines.""" @@ -328,7 +320,7 @@ def _fix_reindent(self, result, logical, fix_distinct=False): return [] ls, _, original = logical try: - rewrapper = Wrapper(original, hard_wrap=MAX_LINE_WIDTH) + rewrapper = Wrapper(original) except (tokenize.TokenError, IndentationError): return [] valid_indents = rewrapper.pep8_expected() @@ -425,7 +417,7 @@ def fix_e126(self, result, logical): fixed = (_get_indentation(logical_lines[0]) + self.indent_word + original.lstrip()) if fixed == original: - # Fallback to slower method. + # Fall back to slower method. return self._fix_reindent(result, logical) else: self.source[line_index] = fixed @@ -434,10 +426,10 @@ def fix_e127(self, result, logical): """Fix visual indentation.""" # Fix by inserting/deleting whitespace to the correct level. modified_lines = self._align_visual_indent(result, logical) - if modified_lines: + if modified_lines != []: return modified_lines else: - # Fallback to slower method. + # Fall back to slower method. return self._fix_reindent(result, logical) def _align_visual_indent(self, result, logical): @@ -453,13 +445,15 @@ def _align_visual_indent(self, result, logical): original = self.source[line_index] fixed = original - if '(' in logical_lines[0]: - fixed = logical_lines[0].find('(') * ' ' + original.lstrip() - elif logical_lines[0].rstrip().endswith('\\'): + if logical_lines[0].rstrip().endswith('\\'): fixed = (_get_indentation(logical_lines[0]) + self.indent_word + original.lstrip()) else: - return [] + for symbol in '([{': + if symbol in logical_lines[0]: + fixed = logical_lines[0].find( + symbol) * ' ' + original.lstrip() + break if fixed == original: return [] @@ -475,7 +469,9 @@ def fix_e201(self, result): # When multiline strings are involved, pep8 reports the error as # being at the start of the multiline string, which doesn't work # for us. - if '"""' in target or "'''" in target: + if ('"""' in target or + "'''" in target or + target.rstrip().endswith('\\')): return [] fixed = fix_whitespace(target, @@ -540,8 +536,7 @@ def fix_e251(self, result): fixed.endswith('=\\\r\n') or fixed.endswith('=\\\r')): self.source[line_index] = fixed.rstrip('\n\r \t\\') - self.source[line_index + 1] = \ - self.source[line_index + 1].lstrip() + self.source[line_index + 1] = self.source[line_index + 1].lstrip() return [line_index + 1, line_index + 2] # Line indexed at 1 self.source[result['line'] - 1] = fixed @@ -565,6 +560,14 @@ def fix_e271(self, result): target = self.source[line_index] offset = result['column'] - 1 + # When multiline strings are involved, pep8 reports the error as + # being at the start of the multiline string, which doesn't work + # for us. + if ('"""' in target or + "'''" in target or + target.rstrip().endswith('\\')): + return [] + fixed = fix_whitespace(target, offset=offset, replacement=' ') @@ -627,26 +630,48 @@ def fix_e401(self, result): if ';' in target: return [] - indentation = target.split('import ')[0] + indentation = re.split(pattern=r'\bimport\b', + string=target, maxsplit=1)[0] fixed = (target[:offset].rstrip('\t ,') + self.newline + indentation + 'import ' + target[offset:].lstrip('\t ,')) self.source[line_index] = fixed def fix_e501(self, result): - """Try to make lines fit within 79 characters.""" + """Try to make lines fit within --max-line-length characters.""" line_index = result['line'] - 1 target = self.source[line_index] + if target.lstrip().startswith('#'): + # Shorten comment if it is the last comment line. + try: + if self.source[line_index + 1].lstrip().startswith('#'): + return [] + except IndexError: + pass + + # Wrap commented lines. + fixed = shorten_comment( + line=target, + newline=self.newline, + max_line_length=self.options.max_line_length) + if fixed == self.source[line_index]: + return [] + else: + self.source[line_index] = fixed + return + indent = _get_indentation(target) source = target[len(indent):] - sio = StringIO(target) + assert source.lstrip() == source + sio = StringIO(source) # Check for multiline string. try: tokens = list(tokenize.generate_tokens(sio.readline)) except (tokenize.TokenError, IndentationError): multi_line_candidate = break_multi_line( - target, newline=self.newline, indent_word=self.indent_word) + target, newline=self.newline, + indent_word=self.indent_word) if multi_line_candidate: self.source[line_index] = multi_line_candidate @@ -654,31 +679,36 @@ def fix_e501(self, result): else: return [] - # Prefer - # my_long_function_name( - # x, y, z, ...) - # - # over - # my_long_function_name(x, y, - # z, ...) - candidate0 = _shorten_line(tokens, source, target, indent, - self.indent_word, newline=self.newline, - reverse=False) - candidate1 = _shorten_line(tokens, source, target, indent, - self.indent_word, newline=self.newline, - reverse=True) - if candidate0 and candidate1: - if candidate0.split(self.newline)[0].endswith('('): - self.source[line_index] = candidate0 - else: - self.source[line_index] = candidate1 - elif candidate0: - self.source[line_index] = candidate0 - elif candidate1: - self.source[line_index] = candidate1 - else: - # Otherwise both don't work - return [] + candidates = shorten_line( + tokens, source, indent, + self.indent_word, newline=self.newline, + aggressive=self.options.aggressive) + + candidates = list(sorted( + set(candidates), + key=lambda x: line_shortening_rank(x, + self.newline, + self.indent_word))) + + if self.options.verbose >= 4: + print(('-' * 79 + '\n').join([''] + candidates + ['']), + file=sys.stderr) + + for _candidate in candidates: + if _candidate is None: + continue + + if _candidate == target: + continue + + if (get_longest_length(_candidate, self.newline) >= + get_longest_length(target, self.newline)): + continue + + self.source[line_index] = _candidate + return + + return [] def fix_e502(self, result): """Remove extraneous escape of newline.""" @@ -722,7 +752,7 @@ def fix_e702(self, result, logical): self.source[line_index] = first + self.newline + second def fix_e711(self, result): - """Fix comparison.""" + """Fix comparison with None.""" line_index = result['line'] - 1 target = self.source[line_index] offset = result['column'] - 1 @@ -747,6 +777,37 @@ def fix_e711(self, result): self.source[line_index] = ' '.join([left, new_center, right]) + def fix_e712(self, result): + """Fix comparison with boolean.""" + line_index = result['line'] - 1 + target = self.source[line_index] + offset = result['column'] - 1 + + right_offset = offset + 2 + if right_offset >= len(target): + return [] + + left = target[:offset].rstrip() + center = target[offset:right_offset] + right = target[right_offset:].lstrip() + + # Handle simple cases only. + new_right = None + if center.strip() == '==': + if re.match(r'\bTrue\b', right): + new_right = re.sub(r'\bTrue\b *', '', right, count=1) + elif center.strip() == '!=': + if re.match(r'\bFalse\b', right): + new_right = re.sub(r'\bFalse\b *', '', right, count=1) + + if new_right is None: + return [] + + if new_right[0].isalnum(): + new_right = ' ' + new_right + + self.source[line_index] = left + new_right + def fix_e721(self, _): """Switch to use isinstance().""" return self.refactor('idioms') @@ -789,10 +850,7 @@ def refactor(self, fixer_name, ignore=None): UnicodeDecodeError, UnicodeEncodeError): return [] - try: - original = unicode(''.join(self.source).strip(), 'utf-8') - except (NameError, TypeError): - original = ''.join(self.source).strip() + original = unicode().join(self.source).strip() if original == new_text.strip(): return [] else: @@ -825,11 +883,11 @@ def find_newline(source): """Return type of newline used in source.""" cr, lf, crlf = 0, 0, 0 for s in source: - if CRLF in s: + if s.endswith(CRLF): crlf += 1 - elif CR in s: + elif s.endswith(CR): cr += 1 - elif LF in s: + elif s.endswith(LF): lf += 1 _max = max(cr, crlf, lf) if _max == lf: @@ -865,19 +923,11 @@ def _get_indentation(line): return '' -def _analyze_pep8result(result): - tmp = result.split(':') - filename = tmp[0] - line = int(tmp[1]) - column = int(tmp[2]) - info = ' '.join(result.split()[1:]) - pep8id = info.lstrip().split()[0] - return dict(id=pep8id, filename=filename, line=line, - column=column, info=info) - - def _get_difftext(old, new, filename): - diff = unified_diff(old, new, 'original/' + filename, 'fixed/' + filename) + diff = difflib.unified_diff( + old, new, + 'original/' + filename, + 'fixed/' + filename) return ''.join(diff) @@ -903,26 +953,50 @@ def _priority_key(pep8_result): return len(priority) -def _shorten_line(tokens, source, target, indentation, indent_word, newline, - reverse=False): - """Separate line at OPERATOR.""" - max_line_width_minus_indentation = MAX_LINE_WIDTH - len(indentation) - if reverse: - tokens.reverse() +def shorten_line(tokens, source, indentation, indent_word, newline, + aggressive=False): + """Separate line at OPERATOR. + + Multiple candidates will be yielded. + + """ + for candidate in _shorten_line(tokens=tokens, + source=source, + indentation=indentation, + indent_word=indent_word, + newline=newline, + aggressive=aggressive): + yield candidate + + if aggressive: + for key_token_strings in SHORTEN_OPERATOR_GROUPS: + shortened = _shorten_line_at_tokens( + tokens=tokens, + source=source, + indentation=indentation, + indent_word=indent_word, + newline=newline, + key_token_strings=key_token_strings, + aggressive=aggressive) + + if shortened is not None and shortened != source: + yield shortened + + +def _shorten_line(tokens, source, indentation, indent_word, newline, + aggressive=False): + """Separate line at OPERATOR. + + Multiple candidates will be yielded. + + """ for tkn in tokens: # Don't break on '=' after keyword as this violates PEP 8. if token.OP == tkn[0] and tkn[1] != '=': + assert tkn[0] != token.INDENT + offset = tkn[2][1] + 1 - if reverse: - if offset > (max_line_width_minus_indentation - - len(indent_word)): - continue - else: - if (len(target.rstrip()) - offset > - (max_line_width_minus_indentation - - len(indent_word))): - continue - first = source[:offset - len(indentation)] + first = source[:offset] second_indent = indentation if first.rstrip().endswith('('): @@ -932,16 +1006,10 @@ def _shorten_line(tokens, source, target, indentation, indent_word, newline, else: second_indent += indent_word - second = (second_indent + - source[offset - len(indentation):].lstrip()) + second = (second_indent + source[offset:].lstrip()) if not second.strip(): continue - # Don't modify if lines are not short enough - if len(first) > max_line_width_minus_indentation: - continue - if len(second) > MAX_LINE_WIDTH: # Already includes indentation - continue # Do not begin a line with a comma if second.lstrip().startswith(','): continue @@ -952,9 +1020,81 @@ def _shorten_line(tokens, source, target, indentation, indent_word, newline, fixed = first + ' \\' + newline + second else: fixed = first + newline + second - if check_syntax(fixed): - return indentation + fixed - return None + + # Only fix if syntax is okay. + if check_syntax(normalize_multiline(fixed) + if aggressive else fixed): + yield indentation + fixed + + +def _shorten_line_at_tokens(tokens, source, indentation, indent_word, newline, + key_token_strings, aggressive): + """Separate line by breaking at tokens in key_token_strings. + + This will always break the line at the first parenthesis. + + """ + offsets = [] + first_paren = True + for tkn in tokens: + token_type = tkn[0] + token_string = tkn[1] + next_offset = tkn[2][1] + 1 + + assert token_type != token.INDENT + + if token_string in key_token_strings or (first_paren and + token_string == '('): + # Don't split right before newline. + if next_offset < len(source) - 1: + offsets.append(next_offset) + + if token_string == '(': + first_paren = False + + current_indent = None + fixed = None + for line in split_at_offsets(source, offsets): + if fixed: + fixed += newline + current_indent + line + + for symbol in '([{': + if line.endswith(symbol): + current_indent += indent_word + else: + # First line. + fixed = line + assert not current_indent + current_indent = indent_word + + assert fixed is not None + + if check_syntax(normalize_multiline(fixed) + if aggressive > 1 else fixed): + return indentation + fixed + else: + return None + + +def normalize_multiline(line): + """Remove multiline-related code that will cause syntax error. + + This is for purposes of checking syntax. + + """ + for quote in '\'"': + dict_pattern = r'^{q}[^{q}]*{q}\s*:\s*'.format(q=quote) + if re.match(dict_pattern, line): + if not line.strip().endswith('}'): + line += '}' + return '{' + line + + if line.startswith('def ') and line.rstrip().endswith(':'): + # Do not allow ':' to be alone. That is invalid. + if ':' not in line.split(): + return line[len('def'):].strip().rstrip(':') + + return line def fix_whitespace(line, offset, replacement): @@ -968,19 +1108,6 @@ def fix_whitespace(line, offset, replacement): return left + replacement + right -def _spawn_pep8(pep8_options): - """Execute pep8 via subprocess.Popen.""" - for path in os.environ['PATH'].split(':'): - if os.path.exists(os.path.join(path, PEP8_BIN)): - cmd = ([os.path.join(path, PEP8_BIN)] + - pep8_options) - p = Popen(cmd, stdout=PIPE) - output = p.communicate()[0].decode('utf-8') - return [_analyze_pep8result(l) - for l in output.splitlines()] - raise Exception("'%s' is not found." % PEP8_BIN) - - def _execute_pep8(pep8_options, source): """Execute pep8 via python method calls.""" class QuietReport(pep8.BaseReport): @@ -996,8 +1123,10 @@ def error(self, line_number, offset, text, _): code = super(QuietReport, self).error(line_number, offset, text, _) if code: self.__full_error_results.append( - dict(id=code, line=line_number, - column=offset + 1, info=text)) + {'id': code, + 'line': line_number, + 'column': offset + 1, + 'info': text}) def full_error_results(self): """Return error results in detail. @@ -1025,9 +1154,6 @@ class Reindenter(object): def __init__(self, input_text, newline): self.newline = newline - self.find_stmt = 1 # next token begins a fresh stmt? - self.level = 0 # current indent level - # Raw file lines. self.raw = input_text self.after = None @@ -1035,7 +1161,7 @@ def __init__(self, input_text, newline): self.string_content_line_numbers = multiline_string_lines( ''.join(self.raw)) - # File lines, rstripped & tab-expanded. Dummy at start is so + # File lines, rstripped & tab-expanded. Dummy at start is so # that we can use tokenize's 1-based line numbering easily. # Note that a line is all-blank iff it is a newline. self.lines = [] @@ -1051,22 +1177,14 @@ def __init__(self, input_text, newline): self.lines.insert(0, None) self.index = 1 # index into self.lines of next line - # List of (lineno, indentlevel) pairs, one for each stmt and - # comment line. indentlevel is -1 for comment lines, as a - # signal that tokenize doesn't know what to do about them; - # indeed, they're our headache! - self.stats = [] - def run(self): """Fix indentation and return modified line numbers. Line numbers are indexed at 1. """ - tokens = tokenize.generate_tokens(self.getline) try: - for t in tokens: - self.tokeneater(*t) + stats = reindent_stats(tokenize.generate_tokens(self.getline)) except (tokenize.TokenError, IndentationError): return set() # Remove trailing empty lines. @@ -1074,7 +1192,6 @@ def run(self): while lines and lines[-1] == self.newline: lines.pop() # Sentinel. - stats = self.stats stats.append((len(lines), 0)) # Map count of leading spaces to # we want. have2want = {} @@ -1092,7 +1209,7 @@ def run(self): if want < 0: # A comment line. if have: - # An indented comment line. If we saw the same + # An indented comment line. If we saw the same # indentation before, reuse what it most recently # mapped to. want = have2want.get(have, - 1) @@ -1151,45 +1268,62 @@ def fixed_lines(self): def getline(self): """Line-getter for tokenize.""" if self.index >= len(self.lines): - line = "" + line = '' else: line = self.lines[self.index] self.index += 1 return line - def tokeneater(self, token_type, _, start, __, line): - """Line-eater for tokenize.""" - sline = start[0] + +def reindent_stats(tokens): + """Return list of (lineno, indentlevel) pairs. + + One for each stmt and comment line. indentlevel is -1 for comment lines, as + a signal that tokenize doesn't know what to do about them; indeed, they're + our headache! + + """ + find_stmt = 1 # next token begins a fresh stmt? + level = 0 # current indent level + stats = [] + + for t in tokens: + token_type = t[0] + sline = t[2][0] + line = t[4] + if token_type == tokenize.NEWLINE: # A program statement, or ENDMARKER, will eventually follow, # after some (possibly empty) run of tokens of the form # (NL | COMMENT)* (INDENT | DEDENT+)? - self.find_stmt = 1 + find_stmt = 1 elif token_type == tokenize.INDENT: - self.find_stmt = 1 - self.level += 1 + find_stmt = 1 + level += 1 elif token_type == tokenize.DEDENT: - self.find_stmt = 1 - self.level -= 1 + find_stmt = 1 + level -= 1 elif token_type == tokenize.COMMENT: - if self.find_stmt: - self.stats.append((sline, -1)) + if find_stmt: + stats.append((sline, -1)) # but we're still looking for a new stmt, so leave # find_stmt alone elif token_type == tokenize.NL: pass - elif self.find_stmt: + elif find_stmt: # This is the first "real token" following a NEWLINE, so it # must be the first token of the next program statement, or an # ENDMARKER. - self.find_stmt = 0 + find_stmt = 0 if line: # not endmarker - self.stats.append((sline, self.level)) + stats.append((sline, level)) + + return stats class Wrapper(object): @@ -1205,14 +1339,9 @@ class Wrapper(object): tokenize.DEDENT, tokenize.NEWLINE, tokenize.ENDMARKER ]) - def __init__(self, physical_lines, hard_wrap=79, soft_wrap=72): - if type(physical_lines) != list: - physical_lines = physical_lines.splitlines(keepends=True) + def __init__(self, physical_lines): self.lines = physical_lines - self.index = 0 - self.hard_wrap = hard_wrap - self.soft_wrap = soft_wrap - self.tokens = list() + self.tokens = [] self.rel_indent = None sio = StringIO(''.join(physical_lines)) for t in tokenize.generate_tokens(sio.readline): @@ -1259,7 +1388,7 @@ def pep8_expected(self): """Replicate logic in pep8.py, to know what level to indent things to. Return a list of lists; each list represents valid indent levels for - the line in question, relative from the initial indent. However, the + the line in question, relative from the initial indent. However, the first entry is the indent level which was expected. """ @@ -1350,8 +1479,8 @@ def pep8_expected(self): vi.append(indent_level + hang) # about the best we can do without look-ahead - if indent_next and vi[0] == indent_level + 4 and \ - nrows == row + 1: + if (indent_next and vi[0] == indent_level + 4 and + nrows == row + 1): vi[0] += 4 if add_second_chances: @@ -1368,36 +1497,18 @@ def pep8_expected(self): valid_indents[row] = vi - # ...returning to original continuation_line_identation func... + # Returning to original continuation_line_indentation() from + # pep8. visual_indent = indent_chances.get(start[1]) last_indent = start rel_indent[row] = start[1] - indent_level hang = rel_indent[row] - rel_indent[open_row] if token_type == tokenize.OP and text in ']})': - if indent[depth]: - if start[1] != indent[depth]: - pass # E124 - elif hang: - pass # E123 + pass elif visual_indent is True: if not indent[depth]: indent[depth] = start[1] - elif visual_indent in (text, str): - pass - elif indent[depth] and start[1] < indent[depth]: - pass # E128 - elif hang == 4 or (indent_next and rel_indent[row] == 8): - pass - else: - if hang <= 0: - pass # E122 - elif indent[depth]: - pass # E127 - elif hang % 4: - pass # E121 - else: - pass # E126 # line altered: comments shouldn't define a visual indent if parens[row] and not indent[depth] and token_type not in ( @@ -1436,9 +1547,6 @@ def pep8_expected(self): last_token_multiline = (start[0] != end[0]) - if indent_next and rel_indent[-1] == 4: - pass # E125 - return valid_indents @@ -1461,11 +1569,7 @@ def refactor_with_2to3(source_text, fixer_name): tool = refactor.RefactoringTool( fixer_names=fixers, explicit=fixers) - try: - return unicode(tool.refactor_string( - source_text.decode('utf-8'), name='')) - except NameError: - return str(tool.refactor_string(source_text, name='')) + return unicode(tool.refactor_string(source_text, name='')) def break_multi_line(source_text, newline, indent_word): @@ -1474,30 +1578,45 @@ def break_multi_line(source_text, newline, indent_word): Return None if a break is not possible. """ + indentation = _get_indentation(source_text) + # Handle special case only. - if ('(' in source_text and source_text.rstrip().endswith(',')): - index = 1 + source_text.find('(') - if index >= MAX_LINE_WIDTH: - return None + for symbol in '([{': + # Only valid if symbol is not on a line by itself. + if (symbol in source_text + and source_text.rstrip().endswith(',') + and not source_text.lstrip().startswith(symbol)): - # Make sure we are not in a string. - for quote in ['"', "'"]: - if quote in source_text: - if source_text.find(quote) < index: - return None - - # Make sure we are not in a comment. - if '#' in source_text: - if source_text.find('#') < index: - return None - - assert index < len(source_text) - return ( - source_text[:index].rstrip() + newline + - _get_indentation(source_text) + indent_word + - source_text[index:].lstrip()) - else: - return None + index = 1 + source_text.find(symbol) + + if index <= len(indent_word) + len(indentation): + continue + + if is_probably_inside_string_or_comment(source_text, index - 1): + continue + + return ( + source_text[:index].rstrip() + newline + + indentation + indent_word + + source_text[index:].lstrip()) + + return None + + +def is_probably_inside_string_or_comment(line, index): + """Return True if index may be inside a string or comment.""" + # Make sure we are not in a string. + for quote in ['"', "'"]: + if quote in line: + if line.find(quote) <= index: + return True + + # Make sure we are not in a comment. + if '#' in line: + if line.find('#') <= index: + return True + + return False def check_syntax(code): @@ -1508,31 +1627,45 @@ def check_syntax(code): return False -def filter_results(source, results): +def filter_results(source, results, aggressive=False): """Filter out spurious reports from pep8. - Currently we filter out errors about indentation in multiline strings. + If aggressive is True, we allow possibly unsafe fixes (E711, E712). """ - string_line_numbers = multiline_string_lines(source) + non_docstring_string_line_numbers = multiline_string_lines( + source, include_docstrings=False) + all_string_line_numbers = multiline_string_lines( + source, include_docstrings=True) + + split_source = [None] + source.splitlines() for r in results: - if r['line'] in string_line_numbers: - if r['id'].lower().startswith('e1'): + issue_id = r['id'].lower() + + if r['line'] in non_docstring_string_line_numbers: + if issue_id.startswith('e1'): + continue + elif issue_id in ['e501', 'w191']: continue - elif r['id'].lower() in ['e501', 'w191']: + + if r['line'] in all_string_line_numbers: + if issue_id in ['e501']: continue # Filter out incorrect E101 reports when there are no tabs. # pep8 will complain about this even if the tab indentation found # elsewhere is in a multi-line string. - if r['id'].lower() == 'e101' and '\t' not in source[r['line'] - 1]: + if issue_id == 'e101' and '\t' not in split_source[r['line']]: + continue + + if issue_id in ['e711', 'e712'] and not aggressive: continue yield r -def multiline_string_lines(source): +def multiline_string_lines(source, include_docstrings=False): """Return line numbers that are within multiline strings. The line numbers are indexed at 1. @@ -1546,16 +1679,17 @@ def multiline_string_lines(source): try: for t in tokenize.generate_tokens(sio.readline): token_type = t[0] - token_string = t[1] + start_row = t[2][0] + end_row = t[3][0] start_row = t[2][0] end_row = t[3][0] - if (token_type == tokenize.STRING and - starts_with_triple(token_string) and - previous_token_type != tokenize.INDENT): - # We increment by one since we want the contents of the - # string. - line_numbers |= set(range(1 + start_row, 1 + end_row)) + if (token_type == tokenize.STRING and start_row != end_row): + if (include_docstrings or + previous_token_type != tokenize.INDENT): + # We increment by one since we want the contents of the + # string. + line_numbers |= set(range(1 + start_row, 1 + end_row)) previous_token_type = token_type except (IndentationError, tokenize.TokenError): @@ -1564,61 +1698,156 @@ def multiline_string_lines(source): return line_numbers -def starts_with_triple(string): - """Return True if the string starts with triple single/double quotes.""" - return (string.strip().startswith('"""') or - string.strip().startswith("'''")) +def shorten_comment(line, newline, max_line_length): + """Return trimmed or split long comment line.""" + assert len(line) > max_line_length + line = line.rstrip() + + # PEP 8 recommends 72 characters for comment text. + indentation = _get_indentation(line) + '# ' + max_line_length = min(max_line_length, + len(indentation) + 72) + + MIN_CHARACTER_REPEAT = 5 + if (len(line) - len(line.rstrip(line[-1])) >= MIN_CHARACTER_REPEAT and + not line[-1].isalnum()): + # Trim comments that end with things like --------- + return line[:max_line_length] + newline + elif re.match(r'\s*#+\s*\w+', line): + import textwrap + split_lines = textwrap.wrap(line.lstrip(' \t#'), + initial_indent=indentation, + subsequent_indent=indentation, + width=max_line_length, + break_long_words=False) + return newline.join(split_lines) + newline + else: + return line + newline -def fix_file(filename, opts, output=sys.stdout): - tmp_source = read_from_filename(filename) +def format_block_comments(source): + """Format block comments.""" + if '#' not in source: + # Optimization. + return source - # Add missing newline (important for diff) - if tmp_source: - tmp_newline = find_newline(tmp_source) - if tmp_source == tmp_source.rstrip(tmp_newline): - tmp_source += tmp_newline + string_line_numbers = multiline_string_lines(source, + include_docstrings=True) + fixed_lines = [] + sio = StringIO(source) + for (line_number, line) in enumerate(sio.readlines(), start=1): + if (re.match(r'\s*#+\w+', line) and + line_number not in string_line_numbers): + fixed_lines.append(_get_indentation(line) + + '# ' + + line.lstrip().lstrip('#')) + else: + fixed_lines.append(line) - fix = FixPEP8(filename, opts, contents=tmp_source) - fixed_source = fix.fix() - original_source = copy.copy(fix.original_source) - tmp_filename = filename - if not pep8 or opts.in_place: - encoding = detect_encoding(filename) + return ''.join(fixed_lines) - for _ in range(opts.pep8_passes): - if fixed_source == tmp_source: - break + +def normalize_line_endings(lines): + """Return fixed line endings. + + All lines will be modified to use the most common line ending. + + """ + newline = find_newline(lines) + return [line.rstrip('\n\r') + newline for line in lines] + + +def mutual_startswith(a, b): + return b.startswith(a) or a.startswith(b) + + +def code_match(code, select, ignore): + if ignore: + for ignored_code in [c.strip() for c in ignore]: + if mutual_startswith(code.lower(), ignored_code.lower()): + return False + + if select: + for selected_code in [c.strip() for c in select]: + if mutual_startswith(code.lower(), selected_code.lower()): + return True + return False + + return True + + +def fix_string(source, options=None): + """Return fixed source code.""" + if not options: + options = parse_args([''])[0] + + sio = StringIO(source) + return fix_lines(sio.readlines(), options=options) + + +def fix_lines(source_lines, options, filename=''): + """Return fixed source code.""" + tmp_source = unicode().join(normalize_line_endings(source_lines)) + + # Keep a history to break out of cycles. + previous_hashes = set([hash(tmp_source)]) + + fixed_source = tmp_source + if code_match('e26', select=options.select, ignore=options.ignore): + fixed_source = format_block_comments(fixed_source) + + for _ in range(-1, options.pep8_passes): tmp_source = copy.copy(fixed_source) - if not pep8: - tmp_filename = tempfile.mkstemp()[1] - fp = open_with_encoding(tmp_filename, encoding=encoding, mode='w') - fp.write(fixed_source) - fp.close() - fix = FixPEP8(tmp_filename, opts, contents=tmp_source) + + fix = FixPEP8(filename, options, contents=tmp_source) fixed_source = fix.fix() - if not pep8: - os.remove(tmp_filename) - del tmp_filename - del tmp_source - if opts.diff: - new = StringIO(''.join(fix.source)) + if hash(fixed_source) in previous_hashes: + break + else: + previous_hashes.add(hash(fixed_source)) + + return fixed_source + + +def fix_file(filename, options=None, output=None): + if not options: + options = parse_args([filename])[0] + + original_source = read_from_filename(filename, readlines=True) + + fixed_source = original_source + + if options.in_place: + encoding = detect_encoding(filename) + + fixed_source = fix_lines(fixed_source, options, filename=filename) + + if options.diff: + new = StringIO(fixed_source) new = new.readlines() - output.write(_get_difftext(original_source, new, filename)) - elif opts.in_place: + diff = _get_difftext(original_source, new, filename) + if output: + output.write(diff) + else: + return output + elif options.in_place: fp = open_with_encoding(filename, encoding=encoding, mode='w') fp.write(fixed_source) fp.close() else: - output.write(fixed_source) + if output: + output.write(fixed_source) + else: + return fixed_source def parse_args(args): """Parse command-line options.""" parser = OptionParser(usage='Usage: autopep8 [options] ' - '[filename [filename ...]]', + '[filename [filename ...]]' + '\nUse filename \'-\' for stdin.', version='autopep8: %s' % __version__, description=__doc__, prog='autopep8') @@ -1633,34 +1862,82 @@ def parse_args(args): parser.add_option('-r', '--recursive', action='store_true', help='run recursively; must be used with --in-place or ' '--diff') - parser.add_option('-p', '--pep8-passes', - default=100, type='int', + parser.add_option('-j', '--jobs', type=int, metavar='n', default=1, + help='number of parallel jobs; ' + 'match CPU count if value is less than 1') + parser.add_option('-p', '--pep8-passes', metavar='n', + default=100, type=int, help='maximum number of additional pep8 passes' ' (default: %default)') + parser.add_option('-a', '--aggressive', action='count', default=0, + help='enable possibly unsafe changes (E711, E712); ' + 'multiple -a result in more aggressive changes') + parser.add_option('--exclude', metavar='globs', + help='exclude files/directories that match these ' + 'comma-separated globs') parser.add_option('--list-fixes', action='store_true', help='list codes for fixes; ' 'used by --ignore and --select') - parser.add_option('--ignore', default='', - help='do not fix these errors/warnings (e.g. E4,W)') - parser.add_option('--select', default='', + parser.add_option('--ignore', metavar='errors', default='', + help='do not fix these errors/warnings ' + '(default {0})'.format(pep8.DEFAULT_IGNORE)) + parser.add_option('--select', metavar='errors', default='', help='fix only these errors/warnings (e.g. E4,W)') - opts, args = parser.parse_args(args) + parser.add_option('--max-line-length', metavar='n', default=79, type=int, + help='set maximum allowed line length ' + '(default: %default)') + options, args = parser.parse_args(args) - if not len(args) and not opts.list_fixes: + if not len(args) and not options.list_fixes: parser.error('incorrect number of arguments') - if len(args) > 1 and not (opts.in_place or opts.diff): + if '-' in args and len(args) > 1: + parser.error('cannot mix stdin and regular files') + + if len(args) > 1 and not (options.in_place or options.diff): parser.error('autopep8 only takes one filename as argument ' 'unless the "--in-place" or "--diff" options are ' 'used') - if opts.recursive and not (opts.in_place or opts.diff): + if options.recursive and not (options.in_place or options.diff): parser.error('--recursive must be used with --in-place or --diff') - if opts.in_place and opts.diff: + if options.exclude and not options.recursive: + parser.error('--exclude is only relevant when used with --recursive') + + if options.in_place and options.diff: parser.error('--in-place and --diff are mutually exclusive') - return opts, args + if options.max_line_length <= 0: + parser.error('--max-line-length must be greater than 0') + + if args == ['-'] and (options.in_place or options.recursive): + parser.error('--in-place or --recursive cannot be used with ' + 'standard input') + + if options.select: + options.select = options.select.split(',') + + if options.ignore: + options.ignore = options.ignore.split(',') + elif not options.select and pep8.DEFAULT_IGNORE: + options.ignore = pep8.DEFAULT_IGNORE.split(',') + + if options.exclude: + options.exclude = options.exclude.split(',') + else: + options.exclude = [] + + if options.jobs < 1: + # Do not import multiprocessing globally in case it is not supported + # on the platform. + import multiprocessing + options.jobs = multiprocessing.cpu_count() + + if options.jobs > 1 and not options.in_place: + parser.error('parallel jobs requires --in-place') + + return options, args def supported_fixes(): @@ -1678,49 +1955,207 @@ def supported_fixes(): getattr(instance, attribute).__doc__)) -def main(): - """Tool main.""" - opts, args = parse_args(sys.argv[1:]) +def line_shortening_rank(candidate, newline, indent_word): + """Return rank of candidate. - if opts.list_fixes: - for code, description in supported_fixes(): - print('{code} - {description}'.format( - code=code, description=description)) - return 0 + This is for sorting candidates. - if opts.in_place or opts.diff: - filenames = list(set(args)) - else: - assert len(args) == 1 - assert not opts.recursive - filenames = args[:1] + """ + rank = 0 + if candidate: + lines = candidate.split(newline) + + offset = 0 + if lines[0].rstrip()[-1] not in '([{': + for symbol in '([{': + offset = max(offset, 1 + lines[0].find(symbol)) + + max_length = max([offset + len(x.strip()) for x in lines]) + rank += max_length + rank += len(lines) + + bad_staring_symbol = { + '(': ')', + '[': ']', + '{': '}'}.get(lines[0][-1], None) + + if len(lines) > 1: + if (bad_staring_symbol and + lines[1].lstrip().startswith(bad_staring_symbol)): + rank += 20 + else: + rank -= 10 + + if lines[0].endswith('(['): + rank += 10 + + for current_line in lines: + for bad_start in ['.', '%', '+', '-', '/']: + if current_line.startswith(bad_start): + rank += 100 + + for ending in '([{': + # Avoid lonely opening. They result in longer lines. + if (current_line.endswith(ending) and + len(current_line.strip()) <= len(indent_word)): + rank += 100 - if sys.version_info[0] >= 3: - output = sys.stdout + if current_line.endswith('%'): + rank -= 20 else: - output = codecs.getwriter(locale.getpreferredencoding())(sys.stdout) + rank = 100000 + + return max(0, rank) + + +def split_at_offsets(line, offsets): + """Split line at offsets. + + Return list of strings. + + """ + result = [] + + previous_offset = 0 + current_offset = 0 + for current_offset in sorted(offsets): + if current_offset < len(line) and previous_offset != current_offset: + result.append(line[previous_offset:current_offset]) + previous_offset = current_offset + + result.append(line[current_offset:]) + + return result + + +def get_longest_length(text, newline): + """Return length of longest line.""" + return max([len(line) for line in text.split(newline)]) + + +class LineEndingWrapper(object): + + r"""Replace line endings to work with sys.stdout. + + It seems that sys.stdout expects only '\n' as the line ending, no matter + the platform. Otherwise, we get repeated line endings. + + """ + + def __init__(self, output): + self.__output = output + + def write(self, s): + self.__output.write(s.replace('\r\n', '\n').replace('\r', '\n')) + + def __getattr__(self, key): + return getattr(self.__output, key) + + +def temporary_file(): + """Return temporary file.""" + try: + return tempfile.NamedTemporaryFile(mode='w', encoding='utf-8') + except TypeError: + return tempfile.NamedTemporaryFile(mode='w') + +def match_file(filename, exclude): + """Return True if file is okay for modifying/recursing.""" + if not filename.endswith('.py'): + return False + + if filename.startswith('.'): + return False + + for pattern in exclude: + if fnmatch.fnmatch(filename, pattern): + return False + + return True + + +def find_files(filenames, recursive, exclude): + """Yield filenames.""" while filenames: name = filenames.pop(0) - if opts.recursive and os.path.isdir(name): + if recursive and os.path.isdir(name): for root, directories, children in os.walk(name): filenames += [os.path.join(root, f) for f in children - if f.endswith('.py') and - not f.startswith('.')] + if match_file(f, exclude)] for d in directories: if d.startswith('.'): directories.remove(d) else: - if opts.verbose: - print('[file:%s]' % name, file=sys.stderr) - try: - fix_file(name, opts, output) - except IOError as error: - print(str(error), file=sys.stderr) + yield name -if __name__ == '__main__': +def _fix_file(parameters): + """Helper function for optionally running fix_file() in parallel.""" + if parameters[1].verbose: + print('[file:{0}]'.format(parameters[0]), file=sys.stderr) + try: + fix_file(*parameters) + except IOError as error: + print(str(error), file=sys.stderr) + + +def fix_multiple_files(filenames, options, output=None): + """Fix list of files. + + Optionally fix files recursively. + + """ + filenames = find_files(filenames, options.recursive, options.exclude) + if options.jobs > 1: + import multiprocessing + pool = multiprocessing.Pool(options.jobs) + pool.map(_fix_file, + [(name, options) for name in filenames]) + else: + for name in filenames: + _fix_file((name, options, output)) + + +def main(): + """Tool main.""" + if not pep8: + print('pep8 >= 1.3.2 required', file=sys.stderr) + return 1 + try: - sys.exit(main()) + options, args = parse_args(sys.argv[1:]) + + if options.list_fixes: + for code, description in supported_fixes(): + print('{code} - {description}'.format( + code=code, description=description)) + return 0 + + if options.in_place or options.diff: + filenames = list(set(args)) + else: + assert len(args) == 1 + assert not options.recursive + if args == ['-']: + assert not options.in_place + temp = temporary_file() + temp.write(sys.stdin.read()) + temp.flush() + filenames = [temp.name] + else: + filenames = args[:1] + + output = codecs.getwriter('utf-8')(sys.stdout.buffer + if sys.version_info[0] >= 3 + else sys.stdout) + + output = LineEndingWrapper(output) + + fix_multiple_files(filenames, options, output) except KeyboardInterrupt: - sys.exit(1) + return 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/pylibs/pymode/auto.py b/pylibs/pymode/auto.py index 87d38992..27adeaff 100644 --- a/pylibs/pymode/auto.py +++ b/pylibs/pymode/auto.py @@ -3,13 +3,15 @@ class Options(): - verbose = 0 + aggressive = 0 diff = False + ignore = '' in_place = True - recursive = False + max_line_length = 79 pep8_passes = 100 - ignore = '' + recursive = False select = '' + verbose = 0 def fix_current_file(): From ca2235a5693bfa579d5ddbbc1fbac66e3eb8fbda Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 14 Mar 2013 20:52:23 +0800 Subject: [PATCH 141/513] Update pylama --- pylibs/pylama/__init__.py | 2 +- pylibs/pylama/main.py | 22 ++++++++++------------ pylibs/pylama/pylint/lint.py | 2 +- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index 78e32370..2acd1b0b 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -1,6 +1,6 @@ " pylama -- Python code audit. " -version_info = (0, 2, 1) +version_info = (0, 2, 2) __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index 23099d8c..9aea9478 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -1,10 +1,11 @@ -import logging import fnmatch import re import sys -from argparse import ArgumentParser from os import getcwd, walk, path as op +import logging +from argparse import ArgumentParser + from . import utils @@ -37,16 +38,13 @@ def run(path, ignore=None, select=None, linters=default_linters, **meta): if params.get('lint'): for e in linter(path, code=code, **meta): - e.update( - col=e.get('col', 0), - lnum=e.get('lnum', 0), - type=e.get('type', 'E'), - text="{0} [{1}]".format( - e.get('text', '').strip( - ).replace("'", "\"").split('\n')[0], - lint), - filename=path or '', - ) + e['col'] = e.get('col') or 0 + e['lnum'] = e.get('lnum') or 0 + e['type'] = e.get('type') or 'E' + e['text'] = "{0} [{1}]".format((e.get( + 'text') or '').strip() + .replace("'", "\"").split('\n')[0], lint) + e['filename'] = path or '' errors.append(e) except IOError, e: diff --git a/pylibs/pylama/pylint/lint.py b/pylibs/pylama/pylint/lint.py index e1cc8b6f..905cfd38 100644 --- a/pylibs/pylama/pylint/lint.py +++ b/pylibs/pylama/pylint/lint.py @@ -70,7 +70,7 @@ def _get_python_path(filepath): dirname = os.path.dirname(os.path.realpath( - os.path.expanduser(filepath))) + os.path.expanduser(filepath))) while True: if not os.path.exists(os.path.join(dirname, "__init__.py")): return dirname From 304931e5441ae387c31d77f78a026c623cedba24 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 14 Mar 2013 20:55:16 +0800 Subject: [PATCH 142/513] Updare syntax --- Changelog.rst | 1 + syntax/python.vim | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 61834d57..007a83d3 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -7,6 +7,7 @@ Changelog * Update `Pylint` to version 0.27.0; * Update `autopep8` to version 0.8.7; * Fix breakpoint definition; +* Update python syntax ## 2012-09-07 0.6.10 -------------------- diff --git a/syntax/python.vim b/syntax/python.vim index 2f03511b..e0809c7f 100644 --- a/syntax/python.vim +++ b/syntax/python.vim @@ -21,12 +21,12 @@ call pymode#Default('g:pymode_syntax_all', 1) syn keyword pythonStatement pass raise syn keyword pythonStatement global assert syn keyword pythonStatement lambda yield - syn keyword pythonStatement with + syn keyword pythonStatement with as syn keyword pythonStatement def class nextgroup=pythonFunction skipwhite syn match pythonFunction "[a-zA-Z_][a-zA-Z0-9_]*" display contained syn keyword pythonRepeat for while syn keyword pythonConditional if elif else - syn keyword pythonPreCondit import from as + syn keyword pythonInclude import from syn keyword pythonException try except finally syn keyword pythonOperator and in is not or @@ -240,7 +240,7 @@ endif " ============= hi def link pythonStatement Statement - hi def link pythonPreCondit Statement + hi def link pythonInclude Include hi def link pythonFunction Function hi def link pythonConditional Conditional hi def link pythonRepeat Repeat From 551aed144895d68e947aec24bb3412f5536fd02e Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 14 Mar 2013 23:33:28 +0800 Subject: [PATCH 143/513] Added pyrex syntax --- syntax/pyrex.vim | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 syntax/pyrex.vim diff --git a/syntax/pyrex.vim b/syntax/pyrex.vim new file mode 100644 index 00000000..acbe2379 --- /dev/null +++ b/syntax/pyrex.vim @@ -0,0 +1,71 @@ +" Vim syntax file +" Language: Pyrex +" Maintainer: John Tyree +" Last Change: 2012 Nov 06 + +" For version 5.x: Clear all syntax items +" For version 6.x: Quit when a syntax file was already loaded +if version < 600 + syntax clear +elseif exists("b:current_syntax") + finish +endif + +" Read the Python syntax to start with +if version < 600 + so :p:h/python.vim +else + runtime! syntax/python.vim + unlet b:current_syntax +endif + +" Pyrex extentions +syn keyword pyrexStatement nogil inline typedef ctypedef sizeof +syn keyword pyrexType Py_ssize_t int long short float double char object void +" Here we want slightly different behavior depending on whether we're declaring +" variables or functions. c[p]def should work on the top level as a keyword, but +" should ALSO work to identify functions and classes. +syn match pyrexStatement "\" +syn match pyrexStatement "\[^=]*(\@=" contains=pythonStatement,pyrexStatement,pythonFunction,pyrexType skipwhite +syn keyword pyrexType signed unsigned +syn keyword pyrexStructure struct union enum +syn keyword pyrexInclude include cimport +syn keyword pyrexAccess public private property readonly extern +" If someome wants Python's built-ins highlighted probably he +" also wants Pyrex's built-ins highlighted +if exists("python_highlight_builtins") || exists("pyrex_highlight_builtins") + syn keyword pyrexBuiltin NULL +endif + +" This deletes "from" from the keywords and re-adds it as a +" match with lower priority than pyrexForFrom +syn clear pythonInclude +syn keyword pythonInclude import +syn match pythonInclude "\" + +" With "for[^:]*\zsfrom" VIM does not match "for" anymore, so +" I used the slower "\@<=" form +syn match pyrexForFrom "\(\[^:]*\)\@<=\" + +" Default highlighting +if version >= 508 || !exists("did_pyrex_syntax_inits") + if version < 508 + let did_pyrex_syntax_inits = 1 + command -nargs=+ HiLink hi link + else + command -nargs=+ HiLink hi def link + endif + HiLink pyrexStatement Statement + HiLink pyrexType Type + HiLink pyrexStructure Structure + HiLink pyrexInclude PreCondit + HiLink pyrexAccess pyrexStatement + if exists("python_highlight_builtins") || exists("pyrex_highlight_builtins") + HiLink pyrexBuiltin Function + endif + HiLink pyrexForFrom Statement + + delcommand HiLink +endif + +let b:current_syntax = "pyrex" From 0350b0615f576d8c7d84e2d49664fa89f7bde90c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 14 Mar 2013 23:59:38 +0800 Subject: [PATCH 144/513] Fail silenly on vim with -python --- plugin/pymode.vim | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugin/pymode.vim b/plugin/pymode.vim index 3d3820ce..8f7f8bbb 100644 --- a/plugin/pymode.vim +++ b/plugin/pymode.vim @@ -1,4 +1,4 @@ -let g:pymode_version = "0.6.9" +let g:pymode_version = "0.6.11" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version @@ -10,13 +10,13 @@ endif " DESC: Check python support if !has('python') - echoerr expand(":t") . " required vim compiled with +python." - let g:pymode_lint = 0 - let g:pymode_rope = 0 + let g:pymode_virtualenv = 0 let g:pymode_path = 0 + let g:pymode_lint = 0 let g:pymode_doc = 0 + let g:pymode_breakpoint = 0 + let g:pymode_rope = 0 let g:pymode_run = 0 - let g:pymode_virtualenv = 0 endif @@ -34,7 +34,7 @@ endif " }}} -" DESC: Fix python path +" DESC: Add pymode's pylibs to sys.path {{{ if !pymode#Default('g:pymode_path', 1) || g:pymode_path call pymode#Default('g:pymode_paths', []) @@ -49,7 +49,7 @@ libpath = os.path.join(os.path.dirname(os.path.dirname( sys.path = [os.path.dirname(libpath), libpath, curpath] + vim.eval("g:pymode_paths") + sys.path EOF -endif +endif " }}} " Lint {{{ From d9716c08ca1674be40923083be8b87449cbd3983 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 00:02:34 +0800 Subject: [PATCH 145/513] Added documentation regarding pymode setting --- README.rst | 9 +++++++++ doc/pymode.txt | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/README.rst b/README.rst index 28bff927..71f58b5d 100644 --- a/README.rst +++ b/README.rst @@ -116,6 +116,15 @@ To change this settings, edit your ``~/.vimrc``: :: let g:pymode_run_key = 'R' +Loading the Plugin +------------------ + +Default values: :: + + " Load the whole plugin + let g:pymode = 1 + + Show documentation ------------------ diff --git a/doc/pymode.txt b/doc/pymode.txt index ab096ea2..4cde9a18 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -44,6 +44,8 @@ to install the pylint or rope libraries on your system. This script provides the following options that can customise the behaviour of PythonMode. These options should be set in your vimrc. +|'pymode'| Turn off the whole plugin + |'pymode_paths'| Additional python paths for pymode |'pymode_doc'| Turns off the documentation script @@ -142,6 +144,13 @@ Set linters and mccabe complexity. This changes will work only in current buffer. +------------------------------------------------------------------------------ + *'pymode'* +Values: 0 or 1. +Default: 1. + +If this option is set to 0 then the whole plugin is disabled + ------------------------------------------------------------------------------ *'pymode_paths'* Values: List of strings From ae7abdad8c4212a1df8a25497de4059b6d763305 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 00:05:56 +0800 Subject: [PATCH 146/513] Bugfix for #99 --- autoload/pymode/doc.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/pymode/doc.vim b/autoload/pymode/doc.vim index eaf96091..cf997ad8 100644 --- a/autoload/pymode/doc.vim +++ b/autoload/pymode/doc.vim @@ -10,7 +10,7 @@ fun! pymode#doc#Show(word) "{{{ py help(vim.eval('a:word')) py sys.stdout, out = _, sys.stdout.getvalue() call pymode#TempBuffer() - py vim.current.buffer.append(out.split('\n'), 0) + py vim.current.buffer.append(str(out).split('\n'), 0) wincmd p endif endfunction "}}} From dc3f4087627cd2a5898b948b4d31e7634ae106c5 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 00:44:28 +0800 Subject: [PATCH 147/513] Fix code run --- .gitignore | 4 +++- autoload/pymode/run.vim | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index cddd4dea..f188ca8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -*.pyc +*.py[cod] +*.sw? +*~ .DS_Store .ropeproject tags diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index bd92b1ac..d70fa3a6 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -1,16 +1,39 @@ " DESC: Save file if it modified and run python code fun! pymode#run#Run(line1, line2) "{{{ - if &modifiable && &modified | write | endif + if &modifiable && &modified + try + write + catch /E212/ + echohl Error | echo "File modified and I can't save it. Cancel code checking." | echohl None + return 0 + endtry + endif py import StringIO - py sys.stdout, _ = StringIO.StringIO(), sys.stdout + py sys.stdout, stdout_ = StringIO.StringIO(), sys.stdout + py sys.stderr, stderr_ = StringIO.StringIO(), sys.stderr + py enc = vim.eval('&enc') call pymode#WideMessage("Code running.") try + call setqflist([]) py execfile(vim.eval('expand("%s:p")')) - py sys.stdout, out = _, sys.stdout.getvalue() - call pymode#TempBuffer() - py vim.current.buffer.append(out.split('\n'), 0) - wincmd p - call pymode#WideMessage("") + py out, err = sys.stdout.getvalue().strip(), sys.stderr.getvalue() + py sys.stdout, sys.stderr = stdout_, stderr_ + + cexpr "" + py for x in err.strip().split('\n'): vim.command('caddexpr "' + x.replace('"', r'\"') + '"') + let l:oldefm = &efm + set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m + call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, 0) + let &efm = l:oldefm + +python << EOF +if out: + vim.command("call pymode#TempBuffer()") + vim.current.buffer.append([x.encode(enc) for x in out.split('\n')], 0) + vim.command("wincmd p") +else: + vim.command('call pymode#WideMessage("No output.")') +EOF catch /.*/ From 73fa0cb35f8ea8835a6fd13408fc39394194fe14 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 00:45:47 +0800 Subject: [PATCH 148/513] update changelog --- Changelog.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index 007a83d3..5c0d40f1 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -7,7 +7,8 @@ Changelog * Update `Pylint` to version 0.27.0; * Update `autopep8` to version 0.8.7; * Fix breakpoint definition; -* Update python syntax +* Update python syntax; +* fixed run-time error when output non-ascii in multibyte locale; ## 2012-09-07 0.6.10 -------------------- From 89de269b5b36a83518791cf4184b258ca6dd7c93 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 00:55:34 +0800 Subject: [PATCH 149/513] move initialization into ftplugin as it is python specific --- Changelog.rst | 3 ++- plugin/pymode.vim => ftplugin/python/init-pymode.vim | 8 ++++++-- ftplugin/python/pymode.vim | 2 ++ syntax/python.vim | 2 ++ 4 files changed, 12 insertions(+), 3 deletions(-) rename plugin/pymode.vim => ftplugin/python/init-pymode.vim (98%) diff --git a/Changelog.rst b/Changelog.rst index 5c0d40f1..b489c96b 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -8,7 +8,8 @@ Changelog * Update `autopep8` to version 0.8.7; * Fix breakpoint definition; * Update python syntax; -* fixed run-time error when output non-ascii in multibyte locale; +* Fixed run-time error when output non-ascii in multibyte locale; +* Move initialization into ftplugin as it is python specific; ## 2012-09-07 0.6.10 -------------------- diff --git a/plugin/pymode.vim b/ftplugin/python/init-pymode.vim similarity index 98% rename from plugin/pymode.vim rename to ftplugin/python/init-pymode.vim index 8f7f8bbb..8358bd29 100644 --- a/plugin/pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -1,3 +1,8 @@ +if exists('did_init_pymode_vim') + finish +endif +let did_init_pymode_vim = 1 + let g:pymode_version = "0.6.11" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version @@ -43,8 +48,7 @@ python << EOF import sys, vim, os curpath = vim.eval("getcwd()") -libpath = os.path.join(os.path.dirname(os.path.dirname( - vim.eval("expand(':p')"))), 'pylibs') +libpath = os.path.join(vim.eval("expand(':p:h:h:h')"), 'pylibs') sys.path = [os.path.dirname(libpath), libpath, curpath] + vim.eval("g:pymode_paths") + sys.path EOF diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 854e51d0..ffcf0803 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -1,3 +1,5 @@ +runtime ftplugin/python/init-pymode.vim + if pymode#Default('b:pymode', 1) finish endif diff --git a/syntax/python.vim b/syntax/python.vim index e0809c7f..ba2b9f92 100644 --- a/syntax/python.vim +++ b/syntax/python.vim @@ -1,4 +1,6 @@ " vim: ft=vim:fdm=marker +" +runtime ftplugin/python/init-pymode.vim " DESC: Disable script loading if !pymode#Option('syntax') || pymode#Default('b:current_syntax', 'python') From ccd35c8afda18b05e7b49df50059469da3c45414 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 01:05:32 +0800 Subject: [PATCH 150/513] Smart ve activation --- autoload/pymode/virtualenv.vim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/autoload/pymode/virtualenv.vim b/autoload/pymode/virtualenv.vim index 163634ad..c771d907 100644 --- a/autoload/pymode/virtualenv.vim +++ b/autoload/pymode/virtualenv.vim @@ -15,7 +15,7 @@ fun! pymode#virtualenv#Activate() "{{{ python << EOF import sys, vim, os -ve_dir = os.environ['VIRTUAL_ENV'] +ve_dir = vim.eval('$VIRTUAL_ENV') ve_dir in sys.path or sys.path.insert(0, ve_dir) activate_this = os.path.join(os.path.join(ve_dir, 'bin'), 'activate_this.py') @@ -25,4 +25,7 @@ if not os.path.exists(activate_this): execfile(activate_this, dict(__file__=activate_this)) EOF + + call pymode#WideMessage("Activate virtualenv: ".$VIRTUAL_ENV) + endfunction "}}} From 33915dfcb5456a35be253de390391286181828a1 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 01:15:45 +0800 Subject: [PATCH 151/513] pyrex support --- Changelog.rst | 1 + after/ftplugin/pyrex.vim | 1 + after/indent/pyrex.vim | 1 + ftplugin/pyrex.vim | 1 + 4 files changed, 4 insertions(+) create mode 100644 after/ftplugin/pyrex.vim create mode 100644 after/indent/pyrex.vim create mode 100644 ftplugin/pyrex.vim diff --git a/Changelog.rst b/Changelog.rst index b489c96b..61869bcb 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -10,6 +10,7 @@ Changelog * Update python syntax; * Fixed run-time error when output non-ascii in multibyte locale; * Move initialization into ftplugin as it is python specific; +* Pyrex (Cython) files support; ## 2012-09-07 0.6.10 -------------------- diff --git a/after/ftplugin/pyrex.vim b/after/ftplugin/pyrex.vim new file mode 100644 index 00000000..61d43637 --- /dev/null +++ b/after/ftplugin/pyrex.vim @@ -0,0 +1 @@ +runtime after/ftplugin/python.vim diff --git a/after/indent/pyrex.vim b/after/indent/pyrex.vim new file mode 100644 index 00000000..ab2e54dd --- /dev/null +++ b/after/indent/pyrex.vim @@ -0,0 +1 @@ +runtime after/indent/python.vim diff --git a/ftplugin/pyrex.vim b/ftplugin/pyrex.vim new file mode 100644 index 00000000..93e0556d --- /dev/null +++ b/ftplugin/pyrex.vim @@ -0,0 +1 @@ +runtime ftplugin/python/pymode.vim From 6b106e15961035e413ad83fd3847adb2b103672d Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 02:43:40 +0800 Subject: [PATCH 152/513] Fix pylint --- .gitignore | 1 + autoload/pymode/lint.vim | 3 +++ ftplugin/python/init-pymode.vim | 4 ++-- pylibs/autopep8.py | 2 +- pylibs/pymode/auto.py | 2 +- pylibs/ropevim.py | 6 +++--- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index f188ca8d..e3b40119 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.py[cod] +.vimrc *.sw? *~ .DS_Store diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 0ea56fcf..364cf114 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -14,6 +14,7 @@ fun! pymode#lint#Check() "{{{ let g:pymode_lint_buffer = bufnr('%') + py from pymode import lint py lint.check_file() endfunction " }}} @@ -99,7 +100,9 @@ fun! pymode#lint#Auto() "{{{ return 0 endtry endif + py from pymode import auto py auto.fix_current_file() cclose edit + call pymode#WideMessage("AutoPep8 done.") endfunction "}}} diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index 8358bd29..4807c84a 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -50,7 +50,7 @@ import sys, vim, os curpath = vim.eval("getcwd()") libpath = os.path.join(vim.eval("expand(':p:h:h:h')"), 'pylibs') -sys.path = [os.path.dirname(libpath), libpath, curpath] + vim.eval("g:pymode_paths") + sys.path +sys.path = [libpath, curpath] + vim.eval("g:pymode_paths") + sys.path EOF endif " }}} @@ -130,7 +130,7 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint let g:pymode_lint_config = expand(":p:h:h") . "/pylint.ini" endif - py from pymode import lint, queue, auto + py from pymode import queue au VimLeavePre * py queue.stop_queue() diff --git a/pylibs/autopep8.py b/pylibs/autopep8.py index 1c4d23e7..c024f21c 100644 --- a/pylibs/autopep8.py +++ b/pylibs/autopep8.py @@ -44,7 +44,7 @@ import difflib import tempfile -from .pylama import pep8 +from pylama import pep8 try: diff --git a/pylibs/pymode/auto.py b/pylibs/pymode/auto.py index 27adeaff..16d7b811 100644 --- a/pylibs/pymode/auto.py +++ b/pylibs/pymode/auto.py @@ -1,5 +1,5 @@ import vim -from pylibs.autopep8 import fix_file +from autopep8 import fix_file class Options(): diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index b2d150c4..762457f9 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -4,9 +4,9 @@ import tempfile import re -from pylibs.ropemode import decorators -from pylibs.ropemode import environment -from pylibs.ropemode import interface +from ropemode import decorators +from ropemode import environment +from ropemode import interface import vim From d7010df113a86d5e9d4d6d64ef82963f129db931 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 02:47:44 +0800 Subject: [PATCH 153/513] update readme --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 71f58b5d..3ce2d44e 100644 --- a/README.rst +++ b/README.rst @@ -22,6 +22,7 @@ There is no need to install the pylint_, rope_ or any used python library on you - And more, more ... See (very old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my first screencast) +Another old presentation here: http://www.youtube.com/watch?v=YhqsjUUHj6g .. contents:: From 796a5a82c72a53419876ed6a393a472c68194d48 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 02:52:46 +0800 Subject: [PATCH 154/513] Update pylama --- pylibs/pylama/mccabe.py | 31 -------- pylibs/pylama/pep8.py | 148 +++-------------------------------- pylibs/pylama/pylint/lint.py | 2 +- pylibs/pylama/utils.py | 4 +- 4 files changed, 16 insertions(+), 169 deletions(-) diff --git a/pylibs/pylama/mccabe.py b/pylibs/pylama/mccabe.py index 01e00537..f8f1de05 100644 --- a/pylibs/pylama/mccabe.py +++ b/pylibs/pylama/mccabe.py @@ -9,7 +9,6 @@ except ImportError: from ast import parse, iter_child_nodes # NOQA -import optparse import sys from collections import defaultdict @@ -263,34 +262,4 @@ def get_module_complexity(module_path, min=7): return get_code_complexity(code, min, filename=module_path) -def main(argv): - opar = optparse.OptionParser() - opar.add_option("-d", "--dot", dest="dot", - help="output a graphviz dot file", action="store_true") - opar.add_option("-m", "--min", dest="min", - help="minimum complexity for output", type="int", - default=2) - - options, args = opar.parse_args(argv) - - text = open(args[0], "rU").read() + '\n\n' - ast = parse(text) - visitor = PathGraphingAstVisitor() - visitor.preorder(ast, visitor) - - if options.dot: - print('graph {') - for graph in visitor.graphs.values(): - if graph.complexity() >= options.min: - graph.to_dot() - print('}') - else: - for graph in visitor.graphs.values(): - if graph.complexity() >= options.min: - print(graph.name, graph.complexity()) - - -if __name__ == '__main__': - main(sys.argv[1:]) - # pymode:lint=0 diff --git a/pylibs/pylama/pep8.py b/pylibs/pylama/pep8.py index 025b1d0b..bbff9107 100644 --- a/pylibs/pylama/pep8.py +++ b/pylibs/pylama/pep8.py @@ -54,7 +54,6 @@ import inspect import keyword import tokenize -from optparse import OptionParser from fnmatch import fnmatch try: from configparser import RawConfigParser @@ -1554,8 +1553,19 @@ def __init__(self, *args, **kwargs): parse_argv = kwargs.pop('parse_argv', False) config_file = kwargs.pop('config_file', None) parser = kwargs.pop('parser', None) - options, self.paths = process_options( - parse_argv=parse_argv, config_file=config_file, parser=parser) + + class options: + exclude = [] + select = [] + ignore = [] + testsuite = None + doctest = None + verbose = False + max_line_length = 79 + + self.paths = [] + # options, self.paths = process_options( + # parse_argv=parse_argv, config_file=config_file, parser=parser) if args or kwargs: # build options from dict options_dict = dict(*args, **kwargs) @@ -1673,62 +1683,6 @@ def get_checks(self, argument_name): return sorted(checks) -def get_parser(prog='pep8', version=__version__): - parser = OptionParser(prog=prog, version=version, - usage="%prog [options] input ...") - parser.config_options = [ - 'exclude', 'filename', 'select', 'ignore', 'max-line-length', 'count', - 'format', 'quiet', 'show-pep8', 'show-source', 'statistics', 'verbose'] - parser.add_option('-v', '--verbose', default=0, action='count', - help="print status messages, or debug with -vv") - parser.add_option('-q', '--quiet', default=0, action='count', - help="report only file names, or nothing with -qq") - parser.add_option('-r', '--repeat', default=True, action='store_true', - help="(obsolete) show all occurrences of the same error") - parser.add_option('--first', action='store_false', dest='repeat', - help="show first occurrence of each error") - parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE, - help="exclude files or directories which match these " - "comma separated patterns (default: %default)") - parser.add_option('--filename', metavar='patterns', default='*.py', - help="when parsing directories, only check filenames " - "matching these comma separated patterns " - "(default: %default)") - parser.add_option('--select', metavar='errors', default='', - help="select errors and warnings (e.g. E,W6)") - parser.add_option('--ignore', metavar='errors', default='', - help="skip errors and warnings (e.g. E4,W)") - parser.add_option('--show-source', action='store_true', - help="show source code for each error") - parser.add_option('--show-pep8', action='store_true', - help="show text of PEP 8 for each error " - "(implies --first)") - parser.add_option('--statistics', action='store_true', - help="count errors and warnings") - parser.add_option('--count', action='store_true', - help="print total number of errors and warnings " - "to standard error and set exit code to 1 if " - "total is not null") - parser.add_option('--max-line-length', type='int', metavar='n', - default=MAX_LINE_LENGTH, - help="set maximum allowed line length " - "(default: %default)") - parser.add_option('--format', metavar='format', default='default', - help="set the error format [default|pylint|]") - parser.add_option('--diff', action='store_true', - help="report only lines changed according to the " - "unified diff received on STDIN") - group = parser.add_option_group("Testing Options") - if os.path.exists(TESTSUITE_PATH): - group.add_option('--testsuite', metavar='dir', - help="run regression tests from dir") - group.add_option('--doctest', action='store_true', - help="run doctest on myself") - group.add_option('--benchmark', action='store_true', - help="measure processing speed") - return parser - - def read_config(options, args, arglist, parser): """Read both user configuration and local configuration.""" config = RawConfigParser() @@ -1784,79 +1738,3 @@ def read_config(options, args, arglist, parser): options, _ = parser.parse_args(arglist, values=new_options) options.doctest = options.testsuite = False return options - - -def process_options(arglist=None, parse_argv=False, config_file=None, - parser=None): - """Process options passed either via arglist or via command line args.""" - if not arglist and not parse_argv: - # Don't read the command line if the module is used as a library. - arglist = [] - if not parser: - parser = get_parser() - if not parser.has_option('--config'): - if config_file is True: - config_file = DEFAULT_CONFIG - group = parser.add_option_group("Configuration", description=( - "The project options are read from the [%s] section of the " - "tox.ini file or the setup.cfg file located in any parent folder " - "of the path(s) being processed. Allowed options are: %s." % - (parser.prog, ', '.join(parser.config_options)))) - group.add_option('--config', metavar='path', default=config_file, - help="user config file location (default: %default)") - options, args = parser.parse_args(arglist) - options.reporter = None - - if options.ensure_value('testsuite', False): - args.append(options.testsuite) - elif not options.ensure_value('doctest', False): - if parse_argv and not args: - if options.diff or any(os.path.exists(name) - for name in PROJECT_CONFIG): - args = ['.'] - else: - parser.error('input not specified') - options = read_config(options, args, arglist, parser) - options.reporter = parse_argv and options.quiet == 1 and FileReport - - if options.filename: - options.filename = options.filename.split(',') - options.exclude = options.exclude.split(',') - if options.select: - options.select = options.select.split(',') - if options.ignore: - options.ignore = options.ignore.split(',') - - if options.diff: - options.reporter = DiffReport - stdin = stdin_get_value() - options.selected_lines = parse_udiff(stdin, options.filename, args[0]) - args = sorted(options.selected_lines) - - return options, args - - -def _main(): - """Parse options and run checks on Python source.""" - pep8style = StyleGuide(parse_argv=True, config_file=True) - options = pep8style.options - if options.doctest or options.testsuite: - from testsuite.support import run_tests - report = run_tests(pep8style) - else: - report = pep8style.check_files() - if options.statistics: - report.print_statistics() - if options.benchmark: - report.print_benchmark() - if options.testsuite and not options.quiet: - report.print_results() - if report.total_errors: - if options.count: - sys.stderr.write(str(report.total_errors) + '\n') - sys.exit(1) - -if __name__ == '__main__': - _main() - -# pymode:lint=0 diff --git a/pylibs/pylama/pylint/lint.py b/pylibs/pylama/pylint/lint.py index 905cfd38..e1cc8b6f 100644 --- a/pylibs/pylama/pylint/lint.py +++ b/pylibs/pylama/pylint/lint.py @@ -70,7 +70,7 @@ def _get_python_path(filepath): dirname = os.path.dirname(os.path.realpath( - os.path.expanduser(filepath))) + os.path.expanduser(filepath))) while True: if not os.path.exists(os.path.join(dirname, "__init__.py")): return dirname diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py index 628195f7..b682b013 100644 --- a/pylibs/pylama/utils.py +++ b/pylibs/pylama/utils.py @@ -66,8 +66,8 @@ def pyflakes(path, code=None, **meta): def pylint(path, **meta): - from pylama.pylint.lint import Run - from pylama.pylint.reporters import BaseReporter + from .pylint.lint import Run + from .pylint.reporters import BaseReporter class Reporter(BaseReporter): From be6ad5c09ed0f93314486015d7e5e36032fbce96 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 03:10:05 +0800 Subject: [PATCH 155/513] Support raw_input --- Changelog.rst | 1 + autoload/pymode/run.vim | 3 ++- pylibs/pymode/lint.py | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 61869bcb..c496df6f 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -11,6 +11,7 @@ Changelog * Fixed run-time error when output non-ascii in multibyte locale; * Move initialization into ftplugin as it is python specific; * Pyrex (Cython) files support; +* Support `raw_input` in run python code; ## 2012-09-07 0.6.10 -------------------- diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index d70fa3a6..d6e5e3b8 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -15,7 +15,8 @@ fun! pymode#run#Run(line1, line2) "{{{ call pymode#WideMessage("Code running.") try call setqflist([]) - py execfile(vim.eval('expand("%s:p")')) + py execfile(vim.eval('expand("%s:p")'), {'raw_input': lambda s: + vim.eval('input("{0}")'.format(s)), 'input': vim.eval('input("{0}")'.format(s))}) py out, err = sys.stdout.getvalue().strip(), sys.stderr.getvalue() py sys.stdout, sys.stderr = stdout_, stderr_ diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index 5d93f018..074d1be3 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -6,7 +6,10 @@ from .queue import add_task -locale.setlocale(locale.LC_CTYPE, "C") +try: + locale.setlocale(locale.LC_CTYPE, "C") +except AttributeError: + pass def check_file(): From f322c957fc8699d04e32660e734a3d3cb3bc3a1c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 03:18:11 +0800 Subject: [PATCH 156/513] update version --- Changelog.rst | 2 +- README.rst | 22 +++++++++++----------- doc/pymode.txt | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index c496df6f..2f672192 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -## 2013-03-** 0.6.11 +## 2013-03-15 0.6.11 -------------------- * Update `PEP8` to version 1.4.5; * Update `Pylint` to version 0.27.0; diff --git a/README.rst b/README.rst index 3ce2d44e..7d02f392 100644 --- a/README.rst +++ b/README.rst @@ -31,17 +31,17 @@ Another old presentation here: http://www.youtube.com/watch?v=YhqsjUUHj6g Changelog ========= -## 2012-08-02 0.6.5 -------------------- -* Updated Pep8 to version 1.3.3 -* Updated Pylint to version 0.25.2 -* Fixed virtualenv support for windows users -* Added pymode modeline ':help PythonModeModeline' -* Added diagnostic tool ':call pymode#troubleshooting#Test()' -* Added `PyLintAuto` command ':help PyLintAuto' -* Code checking is async operation now -* More, more fast the pymode folding -* Repaired execution of python code +## 2013-03-15 0.6.11 +-------------------- +* Update `PEP8` to version 1.4.5; +* Update `Pylint` to version 0.27.0; +* Update `autopep8` to version 0.8.7; +* Fix breakpoint definition; +* Update python syntax; +* Fixed run-time error when output non-ascii in multibyte locale; +* Move initialization into ftplugin as it is python specific; +* Pyrex (Cython) files support; +* Support `raw_input` in run python code; Requirements diff --git a/doc/pymode.txt b/doc/pymode.txt index 4cde9a18..fb454d82 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.9 + Version: 0.6.11 ============================================================================== CONTENTS *Python-mode-contents* From 3f048fa24200da65e43392c6419e2cb75fe0b0da Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 03:45:52 +0800 Subject: [PATCH 157/513] Fix run --- autoload/pymode/run.vim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index d6e5e3b8..8fb584b4 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -15,8 +15,7 @@ fun! pymode#run#Run(line1, line2) "{{{ call pymode#WideMessage("Code running.") try call setqflist([]) - py execfile(vim.eval('expand("%s:p")'), {'raw_input': lambda s: - vim.eval('input("{0}")'.format(s)), 'input': vim.eval('input("{0}")'.format(s))}) + py execfile(vim.eval('expand("%s:p")'), {'raw_input': lambda s: vim.eval('input("{0}")'.format(s)), 'input': lambda s: vim.eval('input("{0}")'.format(s))}) py out, err = sys.stdout.getvalue().strip(), sys.stderr.getvalue() py sys.stdout, sys.stderr = stdout_, stderr_ From 21b444c8cfcf02a78031441fbd5be89e22885ec8 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 03:46:27 +0800 Subject: [PATCH 158/513] Fix run bug --- Changelog.rst | 2 +- README.rst | 2 +- doc/pymode.txt | 2 +- ftplugin/python/init-pymode.vim | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 2f672192..56ded67a 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -## 2013-03-15 0.6.11 +## 2013-03-15 0.6.12 -------------------- * Update `PEP8` to version 1.4.5; * Update `Pylint` to version 0.27.0; diff --git a/README.rst b/README.rst index 7d02f392..96b5e61c 100644 --- a/README.rst +++ b/README.rst @@ -31,7 +31,7 @@ Another old presentation here: http://www.youtube.com/watch?v=YhqsjUUHj6g Changelog ========= -## 2013-03-15 0.6.11 +## 2013-03-15 0.6.12 -------------------- * Update `PEP8` to version 1.4.5; * Update `Pylint` to version 0.27.0; diff --git a/doc/pymode.txt b/doc/pymode.txt index fb454d82..b0dbc62d 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.11 + Version: 0.6.12 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index 4807c84a..1ff5840f 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -3,7 +3,7 @@ if exists('did_init_pymode_vim') endif let did_init_pymode_vim = 1 -let g:pymode_version = "0.6.11" +let g:pymode_version = "0.6.12" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 28d6db24d34dadb6b1147b945562126e0e06b815 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 14:42:13 +0800 Subject: [PATCH 159/513] Fix pylint dynamic usage --- pylibs/pylama/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py index b682b013..8cff4b94 100644 --- a/pylibs/pylama/utils.py +++ b/pylibs/pylama/utils.py @@ -67,8 +67,11 @@ def pyflakes(path, code=None, **meta): def pylint(path, **meta): from .pylint.lint import Run + from .pylint.logilab.astng.builder import MANAGER from .pylint.reporters import BaseReporter + MANAGER.astng_cache.clear() + class Reporter(BaseReporter): def __init__(self): From 5e00c83af76e908ae79244b8cdbb08ef17dd5846 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 15:57:43 +0800 Subject: [PATCH 160/513] Update pyflakes --- pylibs/pylama/__init__.py | 2 +- pylibs/pylama/pyflakes/__init__.py | 2 +- pylibs/pylama/pyflakes/checker.py | 604 +++++++++++++++-------------- pylibs/pylama/pyflakes/messages.py | 27 +- pylibs/pylama/utils.py | 4 +- 5 files changed, 341 insertions(+), 298 deletions(-) diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index 2acd1b0b..fb034e2c 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -1,6 +1,6 @@ " pylama -- Python code audit. " -version_info = (0, 2, 2) +version_info = (0, 2, 3) __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/pyflakes/__init__.py b/pylibs/pylama/pyflakes/__init__.py index 652a8f47..7a2d0cd1 100644 --- a/pylibs/pylama/pyflakes/__init__.py +++ b/pylibs/pylama/pyflakes/__init__.py @@ -1,2 +1,2 @@ -__version__ = '0.4.0' +__version__ = '0.6.1' diff --git a/pylibs/pylama/pyflakes/checker.py b/pylibs/pylama/pyflakes/checker.py index 8b0bab19..7e5286e2 100644 --- a/pylibs/pylama/pyflakes/checker.py +++ b/pylibs/pylama/pyflakes/checker.py @@ -2,31 +2,46 @@ # (c) 2005-2010 Divmod, Inc. # See LICENSE file for details -import __builtin__ import os.path -import _ast - -from . import messages - +try: + import builtins + PY2 = False +except ImportError: + import __builtin__ as builtins + PY2 = True -# utility function to iterate over an AST node's children, adapted -# from Python 2.6's standard ast module try: import ast iter_child_nodes = ast.iter_child_nodes -except (ImportError, AttributeError): - def iter_child_nodes(node, astcls=_ast.AST): +except ImportError: # Python 2.5 + import _ast as ast + + if 'decorator_list' not in ast.ClassDef._fields: + # Patch the missing attribute 'decorator_list' + ast.ClassDef.decorator_list = () + ast.FunctionDef.decorator_list = property(lambda s: s.decorators) + + def iter_child_nodes(node): """ - Yield all direct child nodes of *node*, that is, all fields that are nodes - and all items of fields that are lists of nodes. + Yield all direct child nodes of *node*, that is, all fields that + are nodes and all items of fields that are lists of nodes. """ for name in node._fields: field = getattr(node, name, None) - if isinstance(field, astcls): + if isinstance(field, ast.AST): yield field elif isinstance(field, list): for item in field: yield item +# Python >= 3.3 uses ast.Try instead of (ast.TryExcept + ast.TryFinally) +if hasattr(ast, 'Try'): + ast_TryExcept = ast.Try + ast_TryFinally = () +else: + ast_TryExcept = ast.TryExcept + ast_TryFinally = ast.TryFinally + +from . import messages class Binding(object): @@ -46,11 +61,9 @@ def __init__(self, name, source): self.source = source self.used = False - def __str__(self): return self.name - def __repr__(self): return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__, self.name, @@ -58,10 +71,8 @@ def __repr__(self): id(self)) - class UnBinding(Binding): - '''Created by the 'del' operator.''' - + """Created by the 'del' operator.""" class Importation(Binding): @@ -78,13 +89,17 @@ def __init__(self, name, source): super(Importation, self).__init__(name, source) - class Argument(Binding): """ Represents binding a name as an argument. """ +class Definition(Binding): + """ + A binding that defines a function or a class. + """ + class Assignment(Binding): """ @@ -96,10 +111,12 @@ class Assignment(Binding): """ +class FunctionDefinition(Definition): + pass -class FunctionDefinition(Binding): - _property_decorator = False +class ClassDefinition(Definition): + pass class ExportBinding(Binding): @@ -121,33 +138,25 @@ def names(self): Return a list of the names referenced by this binding. """ names = [] - if isinstance(self.source, _ast.List): + if isinstance(self.source, ast.List): for node in self.source.elts: - if isinstance(node, _ast.Str): + if isinstance(node, ast.Str): names.append(node.s) return names - class Scope(dict): importStarred = False # set to True when import * is found usesLocals = False - def __repr__(self): return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self)) - def __init__(self): - super(Scope, self).__init__() - - - class ClassScope(Scope): pass - class FunctionScope(Scope): """ I represent a name scope for a function. @@ -159,14 +168,21 @@ def __init__(self): self.globals = {} - class ModuleScope(Scope): pass -# Globally defined names which are not attributes of the __builtin__ module. -_MAGIC_GLOBALS = ['__file__', '__builtins__'] +# Globally defined names which are not attributes of the builtins module, or +# are only present on some platforms. +_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError'] + +def getNodeName(node): + # Returns node.id, or node.name, or None + if hasattr(node, 'id'): # One of the many nodes with an id + return node.id + if hasattr(node, 'name'): # a ExceptHandler node + return node.name class Checker(object): @@ -184,44 +200,43 @@ class Checker(object): nodeDepth = 0 traceTree = False + builtIns = set(dir(builtins)) | set(_MAGIC_GLOBALS) - def __init__(self, tree, filename=None): - if filename is None: - filename = '(none)' + def __init__(self, tree, filename='(none)', builtins=None): self._deferredFunctions = [] self._deferredAssignments = [] - self.dead_scopes = [] + self.deadScopes = [] self.messages = [] self.filename = filename + if builtins: + self.builtIns = self.builtIns.union(builtins) self.scopeStack = [ModuleScope()] self.futuresAllowed = True self.root = tree self.handleChildren(tree) - self._runDeferred(self._deferredFunctions) + self.runDeferred(self._deferredFunctions) # Set _deferredFunctions to None so that deferFunction will fail # noisily if called after we've run through the deferred functions. self._deferredFunctions = None - self._runDeferred(self._deferredAssignments) + self.runDeferred(self._deferredAssignments) # Set _deferredAssignments to None so that deferAssignment will fail - # noisly if called after we've run through the deferred assignments. + # noisily if called after we've run through the deferred assignments. self._deferredAssignments = None del self.scopeStack[1:] self.popScope() - self.check_dead_scopes() - + self.checkDeadScopes() def deferFunction(self, callable): - ''' + """ Schedule a function handler to be called just before completion. This is used for handling function bodies, which must be deferred because code later in the file might modify the global scope. When `callable` is called, the scope at the time this is called will be restored, however it will contain any new bindings added to it. - ''' + """ self._deferredFunctions.append((callable, self.scopeStack[:])) - def deferAssignment(self, callable): """ Schedule an assignment handler to be called just after deferred @@ -229,8 +244,7 @@ def deferAssignment(self, callable): """ self._deferredAssignments.append((callable, self.scopeStack[:])) - - def _runDeferred(self, deferred): + def runDeferred(self, deferred): """ Run the callables in C{deferred} using their associated scope stack. """ @@ -238,44 +252,37 @@ def _runDeferred(self, deferred): self.scopeStack = scope handler() - + @property def scope(self): return self.scopeStack[-1] - scope = property(scope) def popScope(self): - self.dead_scopes.append(self.scopeStack.pop()) - + self.deadScopes.append(self.scopeStack.pop()) - def check_dead_scopes(self): + def checkDeadScopes(self): """ Look at scopes which have been fully examined and report names in them which were imported but unused. """ - for scope in self.dead_scopes: + for scope in self.deadScopes: export = isinstance(scope.get('__all__'), ExportBinding) if export: all = scope['__all__'].names() - if os.path.split(self.filename)[1] != '__init__.py': + if not scope.importStarred and os.path.basename(self.filename) != '__init__.py': # Look for possible mistakes in the export list undefined = set(all) - set(scope) for name in undefined: - self.report( - messages.UndefinedExport, - scope['__all__'].source, - name) + self.report(messages.UndefinedExport, + scope['__all__'].source.lineno, name) else: all = [] # Look for imported names that aren't used. - for importation in scope.itervalues(): + for importation in scope.values(): if isinstance(importation, Importation): if not importation.used and importation.name not in all: - self.report( - messages.UnusedImport, - importation.source, - importation.name) - + self.report(messages.UnusedImport, + importation.source.lineno, importation.name) def pushFunctionScope(self): self.scopeStack.append(FunctionScope()) @@ -286,53 +293,176 @@ def pushClassScope(self): def report(self, messageClass, *args, **kwargs): self.messages.append(messageClass(self.filename, *args, **kwargs)) - def lowestCommonAncestor(self, lnode, rnode, stop=None): + def hasParent(self, node, kind): + while hasattr(node, 'parent'): + node = node.parent + if isinstance(node, kind): + return True + + def getCommonAncestor(self, lnode, rnode, stop=None): if not stop: stop = self.root - if lnode is stop: - return lnode - if rnode is stop: - return rnode - if lnode is rnode: return lnode + if stop in (lnode, rnode): + return stop + + if not hasattr(lnode, 'parent') or not hasattr(rnode, 'parent'): + return if (lnode.level > rnode.level): - return self.lowestCommonAncestor(lnode.parent, rnode, stop) + return self.getCommonAncestor(lnode.parent, rnode, stop) if (rnode.level > lnode.level): - return self.lowestCommonAncestor(lnode, rnode.parent, stop) - if lnode.parent is rnode.parent: - return lnode.parent - else: - return self.lowestCommonAncestor(lnode.parent, rnode.parent, stop) + return self.getCommonAncestor(lnode, rnode.parent, stop) + return self.getCommonAncestor(lnode.parent, rnode.parent, stop) def descendantOf(self, node, ancestors, stop=None): for a in ancestors: - try: - p = self.lowestCommonAncestor(node, a, stop) - if not p is stop: - return True - except AttributeError: - # Skip some bogus objects like <_ast.Pass> - pass + if self.getCommonAncestor(node, a, stop) not in (stop, None): + return True return False def onFork(self, parent, lnode, rnode, items): - return int(self.descendantOf(lnode, items, parent)) + \ - int(self.descendantOf(rnode, items, parent)) + return (self.descendantOf(lnode, items, parent) ^ + self.descendantOf(rnode, items, parent)) def differentForks(self, lnode, rnode): - "True, if lnode and rnode are located on different forks of IF/TRY" - ancestor = self.lowestCommonAncestor(lnode, rnode) - if isinstance(ancestor, _ast.If): + """True, if lnode and rnode are located on different forks of IF/TRY""" + ancestor = self.getCommonAncestor(lnode, rnode) + if isinstance(ancestor, ast.If): for fork in (ancestor.body, ancestor.orelse): - if self.onFork(ancestor, lnode, rnode, fork) == 1: + if self.onFork(ancestor, lnode, rnode, fork): return True - if isinstance(ancestor, _ast.TryExcept): - for fork in (ancestor.body, ancestor.handlers, ancestor.orelse): - if self.onFork(ancestor, lnode, rnode, fork) == 1: + elif isinstance(ancestor, ast_TryExcept): + body = ancestor.body + ancestor.orelse + for fork in [body] + [[hdl] for hdl in ancestor.handlers]: + if self.onFork(ancestor, lnode, rnode, fork): return True + elif isinstance(ancestor, ast_TryFinally): + if self.onFork(ancestor, lnode, rnode, ancestor.body): + return True return False + def addBinding(self, node, value, reportRedef=True): + """ + Called when a binding is altered. + + - `node` is the statement responsible for the change + - `value` is the optional new value, a Binding instance, associated + with the binding; if None, the binding is deleted if it exists. + - if `reportRedef` is True (default), rebinding while unused will be + reported. + """ + redefinedWhileUnused = False + if not isinstance(self.scope, ClassScope): + for scope in self.scopeStack[::-1]: + existing = scope.get(value.name) + if (isinstance(existing, Importation) + and not existing.used + and (not isinstance(value, Importation) or value.fullName == existing.fullName) + and reportRedef + and not self.differentForks(node, existing.source)): + redefinedWhileUnused = True + self.report(messages.RedefinedWhileUnused, + node.lineno, value.name, existing.source.lineno) + + existing = self.scope.get(value.name) + if not redefinedWhileUnused and self.hasParent(value.source, ast.ListComp): + if (existing and reportRedef + and not self.hasParent(existing.source, (ast.For, ast.ListComp)) + and not self.differentForks(node, existing.source)): + self.report(messages.RedefinedInListComp, + node.lineno, value.name, existing.source.lineno) + + if isinstance(value, UnBinding): + try: + del self.scope[value.name] + except KeyError: + self.report(messages.UndefinedName, node.lineno, value.name) + elif (isinstance(existing, Definition) + and not existing.used + and not self.differentForks(node, existing.source)): + self.report(messages.RedefinedWhileUnused, + node.lineno, value.name, existing.source.lineno) + else: + self.scope[value.name] = value + + def handleNodeLoad(self, node): + name = getNodeName(node) + if not name: + return + # try local scope + importStarred = self.scope.importStarred + try: + self.scope[name].used = (self.scope, node.lineno) + except KeyError: + pass + else: + return + + # try enclosing function scopes + for scope in self.scopeStack[-2:0:-1]: + importStarred = importStarred or scope.importStarred + if not isinstance(scope, FunctionScope): + continue + try: + scope[name].used = (self.scope, node.lineno) + except KeyError: + pass + else: + return + + # try global scope + importStarred = importStarred or self.scopeStack[0].importStarred + try: + self.scopeStack[0][name].used = (self.scope, node.lineno) + except KeyError: + if not importStarred and name not in self.builtIns: + if (os.path.basename(self.filename) == '__init__.py' and name == '__path__'): + # the special name __path__ is valid only in packages + pass + else: + self.report(messages.UndefinedName, node.lineno, name) + + def handleNodeStore(self, node): + name = getNodeName(node) + if not name: + return + # if the name hasn't already been defined in the current scope + if isinstance(self.scope, FunctionScope) and name not in self.scope: + # for each function or module scope above us + for scope in self.scopeStack[:-1]: + if not isinstance(scope, (FunctionScope, ModuleScope)): + continue + # if the name was defined in that scope, and the name has + # been accessed already in the current scope, and hasn't + # been declared global + if (name in scope and scope[name].used and scope[name].used[0] is self.scope + and name not in self.scope.globals): + # then it's probably a mistake + self.report(messages.UndefinedLocal, + scope[name].used[1], name, scope[name].source.lineno) + break + + parent = getattr(node, 'parent', None) + if isinstance(parent, (ast.For, ast.comprehension, ast.Tuple, ast.List)): + binding = Binding(name, node) + elif parent is not None and name == '__all__' and isinstance(self.scope, ModuleScope): + binding = ExportBinding(name, parent.value) + else: + binding = Assignment(name, node) + if name in self.scope: + binding.used = self.scope[name].used + self.addBinding(node, binding) + + def handleNodeDelete(self, node): + name = getNodeName(node) + if not name: + return + if isinstance(self.scope, FunctionScope) and name in self.scope.globals: + del self.scope.globals[name] + else: + self.addBinding(node, UnBinding(name, node)) + def handleChildren(self, tree): for node in iter_child_nodes(tree): self.handleNode(node, tree) @@ -342,17 +472,18 @@ def isDocstring(self, node): Determine if the given node is a docstring, as long as it is at the correct place in the node tree. """ - return isinstance(node, _ast.Str) or \ - (isinstance(node, _ast.Expr) and - isinstance(node.value, _ast.Str)) + return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and + isinstance(node.value, ast.Str)) def handleNode(self, node, parent): + if node is None: + return node.parent = parent if self.traceTree: - print ' ' * self.nodeDepth + node.__class__.__name__ + print(' ' * self.nodeDepth + node.__class__.__name__) self.nodeDepth += 1 - if self.futuresAllowed and not \ - (isinstance(node, _ast.ImportFrom) or self.isDocstring(node)): + if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or + self.isDocstring(node)): self.futuresAllowed = False nodeType = node.__class__.__name__.upper() node.level = self.nodeDepth @@ -362,22 +493,23 @@ def handleNode(self, node, parent): finally: self.nodeDepth -= 1 if self.traceTree: - print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__ + print(' ' * self.nodeDepth + 'end ' + node.__class__.__name__) def ignore(self, node): pass # "stmt" type nodes - RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \ - TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren + RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = \ + TRYEXCEPT = TRYFINALLY = TRY = ASSERT = EXEC = EXPR = handleChildren CONTINUE = BREAK = PASS = ignore # "expr" type nodes - BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = COMPARE = \ - CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = handleChildren + BOOLOP = BINOP = UNARYOP = IFEXP = DICT = SET = YIELD = YIELDFROM = \ + COMPARE = CALL = REPR = ATTRIBUTE = SUBSCRIPT = LIST = TUPLE = \ + STARRED = handleChildren - NUM = STR = ELLIPSIS = ignore + NUM = STR = BYTES = ELLIPSIS = ignore # "slice" type nodes SLICE = EXTSLICE = INDEX = handleChildren @@ -387,48 +519,11 @@ def ignore(self, node): # same for operators AND = OR = ADD = SUB = MULT = DIV = MOD = POW = LSHIFT = RSHIFT = \ - BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ - EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore + BITOR = BITXOR = BITAND = FLOORDIV = INVERT = NOT = UADD = USUB = \ + EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore # additional node types - COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren - - def addBinding(self, loc, value, reportRedef=True): - '''Called when a binding is altered. - - - `loc` is the location (an object with lineno and optionally - col_offset attributes) of the statement responsible for the change - - `value` is the optional new value, a Binding instance, associated - with the binding; if None, the binding is deleted if it exists. - - if `reportRedef` is True (default), rebinding while unused will be - reported. - ''' - if (isinstance(self.scope.get(value.name), FunctionDefinition) - and isinstance(value, FunctionDefinition)): - if not value._property_decorator: - if not self.differentForks(loc, self.scope[value.name].source): - self.report(messages.RedefinedFunction, - loc, value.name, self.scope[value.name].source) - - if not isinstance(self.scope, ClassScope): - for scope in self.scopeStack[::-1]: - existing = scope.get(value.name) - if (isinstance(existing, Importation) - and not existing.used - and (not isinstance(value, Importation) or value.fullName == existing.fullName) - and reportRedef): - - if not self.differentForks(loc, existing.source): - self.report(messages.RedefinedWhileUnused, - loc, value.name, existing.source) - - if isinstance(value, UnBinding): - try: - del self.scope[value.name] - except KeyError: - self.report(messages.UndefinedName, loc, value.name) - else: - self.scope[value.name] = value + COMPREHENSION = KEYWORD = handleChildren def GLOBAL(self, node): """ @@ -437,30 +532,42 @@ def GLOBAL(self, node): if isinstance(self.scope, FunctionScope): self.scope.globals.update(dict.fromkeys(node.names)) + NONLOCAL = GLOBAL + def LISTCOMP(self, node): # handle generators before element for gen in node.generators: self.handleNode(gen, node) self.handleNode(node.elt, node) - GENERATOREXP = SETCOMP = LISTCOMP + def GENERATOREXP(self, node): + self.pushFunctionScope() + # handle generators before element + for gen in node.generators: + self.handleNode(gen, node) + self.handleNode(node.elt, node) + self.popScope() + + SETCOMP = GENERATOREXP - # dictionary comprehensions; introduced in Python 2.7 def DICTCOMP(self, node): + self.pushFunctionScope() for gen in node.generators: self.handleNode(gen, node) self.handleNode(node.key, node) self.handleNode(node.value, node) + self.popScope() def FOR(self, node): """ Process bindings for loop variables. """ vars = [] + def collectLoopVars(n): - if isinstance(n, _ast.Name): + if isinstance(n, ast.Name): vars.append(n.id) - elif isinstance(n, _ast.expr_context): + elif isinstance(n, ast.expr_context): return else: for c in iter_child_nodes(n): @@ -472,7 +579,7 @@ def collectLoopVars(n): # unused ones will get an unused import warning and self.scope[varn].used): self.report(messages.ImportShadowedByLoopVar, - node, varn, self.scope[varn].source) + node.lineno, varn, self.scope[varn].source.lineno) self.handleChildren(node) @@ -480,142 +587,68 @@ def NAME(self, node): """ Handle occurrence of Name (which can be a load/store/delete access.) """ - if node.id == 'locals' and isinstance(node.parent, _ast.Call): + if node.id == 'locals' and isinstance(node.parent, ast.Call): # we are doing locals() call in current scope self.scope.usesLocals = True - # Locate the name in locals / function / globals scopes. - if isinstance(node.ctx, (_ast.Load, _ast.AugLoad)): - # try local scope - importStarred = self.scope.importStarred - try: - self.scope[node.id].used = (self.scope, node) - except KeyError: - pass - else: - return - - # try enclosing function scopes - - for scope in self.scopeStack[-2:0:-1]: - importStarred = importStarred or scope.importStarred - if not isinstance(scope, FunctionScope): - continue - try: - scope[node.id].used = (self.scope, node) - except KeyError: - pass - else: - return - - # try global scope - - importStarred = importStarred or self.scopeStack[0].importStarred - try: - self.scopeStack[0][node.id].used = (self.scope, node) - except KeyError: - if ((not hasattr(__builtin__, node.id)) - and node.id not in _MAGIC_GLOBALS - and not importStarred): - if (os.path.basename(self.filename) == '__init__.py' and - node.id == '__path__'): - # the special name __path__ is valid only in packages - pass - else: - self.report(messages.UndefinedName, node, node.id) - elif isinstance(node.ctx, (_ast.Store, _ast.AugStore)): - # if the name hasn't already been defined in the current scope - if isinstance(self.scope, FunctionScope) and node.id not in self.scope: - # for each function or module scope above us - for scope in self.scopeStack[:-1]: - if not isinstance(scope, (FunctionScope, ModuleScope)): - continue - # if the name was defined in that scope, and the name has - # been accessed already in the current scope, and hasn't - # been declared global - if (node.id in scope - and scope[node.id].used - and scope[node.id].used[0] is self.scope - and node.id not in self.scope.globals): - # then it's probably a mistake - self.report(messages.UndefinedLocal, - scope[node.id].used[1], - node.id, - scope[node.id].source) - break - - if isinstance(node.parent, - (_ast.For, _ast.comprehension, _ast.Tuple, _ast.List)): - binding = Binding(node.id, node) - elif (node.id == '__all__' and - isinstance(self.scope, ModuleScope)): - binding = ExportBinding(node.id, node.parent.value) - else: - binding = Assignment(node.id, node) - if node.id in self.scope: - binding.used = self.scope[node.id].used - self.addBinding(node, binding) - elif isinstance(node.ctx, _ast.Del): - if isinstance(self.scope, FunctionScope) and \ - node.id in self.scope.globals: - del self.scope.globals[node.id] - else: - self.addBinding(node, UnBinding(node.id, node)) + if isinstance(node.ctx, (ast.Load, ast.AugLoad)): + self.handleNodeLoad(node) + elif isinstance(node.ctx, (ast.Store, ast.AugStore)): + self.handleNodeStore(node) + elif isinstance(node.ctx, ast.Del): + self.handleNodeDelete(node) else: # must be a Param context -- this only happens for names in function # arguments, but these aren't dispatched through here - raise RuntimeError( - "Got impossible expression context: %r" % (node.ctx,)) - + raise RuntimeError("Got impossible expression context: %r" % (node.ctx,)) def FUNCTIONDEF(self, node): - # the decorators attribute is called decorator_list as of Python 2.6 - if hasattr(node, 'decorators'): - for deco in node.decorators: - self.handleNode(deco, node) - else: - for deco in node.decorator_list: - self.handleNode(deco, node) - - # Check for property decorator - func_def = FunctionDefinition(node.name, node) - - if hasattr(node, 'decorators'): - for decorator in node.decorators: - if getattr(decorator, 'attr', None) in ('setter', 'deleter'): - func_def._property_decorator = True - else: - for decorator in node.decorator_list: - if getattr(decorator, 'attr', None) in ('setter', 'deleter'): - func_def._property_decorator = True - - self.addBinding(node, func_def) + for deco in node.decorator_list: + self.handleNode(deco, node) + self.addBinding(node, FunctionDefinition(node.name, node)) self.LAMBDA(node) def LAMBDA(self, node): - for default in node.args.defaults: - self.handleNode(default, node) - - def runFunction(): - args = [] + args = [] + if PY2: def addArgs(arglist): for arg in arglist: - if isinstance(arg, _ast.Tuple): + if isinstance(arg, ast.Tuple): addArgs(arg.elts) else: if arg.id in args: self.report(messages.DuplicateArgument, - node, arg.id) + node.lineno, arg.id) args.append(arg.id) + addArgs(node.args.args) + defaults = node.args.defaults + else: + for arg in node.args.args + node.args.kwonlyargs: + if arg.arg in args: + self.report(messages.DuplicateArgument, + node.lineno, arg.arg) + args.append(arg.arg) + self.handleNode(arg.annotation, node) + if hasattr(node, 'returns'): # Only for FunctionDefs + for annotation in (node.args.varargannotation, + node.args.kwargannotation, node.returns): + self.handleNode(annotation, node) + defaults = node.args.defaults + node.args.kw_defaults + + # vararg/kwarg identifiers are not Name nodes + for wildcard in (node.args.vararg, node.args.kwarg): + if not wildcard: + continue + if wildcard in args: + self.report(messages.DuplicateArgument, node.lineno, wildcard) + args.append(wildcard) + for default in defaults: + self.handleNode(default, node) + + def runFunction(): self.pushFunctionScope() - addArgs(node.args.args) - # vararg/kwarg identifiers are not Name nodes - if node.args.vararg: - args.append(node.args.vararg) - if node.args.kwarg: - args.append(node.args.kwarg) for name in args: self.addBinding(node, Argument(name, node), reportRedef=False) if isinstance(node.body, list): @@ -625,38 +658,40 @@ def addArgs(arglist): else: # case for Lambdas self.handleNode(node.body, node) + def checkUnusedAssignments(): """ Check to see if any assignments have not been used. """ - for name, binding in self.scope.iteritems(): - if (not binding.used and not name in self.scope.globals - and not self.scope.usesLocals - and isinstance(binding, Assignment)): + for name, binding in self.scope.items(): + if (not binding.used and name not in self.scope.globals + and not self.scope.usesLocals + and isinstance(binding, Assignment)): self.report(messages.UnusedVariable, - binding.source, name) + binding.source.lineno, name) self.deferAssignment(checkUnusedAssignments) self.popScope() self.deferFunction(runFunction) - def CLASSDEF(self, node): """ Check names used in a class definition, including its decorators, base classes, and the body of its definition. Additionally, add its name to the current scope. """ - # decorator_list is present as of Python 2.6 - for deco in getattr(node, 'decorator_list', []): + for deco in node.decorator_list: self.handleNode(deco, node) for baseNode in node.bases: self.handleNode(baseNode, node) + if not PY2: + for keywordNode in node.keywords: + self.handleNode(keywordNode, node) self.pushClassScope() for stmt in node.body: self.handleNode(stmt, node) self.popScope() - self.addBinding(node, Binding(node.name, node)) + self.addBinding(node, ClassDefinition(node.name, node)) def ASSIGN(self, node): self.handleNode(node.value, node) @@ -664,12 +699,8 @@ def ASSIGN(self, node): self.handleNode(target, node) def AUGASSIGN(self, node): - # AugAssign is awkward: must set the context explicitly and visit twice, - # once with AugLoad context, once with AugStore context - node.target.ctx = _ast.AugLoad() - self.handleNode(node.target, node) + self.handleNodeLoad(node.target) self.handleNode(node.value, node) - node.target.ctx = _ast.AugStore() self.handleNode(node.target, node) def IMPORT(self, node): @@ -681,20 +712,27 @@ def IMPORT(self, node): def IMPORTFROM(self, node): if node.module == '__future__': if not self.futuresAllowed: - self.report(messages.LateFutureImport, node, - [n.name for n in node.names]) + self.report(messages.LateFutureImport, + node.lineno, [n.name for n in node.names]) else: self.futuresAllowed = False for alias in node.names: if alias.name == '*': self.scope.importStarred = True - self.report(messages.ImportStarUsed, node, node.module) + self.report(messages.ImportStarUsed, node.lineno, node.module) continue name = alias.asname or alias.name importation = Importation(name, node) if node.module == '__future__': - importation.used = (self.scope, node) + importation.used = (self.scope, node.lineno) self.addBinding(node, importation) -# lint=0 + def EXCEPTHANDLER(self, node): + # 3.x: in addition to handling children, we must handle the name of + # the exception, which is not a Name node, but a simple string. + if isinstance(node.name, str): + self.handleNodeStore(node) + self.handleChildren(node) + +# pymode:lint=0 diff --git a/pylibs/pylama/pyflakes/messages.py b/pylibs/pylama/pyflakes/messages.py index 56beb5cd..b15fad63 100644 --- a/pylibs/pylama/pyflakes/messages.py +++ b/pylibs/pylama/pyflakes/messages.py @@ -1,12 +1,13 @@ # (c) 2005 Divmod, Inc. See LICENSE file for details + class Message(object): message = '' message_args = () - def __init__(self, filename, loc, use_column=True): + + def __init__(self, filename, lineno): self.filename = filename - self.lineno = loc.lineno - self.col = getattr(loc, 'col_offset', None) if use_column else None + self.lineno = lineno def __str__(self): return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args) @@ -28,8 +29,16 @@ def __init__(self, filename, lineno, name, orig_lineno): self.message_args = (name, orig_lineno) +class RedefinedInListComp(Message): + message = 'W801 rlist comprehension redefines %r from line %r' + + def __init__(self, filename, lineno, name, orig_lineno): + Message.__init__(self, filename, lineno) + self.message_args = (name, orig_lineno) + + class ImportShadowedByLoopVar(Message): - message = 'W403 import %r from line %r shadowed by loop variable' + message = 'W403 iimport %r from line %r shadowed by loop variable' def __init__(self, filename, lineno, name, orig_lineno): Message.__init__(self, filename, lineno) @@ -37,7 +46,7 @@ def __init__(self, filename, lineno, name, orig_lineno): class ImportStarUsed(Message): - message = "W404 'from %s import *' used; unable to detect undefined names" + message = "W404 ''from %s import *' used; unable to detect undefined names" def __init__(self, filename, lineno, modname): Message.__init__(self, filename, lineno) @@ -61,8 +70,7 @@ def __init__(self, filename, lineno, name): class UndefinedLocal(Message): - message = "W804 local variable %r (defined in enclosing scope on line " \ - "%r) referenced before assignment" + message = "W804 local variable %r (defined in enclosing scope on line %r) referenced before assignment" def __init__(self, filename, lineno, name, orig_lineno): Message.__init__(self, filename, lineno) @@ -77,8 +85,8 @@ def __init__(self, filename, lineno, name): self.message_args = (name,) -class RedefinedFunction(Message): - message = 'W806 redefinition of function %r from line %r' +class Redefined(Message): + message = 'W806 redefinition of %r from line %r' def __init__(self, filename, lineno, name, orig_lineno): Message.__init__(self, filename, lineno) @@ -98,7 +106,6 @@ class UnusedVariable(Message): Indicates that a variable has been explicity assigned to but not actually used. """ - message = 'W806 local variable %r is assigned to but never used' def __init__(self, filename, lineno, names): diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py index 8cff4b94..a5c3ba47 100644 --- a/pylibs/pylama/utils.py +++ b/pylibs/pylama/utils.py @@ -58,18 +58,16 @@ def pyflakes(path, code=None, **meta): for w in w.messages: errors.append(dict( lnum=w.lineno, - col=w.col, text=w.message % w.message_args, - type='E' )) return errors def pylint(path, **meta): from .pylint.lint import Run - from .pylint.logilab.astng.builder import MANAGER from .pylint.reporters import BaseReporter + from .pylint.logilab.astng.builder import MANAGER MANAGER.astng_cache.clear() class Reporter(BaseReporter): From 996c49648f0eab9d05b1a1e5818921b7826582b6 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 16:00:32 +0800 Subject: [PATCH 161/513] Update version --- Changelog.rst | 3 ++- doc/pymode.txt | 2 +- ftplugin/python/init-pymode.vim | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 56ded67a..0d047321 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,10 +1,11 @@ Changelog ========= -## 2013-03-15 0.6.12 +## 2013-03-16 0.6.13 -------------------- * Update `PEP8` to version 1.4.5; * Update `Pylint` to version 0.27.0; +* Update `pyflakes` to version 0.6.1; * Update `autopep8` to version 0.8.7; * Fix breakpoint definition; * Update python syntax; diff --git a/doc/pymode.txt b/doc/pymode.txt index b0dbc62d..30566ded 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.12 + Version: 0.6.13 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index 1ff5840f..e613cc9c 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -3,7 +3,7 @@ if exists('did_init_pymode_vim') endif let did_init_pymode_vim = 1 -let g:pymode_version = "0.6.12" +let g:pymode_version = "0.6.13" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 91ffa661367352431d1a69c29449cf9189abf836 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 19:04:02 +0800 Subject: [PATCH 162/513] Fix run --- autoload/pymode/breakpoint.vim | 8 +------- autoload/pymode/lint.vim | 4 ++-- autoload/pymode/run.vim | 8 +++++--- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/autoload/pymode/breakpoint.vim b/autoload/pymode/breakpoint.vim index 7d983276..b71bc596 100644 --- a/autoload/pymode/breakpoint.vim +++ b/autoload/pymode/breakpoint.vim @@ -8,13 +8,7 @@ fun! pymode#breakpoint#Set(lnum) "{{{ normal k endif - " Disable lint - let pymode_lint = g:pymode_lint - let g:pymode_lint = 0 - " Save file - if &modifiable && &modified | write | endif - - let g:pymode_lint = pymode_lint + if &modifiable && &modified | noautocmd write | endif endfunction "}}} diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 364cf114..e9c891f7 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -5,7 +5,7 @@ fun! pymode#lint#Check() "{{{ if &modifiable && &modified try - write + noautocmd write catch /E212/ echohl Error | echo "File modified and I can't save it. Cancel code checking." | echohl None return 0 @@ -94,7 +94,7 @@ endfunction " }}} fun! pymode#lint#Auto() "{{{ if &modifiable && &modified try - write + noautocmd write catch /E212/ echohl Error | echo "File modified and I can't save it. Cancel operation." | echohl None return 0 diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index 8fb584b4..f4bcab8b 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -2,7 +2,7 @@ fun! pymode#run#Run(line1, line2) "{{{ if &modifiable && &modified try - write + noautocmd write catch /E212/ echohl Error | echo "File modified and I can't save it. Cancel code checking." | echohl None return 0 @@ -12,10 +12,12 @@ fun! pymode#run#Run(line1, line2) "{{{ py sys.stdout, stdout_ = StringIO.StringIO(), sys.stdout py sys.stderr, stderr_ = StringIO.StringIO(), sys.stderr py enc = vim.eval('&enc') + call setqflist([]) call pymode#WideMessage("Code running.") try - call setqflist([]) - py execfile(vim.eval('expand("%s:p")'), {'raw_input': lambda s: vim.eval('input("{0}")'.format(s)), 'input': lambda s: vim.eval('input("{0}")'.format(s))}) + py context = globals() + py context['raw_input'] = context['input'] = lambda s: vim.eval('input("{0}")'.format(s)) + py execfile(vim.eval('expand("%:p")'), context) py out, err = sys.stdout.getvalue().strip(), sys.stderr.getvalue() py sys.stdout, sys.stderr = stdout_, stderr_ From 36dc194d4f4dd02bc560e3e294b3c7ba6d368702 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 15 Mar 2013 19:05:20 +0800 Subject: [PATCH 163/513] update version --- Changelog.rst | 2 +- doc/pymode.txt | 2 +- ftplugin/python/init-pymode.vim | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index 0d047321..a3061325 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -## 2013-03-16 0.6.13 +## 2013-03-16 0.6.14 -------------------- * Update `PEP8` to version 1.4.5; * Update `Pylint` to version 0.27.0; diff --git a/doc/pymode.txt b/doc/pymode.txt index 30566ded..a2e4cb73 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.13 + Version: 0.6.14 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index e613cc9c..35ca48f8 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -3,7 +3,7 @@ if exists('did_init_pymode_vim') endif let did_init_pymode_vim = 1 -let g:pymode_version = "0.6.13" +let g:pymode_version = "0.6.14" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From 64ed1346d2382d38999f34896efa6ae8eda8ecda Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 19 Mar 2013 18:26:48 +0800 Subject: [PATCH 164/513] Fix mccabe --- pylibs/pymode/lint.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index 074d1be3..9f4683f1 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -15,18 +15,32 @@ def check_file(): checkers = get_option('lint_checker').split(',') - ignore = set(filter(lambda i: i, get_option('lint_ignore').split(',') + - get_var('lint_ignore').split(','))) - - select = set(filter(lambda s: s, get_option('lint_select').split(',') + - get_var('lint_select').split(','))) + ignore = set([ + i for i in ( + get_option('lint_ignore').split(',') + + get_var('lint_ignore').split(',')) + if i + ]) + select = set([ + s for s in ( + get_option('lint_select').split(',') + + get_var('lint_select').split(',')) + if i + ]) buffer = get_current_buffer() + complexity = int(get_option('lint_mccabe_complexity') or 0) - add_task(run_checkers, checkers=checkers, ignore=ignore, title='Code checking', callback=parse_result, buffer=buffer, select=select) + add_task(run_checkers, checkers=checkers, ignore=ignore, + title='Code checking', + callback=parse_result, + buffer=buffer, + select=select, + complexity=complexity) -def run_checkers(task=None, checkers=None, ignore=None, buffer=None, select=None): +def run_checkers(task=None, checkers=None, ignore=None, + buffer=None, select=None, complexity=None): buffer = (task and task.buffer) or buffer filename = buffer.name @@ -34,7 +48,8 @@ def run_checkers(task=None, checkers=None, ignore=None, buffer=None, select=None pylint_options = '--rcfile={0} -r n'.format(get_var('lint_config')).split() - result = run(filename, ignore=ignore, select=select, linters=checkers, pylint=pylint_options) + result = run(filename, ignore=ignore, select=select, linters=checkers, + pylint=pylint_options, complexity=complexity) if task: task.result = result @@ -45,3 +60,5 @@ def run_checkers(task=None, checkers=None, ignore=None, buffer=None, select=None def parse_result(result): command(('let g:qf_list = %s' % repr(result)).replace('\': u', '\': ')) command('call pymode#lint#Parse()') + +# pymode:lint_ignore=W0622 From ad3b22eb5e0d161fae5e5b18dc01b6d148b318fe Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 22 Mar 2013 13:44:11 +0800 Subject: [PATCH 165/513] Update pylama to version 0.2.4 --- pylibs/pylama/__init__.py | 2 +- pylibs/pylama/main.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index fb034e2c..d7e01095 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -1,6 +1,6 @@ " pylama -- Python code audit. " -version_info = (0, 2, 3) +version_info = (0, 2, 4) __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index 9aea9478..ac34e790 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -13,8 +13,10 @@ default_complexity = 10 logger = logging.Logger('pylama') +SKIP_PATTERN = '# nolint' -def run(path, ignore=None, select=None, linters=default_linters, **meta): + +def run(path, ignore=None, select=None, linters=default_linters, **meta): # nolint errors = [] ignore = ignore and list(ignore) or [] select = select and list(select) or [] @@ -29,6 +31,9 @@ def run(path, ignore=None, select=None, linters=default_linters, **meta): try: code = open(path, "rU").read() + '\n\n' params = parse_modeline(code) + params['skip'] = [False] + for line in code.split('\n'): + params['skip'].append(line.endswith(SKIP_PATTERN)) if params.get('lint_ignore'): ignore += params.get('lint_ignore').split(',') @@ -45,7 +50,8 @@ def run(path, ignore=None, select=None, linters=default_linters, **meta): 'text') or '').strip() .replace("'", "\"").split('\n')[0], lint) e['filename'] = path or '' - errors.append(e) + if not params['skip'][e['lnum']]: + errors.append(e) except IOError, e: errors.append(dict( @@ -164,5 +170,3 @@ def parse_modeline(code): if __name__ == '__main__': shell() - -# lint=12:lint_ignore=test From 8b361752ed9ef9c8b029098162a81605aad4a0e3 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 22 Mar 2013 18:17:25 +0800 Subject: [PATCH 166/513] Fix default pylint configuration --- ftplugin/python/init-pymode.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index 35ca48f8..ef25f2c6 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -127,7 +127,7 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint " DESC: Set default pylint configuration if !filereadable(g:pymode_lint_config) - let g:pymode_lint_config = expand(":p:h:h") . "/pylint.ini" + let g:pymode_lint_config = expand(":p:h:h:h") . "/pylint.ini" endif py from pymode import queue From 3598b14ad4de9afe88a805b61dc3842e43535975 Mon Sep 17 00:00:00 2001 From: Jonathan McCall Date: Fri, 22 Mar 2013 10:23:47 -0400 Subject: [PATCH 167/513] Fix for disappearing refactoring dialog. --- pylibs/ropevim.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 762457f9..2912a170 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -380,8 +380,6 @@ def done(self): def echo(message): - if _rope_quiet: - return if isinstance(message, unicode): message = message.encode(vim.eval('&encoding')) print message @@ -445,7 +443,6 @@ def open_project(self, root=None, quiet=False): progress.name = txt progress.done() - echo('Project opened!') decorators.logger.message = echo decorators.logger.only_short = True From 90fb3415313bfb08cf990d6703371e67f6b472d6 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 26 Mar 2013 10:06:10 +0800 Subject: [PATCH 168/513] gitgutter compatibility --- autoload/pymode.vim | 7 ++++--- autoload/pymode/lint.vim | 4 ++-- ftplugin/python/init-pymode.vim | 11 ++++++----- pylibs/pylama/main.py | 2 +- pylibs/pymode/lint.py | 6 +++--- pylibs/pymode/queue.py | 2 +- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index 12e2baba..acbd81d3 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -50,18 +50,19 @@ fun! pymode#QuickfixOpen(onlyRecognized, holdCursor, maxHeight, minHeight, jumpE endfunction "}}} -fun! pymode#PlaceSigns() "{{{ +fun! pymode#PlaceSigns(bnum) "{{{ " DESC: Place error signs " if has('signs') - sign unplace * + " TODO: Create pymode sign namespace + execute "sign unplace buffer=".a:bnum if !pymode#Default("g:pymode_lint_signs_always_visible", 0) || g:pymode_lint_signs_always_visible call RopeShowSignsRulerIfNeeded() endif for item in filter(getqflist(), 'v:val.bufnr != ""') - execute printf('silent! sign place 1 line=%d name=%s buffer=%d', item.lnum, item.type, item.bufnr) + execute printf('sign place %d line=%d name=%s buffer=%d', item.lnum, item.lnum, "Pymode".item.type, item.bufnr) endfor endif diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index e9c891f7..68782e18 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -20,13 +20,13 @@ fun! pymode#lint#Check() "{{{ endfunction " }}} -fun! pymode#lint#Parse() +fun! pymode#lint#Parse(bnum) " DESC: Parse result of code checking. " call setqflist(g:qf_list, 'r') if g:pymode_lint_signs - call pymode#PlaceSigns() + call pymode#PlaceSigns(a:bnum) endif if g:pymode_lint_cwindow diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index ef25f2c6..381feac8 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -111,11 +111,12 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint if (!pymode#Default("g:pymode_lint_signs", 1) || g:pymode_lint_signs) && has('signs') " DESC: Signs definition - sign define W text=WW texthl=Todo - sign define C text=CC texthl=Comment - sign define R text=RR texthl=Visual - sign define E text=EE texthl=Error - sign define I text=II texthl=Info + sign define PymodeW text=WW texthl=Todo + sign define PymodeC text=CC texthl=Comment + sign define PymodeR text=RR texthl=Visual + sign define PymodeE text=EE texthl=Error + sign define PymodeI text=II texthl=Info + sign define PymodeF text=FF texthl=Info if !pymode#Default("g:pymode_lint_signs_always_visible", 0) || g:pymode_lint_signs_always_visible " Show the sign's ruller if asked for, even it there's no error to show diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index ac34e790..d7913334 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -45,7 +45,7 @@ def run(path, ignore=None, select=None, linters=default_linters, **meta): # nol for e in linter(path, code=code, **meta): e['col'] = e.get('col') or 0 e['lnum'] = e.get('lnum') or 0 - e['type'] = e.get('type') or 'E' + e['type'] = e.get('type') or 'W' e['text'] = "{0} [{1}]".format((e.get( 'text') or '').strip() .replace("'", "\"").split('\n')[0], lint) diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index 9f4683f1..b36c3abf 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -57,8 +57,8 @@ def run_checkers(task=None, checkers=None, ignore=None, task.done = 100 -def parse_result(result): - command(('let g:qf_list = %s' % repr(result)).replace('\': u', '\': ')) - command('call pymode#lint#Parse()') +def parse_result(result, bnum): + command(('let g:qf_list = {0}'.format(repr(result)).replace('\': u', '\': '))) + command('call pymode#lint#Parse({0})'.format(bnum)) # pymode:lint_ignore=W0622 diff --git a/pylibs/pymode/queue.py b/pylibs/pymode/queue.py index 220d8397..ed90be8f 100644 --- a/pylibs/pymode/queue.py +++ b/pylibs/pymode/queue.py @@ -55,6 +55,6 @@ def check_task(): if isinstance(thread, Task): if thread.finished: thread.stop() - thread.callback(thread.result) + thread.callback(thread.result, thread.buffer.number) else: show_message('%s %s%%' % (thread.title, thread.done)) From 6c43ae15062f5e330ee0b4f98870c00f0b8d2b9d Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 28 Mar 2013 11:56:33 +0800 Subject: [PATCH 169/513] Fix signs --- autoload/pymode.vim | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/autoload/pymode.vim b/autoload/pymode.vim index acbd81d3..c69e6798 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -54,14 +54,19 @@ fun! pymode#PlaceSigns(bnum) "{{{ " DESC: Place error signs " if has('signs') - " TODO: Create pymode sign namespace - execute "sign unplace buffer=".a:bnum + call pymode#Default('b:pymode_signs', []) + + for item in b:pymode_signs + execute printf('sign unplace %d buffer=%d', item.lnum, item.bufnr) + endfor + let b:pymode_signs = [] if !pymode#Default("g:pymode_lint_signs_always_visible", 0) || g:pymode_lint_signs_always_visible call RopeShowSignsRulerIfNeeded() endif for item in filter(getqflist(), 'v:val.bufnr != ""') + call add(b:pymode_signs, item) execute printf('sign place %d line=%d name=%s buffer=%d', item.lnum, item.lnum, "Pymode".item.type, item.bufnr) endfor From 2a5d5a4e67b23d940c41593e7882d257b84dea63 Mon Sep 17 00:00:00 2001 From: Benjamin Ruston Date: Sun, 31 Mar 2013 20:20:15 +0100 Subject: [PATCH 170/513] Minor grammar fixes. --- doc/pymode.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/doc/pymode.txt b/doc/pymode.txt index a2e4cb73..779495d0 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -29,7 +29,7 @@ Python-mode is a vim plugin that allows you to use the pylint, rope, and pydoc libraries in vim to provide features like python code bug checking, refactoring, and some other useful things. -This plugin allow you create python code in vim very easily. There is no need +This plugin allows you to create python code in vim very easily. There is no need to install the pylint or rope libraries on your system. @@ -122,9 +122,9 @@ To enable any of the options below you should put the given line in your 2.2. Modeline ~ *PythonModeModeline* -Feature like VIM modeline `:help modeline`. Allow changing pymode options for -edited file. Pymode modeline should always be the last line in the file and -look like: +The VIM modeline `:help modeline` feature allows you to change pymode +options for the current file. Pymode modeline should always be the +last line in the vimrc file and look like: > # pymode:lint_ignore=E0202:doc=0:lint_write=0 @@ -142,7 +142,7 @@ Set linters and mccabe complexity. # pymode:lint_checker=pip,mccabe:lint_mccabe_complexity=10 < -This changes will work only in current buffer. +These changes will work only in the current buffer. ------------------------------------------------------------------------------ *'pymode'* @@ -156,7 +156,7 @@ If this option is set to 0 then the whole plugin is disabled Values: List of strings Default: []. -This option set additional python import paths +This option sets additional python import paths ------------------------------------------------------------------------------ *'pymode_doc'* @@ -169,20 +169,20 @@ If this option is set to 0 then the doc script is disabled. *'pymode_doc_key'* Default: 'K'. -Set key for show python documentation. +Set the key to the show python documentation. ------------------------------------------------------------------------------ *'pymode_run'* Values: 0 or 1. Default: 1. -If this option is set to 0 then run script is disabled. +If this option is set to 0 then the run script is disabled. ------------------------------------------------------------------------------ *'pymode_run_key'* Default: 'r'. -Set key for run python code. +Set the key for running python code. ------------------------------------------------------------------------------ *'pymode_lint'* @@ -279,7 +279,7 @@ Values: 0 or 1. Default: 0. If this option is set to 0 then pylint will switch on the quickfix window when -it opens. Doesn't work when |'pymode_lint_jump'| enabled. +it opens. Doesn't work when |'pymode_lint_jump'| is enabled. ------------------------------------------------------------------------------ *'pymode_lint_minheight'* @@ -349,7 +349,7 @@ not be used. Values: 0 or 1. Default: 1. -If this option is set to 1, pymode will enable python indentation support +If this option is set to 1, pymode will enable python indentation support. ------------------------------------------------------------------------------ *'pymode_folding'* @@ -463,8 +463,8 @@ To work, rope_ creates a service directory: `.ropeproject`. If |'pymode_rope_guess_project'| is set on (as it is by default) and `.ropeproject` is not found in the current dir, rope will scan for `.ropeproject` in every dir in the parent path. If rope finds `.ropeproject` -in parent dirs, rope sets project for all child dir and the scan may be slow -for many dirs and files. +in parent dirs, rope sets projectis for all child dirs and the scan may be +slow for many dirs and files. Solutions: @@ -479,7 +479,7 @@ Solutions: Pylint check is very slow ------------------------- -In some projects pylint_ may check slowly, because it also scan imported +In some projects pylint_ may check slowly, because it also scans imported modules if possible. Try using pyflakes: see |'pymode_lint_checker'|. You may set |exrc| and |secure| in your |vimrc| to auto-set custom settings From 4208b36edc1cb7edf64795d5d6aa604525144615 Mon Sep 17 00:00:00 2001 From: Benjamin Ruston Date: Sun, 31 Mar 2013 21:22:02 +0200 Subject: [PATCH 171/513] Update pymode.txt --- doc/pymode.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/pymode.txt b/doc/pymode.txt index 779495d0..998925c1 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -169,7 +169,7 @@ If this option is set to 0 then the doc script is disabled. *'pymode_doc_key'* Default: 'K'. -Set the key to the show python documentation. +Set the key to show the show python documentation. ------------------------------------------------------------------------------ *'pymode_run'* From 2b790f30625882be56e0c8a80d37223fd6ce6ae2 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 1 Apr 2013 10:37:25 +0800 Subject: [PATCH 172/513] Update pylama --- pylibs/pylama/__init__.py | 2 +- pylibs/pylama/hook.py | 88 ++++++++++++++ pylibs/pylama/main.py | 105 ++++++++++------ pylibs/pylama/mccabe.py | 243 +++++++++++++++++++++++--------------- pylibs/pylama/pep8.py | 186 +++++++++++++++++++++++------ pylibs/pylama/pylint.rc | 25 ++++ pylibs/pylama/utils.py | 17 ++- 7 files changed, 492 insertions(+), 174 deletions(-) create mode 100644 pylibs/pylama/hook.py create mode 100644 pylibs/pylama/pylint.rc diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index d7e01095..28159700 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -1,6 +1,6 @@ " pylama -- Python code audit. " -version_info = (0, 2, 4) +version_info = (0, 3, 0) __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/hook.py b/pylibs/pylama/hook.py new file mode 100644 index 00000000..c6884d5c --- /dev/null +++ b/pylibs/pylama/hook.py @@ -0,0 +1,88 @@ +import sys +from os import path as op, chmod +from subprocess import Popen, PIPE +from .main import logger + + +try: + from configparser import ConfigParser # nolint +except ImportError: # Python 2 + from ConfigParser import ConfigParser + + +def run(command): + p = Popen(command.split(), stdout=PIPE, stderr=PIPE) + (stdout, stderr) = p.communicate() + return (p.returncode, [line.strip() for line in stdout.splitlines()], + [line.strip() for line in stderr.splitlines()]) + + +def git_hook(): + from .main import check_files + _, files_modified, _ = run("git diff-index --cached --name-only HEAD") + logger.setLevel('WARN') + check_files([f for f in map(str, files_modified) if f.endswith('.py')]) + + +def hg_hook(ui, repo, **kwargs): + from .main import check_files + seen = set() + paths = [] + for rev in range(repo[kwargs['node']], len(repo)): + for file_ in repo[rev].files(): + file_ = op.join(repo.root, file_) + if file_ in seen or not op.exists(file_): + continue + seen.add(file_) + if file_.endswith('.py'): + paths.append(file_) + logger.setLevel('WARN') + check_files(paths) + + +def install_git(path): + hook = op.join(path, 'pre-commit') + with open(hook, 'w+') as fd: + fd.write("""#!/usr/bin/env python +import sys +from pylama.hook import git_hook + +if __name__ == '__main__': + sys.exit(git_hook()) +""") + chmod(hook, 484) + return True + + +def install_hg(path): + hook = op.join(path, 'hgrc') + if not op.isfile(hook): + open(hook, 'w+').close() + + c = ConfigParser() + c.readfp(open(path, 'r')) + if not c.has_section('hooks'): + c.add_section('hooks') + + if not c.has_option('hooks', 'commit'): + c.set('hooks', 'commit', 'python:pylama.hooks.hg_hook') + + if not c.has_option('hooks', 'qrefresh'): + c.set('hooks', 'qrefresh', 'python:pylama.hooks.hg_hook') + + c.write(open(path, 'w+')) + return True + + +def install_hook(path): + git = op.join(path, '.git', 'hooks') + hg = op.join(path, '.hg') + if op.exists(git): + install_git(git) and logger.warn('Git hook has been installed.') # nolint + + elif op.exists(hg): + install_hg(git) and logger.warn('Mercurial hook has been installed.') # nolint + + else: + logger.error('VCS has not found. Check your path.') + sys.exit(1) diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index d7913334..aebad2d4 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -12,6 +12,8 @@ default_linters = 'pep8', 'pyflakes', 'mccabe' default_complexity = 10 logger = logging.Logger('pylama') +stream = logging.StreamHandler() +logger.addHandler(stream) SKIP_PATTERN = '# nolint' @@ -29,38 +31,40 @@ def run(path, ignore=None, select=None, linters=default_linters, **meta): # nol continue try: - code = open(path, "rU").read() + '\n\n' - params = parse_modeline(code) - params['skip'] = [False] - for line in code.split('\n'): - params['skip'].append(line.endswith(SKIP_PATTERN)) - - if params.get('lint_ignore'): - ignore += params.get('lint_ignore').split(',') - - if params.get('lint_select'): - select += params.get('lint_select').split(',') - - if params.get('lint'): - for e in linter(path, code=code, **meta): - e['col'] = e.get('col') or 0 - e['lnum'] = e.get('lnum') or 0 - e['type'] = e.get('type') or 'W' - e['text'] = "{0} [{1}]".format((e.get( - 'text') or '').strip() - .replace("'", "\"").split('\n')[0], lint) - e['filename'] = path or '' - if not params['skip'][e['lnum']]: - errors.append(e) - - except IOError, e: + with open(path, "rU") as f: + code = f.read() + '\n\n' + params = parse_modeline(code) + params['skip'] = [False] + for line in code.split('\n'): + params['skip'].append(line.endswith(SKIP_PATTERN)) + + if params.get('lint_ignore'): + ignore += params.get('lint_ignore').split(',') + + if params.get('lint_select'): + select += params.get('lint_select').split(',') + + if params.get('lint'): + result = linter(path, code=code, **meta) + for e in result: + e['col'] = e.get('col') or 0 + e['lnum'] = e.get('lnum') or 0 + e['type'] = e.get('type') or 'E' + e['text'] = "{0} [{1}]".format((e.get( + 'text') or '').strip() + .replace("'", "\"").split('\n')[0], lint) + e['filename'] = path or '' + if not params['skip'][e['lnum']]: + errors.append(e) + + except IOError as e: errors.append(dict( lnum=0, type='E', col=0, text=str(e) )) - except SyntaxError, e: + except SyntaxError as e: errors.append(dict( lnum=e.lineno or 0, type='E', @@ -69,7 +73,7 @@ def run(path, ignore=None, select=None, linters=default_linters, **meta): # nol )) break - except Exception, e: + except Exception as e: import traceback logging.error(traceback.format_exc()) @@ -96,6 +100,9 @@ def shell(): split_csp_list = lambda s: list(set(i for i in s.split(',') if i)) + parser.add_argument( + "--format", "-f", default='pep8', choices=['pep8', 'pylint'], + help="Error format.") parser.add_argument( "--select", "-s", default='', type=split_csp_list, @@ -116,14 +123,19 @@ def shell(): parser.add_argument("--complexity", "-c", default=default_complexity, type=int, help="Set mccabe complexity.") parser.add_argument("--report", "-r", help="Filename for report.") + parser.add_argument("--hook", action="store_true", + help="Install Git (Mercurial) hook.") args = parser.parse_args() # Setup logger logger.setLevel(logging.INFO if args.verbose else logging.WARN) if args.report: + logger.removeHandler(stream) logger.addHandler(logging.FileHandler(args.report, mode='w')) - else: - logger.addHandler(logging.StreamHandler()) + + if args.hook: + from .hook import install_hook + return install_hook(args.path) paths = [args.path] @@ -132,16 +144,37 @@ def shell(): for root, _, files in walk(args.path): paths += [op.join(root, f) for f in files if f.endswith('.py')] - for path in skip_paths(args, paths): + check_files( + paths, + rootpath=args.path, + skip=args.skip, + frmt=args.format, + ignore=args.ignore, + select=args.select, + linters=args.linters, + complexity=args.complexity, + ) + + +def check_files(paths, rootpath=None, skip=None, frmt="pep8", + select=None, ignore=None, linters=default_linters, + complexity=default_complexity): + rootpath = rootpath or getcwd() + pattern = "%(rel)s:%(lnum)s:%(col)s: %(text)s" + if frmt == 'pylint': + pattern = "%(rel)s:%(lnum)s: [%(type)s] %(text)s" + + errors = [] + for path in skip_paths(skip, paths): logger.info("Parse file: %s" % path) - errors = run(path, ignore=args.ignore, select=args.select, - linters=args.linters, complexity=args.complexity) + errors = run(path, ignore=ignore, select=select, + linters=linters, complexity=complexity) for error in errors: try: error['rel'] = op.relpath( - error['filename'], op.dirname(args.path)) + error['filename'], op.dirname(rootpath)) error['col'] = error.get('col', 1) - logger.warning("%(rel)s:%(lnum)s:%(col)s: %(text)s", error) + logger.warning(pattern, error) except KeyError: continue @@ -152,9 +185,9 @@ def shell(): r'^\s*#\s+(?:pymode\:)?((?:lint[\w_]*=[^:\n\s]+:?)+)', re.I | re.M) -def skip_paths(args, paths): +def skip_paths(skip, paths): for path in paths: - if args.skip and any(pattern.match(path) for pattern in args.skip): + if skip and any(pattern.match(path) for pattern in skip): continue yield path diff --git a/pylibs/pylama/mccabe.py b/pylibs/pylama/mccabe.py index f8f1de05..b52671bc 100644 --- a/pylibs/pylama/mccabe.py +++ b/pylibs/pylama/mccabe.py @@ -3,34 +3,28 @@ http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html MIT License. """ -try: - from compiler import parse # NOQA - iter_child_nodes = None # NOQA -except ImportError: - from ast import parse, iter_child_nodes # NOQA +from __future__ import with_statement import sys + +import ast +import optparse +from ast import iter_child_nodes from collections import defaultdict -WARNING_CODE = "W901" +__version__ = '0.2' -class ASTVisitor: - VERBOSE = 0 +class ASTVisitor(object): + """Performs a depth-first walk of the AST.""" def __init__(self): self.node = None self._cache = {} - self.visitor = None def default(self, node, *args): - if hasattr(node, 'getChildNodes'): - children = node.getChildNodes() - else: - children = iter_child_nodes(node) - - for child in children: + for child in iter_child_nodes(node): self.dispatch(child, *args) def dispatch(self, node, *args): @@ -41,30 +35,29 @@ def dispatch(self, node, *args): className = klass.__name__ meth = getattr(self.visitor, 'visit' + className, self.default) self._cache[klass] = meth - return meth(node, *args) def preorder(self, tree, visitor, *args): """Do preorder walk of tree using visitor""" self.visitor = visitor visitor.visit = self.dispatch - self.dispatch(tree, *args) + self.dispatch(tree, *args) # XXX *args make sense? -class PathNode: +class PathNode(object): def __init__(self, name, look="circle"): self.name = name self.look = look def to_dot(self): - print('node [shape=%s,label="%s"] %d;' % - (self.look, self.name, self.dot_id())) + print('node [shape=%s,label="%s"] %d;' % ( + self.look, self.name, self.dot_id())) def dot_id(self): return id(self) -class PathGraph: +class PathGraph(object): def __init__(self, name, entity, lineno): self.name = name self.entity = entity @@ -79,8 +72,8 @@ def to_dot(self): for node in self.nodes: node.to_dot() for node, nexts in self.nodes.items(): - for nxt in nexts: - print('%s -- %s;' % (node.dot_id(), nxt.dot_id())) + for next in nexts: + print('%s -- %s;' % (node.dot_id(), next.dot_id())) print('}') def complexity(self): @@ -98,7 +91,7 @@ class PathGraphingAstVisitor(ASTVisitor): """ def __init__(self): - ASTVisitor.__init__(self) + super(PathGraphingAstVisitor, self).__init__() self.classname = "" self.graphs = {} self.reset() @@ -107,7 +100,11 @@ def reset(self): self.graph = None self.tail = None - def visitFunction(self, node): + def dispatch_list(self, node_list): + for node in node_list: + self.dispatch(node) + + def visitFunctionDef(self, node): if self.classname: entity = '%s%s' % (self.classname, node.name) @@ -120,7 +117,7 @@ def visitFunction(self, node): # closure pathnode = self.appendPathNode(name) self.tail = pathnode - self.default(node) + self.dispatch_list(node.body) bottom = PathNode("", look='point') self.graph.connect(self.tail, bottom) self.graph.connect(pathnode, bottom) @@ -129,16 +126,14 @@ def visitFunction(self, node): self.graph = PathGraph(name, entity, node.lineno) pathnode = PathNode(name) self.tail = pathnode - self.default(node) + self.dispatch_list(node.body) self.graphs["%s%s" % (self.classname, node.name)] = self.graph self.reset() - visitFunctionDef = visitFunction - - def visitClass(self, node): + def visitClassDef(self, node): old_classname = self.classname self.classname += node.name + "." - self.default(node) + self.dispatch_list(node.body) self.classname = old_classname def appendPathNode(self, name): @@ -157,9 +152,9 @@ def visitSimpleStatement(self, node): name = "Stmt %d" % lineno self.appendPathNode(name) - visitAssert = visitAssign = visitAssTuple = visitPrint = \ - visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ - visitPass = visitDiscard = visitGlobal = visitReturn = \ + visitAssert = visitAssign = visitAugAssign = visitDelete = visitPrint = \ + visitRaise = visitYield = visitImport = visitCall = visitSubscript = \ + visitPass = visitContinue = visitBreak = visitGlobal = visitReturn = \ visitSimpleStatement def visitLoop(self, node): @@ -170,96 +165,148 @@ def visitLoop(self, node): self.graph = PathGraph(name, name, node.lineno) pathnode = PathNode(name) self.tail = pathnode - self.default(node) + self.dispatch_list(node.body) self.graphs["%s%s" % (self.classname, name)] = self.graph self.reset() else: pathnode = self.appendPathNode(name) self.tail = pathnode - self.default(node.body) + self.dispatch_list(node.body) bottom = PathNode("", look='point') self.graph.connect(self.tail, bottom) self.graph.connect(pathnode, bottom) self.tail = bottom - # TODO: else clause in node.else_ + # TODO: else clause in node.orelse visitFor = visitWhile = visitLoop def visitIf(self, node): name = "If %d" % node.lineno pathnode = self.appendPathNode(name) - if not pathnode: - return # TODO: figure out what to do with if's outside def's. loose_ends = [] - for t, n in node.tests: - self.tail = pathnode - self.default(n) - loose_ends.append(self.tail) - if node.else_: + self.dispatch_list(node.body) + loose_ends.append(self.tail) + if node.orelse: self.tail = pathnode - self.default(node.else_) + self.dispatch_list(node.orelse) loose_ends.append(self.tail) else: loose_ends.append(pathnode) - bottom = PathNode("", look='point') - for le in loose_ends: - self.graph.connect(le, bottom) - self.tail = bottom - - # TODO: visitTryExcept - # TODO: visitTryFinally - # TODO: visitWith - - # XXX todo: determine which ones can add to the complexity - # py2 - # TODO: visitStmt - # TODO: visitAssName - # TODO: visitCallFunc - # TODO: visitConst - - # py3 - # TODO: visitStore - # TODO: visitCall - # TODO: visitLoad - # TODO: visitNum - # TODO: visitarguments - # TODO: visitExpr - - -def get_code_complexity(code, min=7, filename='stdin'): - complex = [] + if pathnode: + bottom = PathNode("", look='point') + for le in loose_ends: + self.graph.connect(le, bottom) + self.tail = bottom + + def visitTryExcept(self, node): + name = "TryExcept %d" % node.lineno + pathnode = self.appendPathNode(name) + loose_ends = [] + self.dispatch_list(node.body) + loose_ends.append(self.tail) + for handler in node.handlers: + self.tail = pathnode + self.dispatch_list(handler.body) + loose_ends.append(self.tail) + if pathnode: + bottom = PathNode("", look='point') + for le in loose_ends: + self.graph.connect(le, bottom) + self.tail = bottom + + def visitWith(self, node): + name = "With %d" % node.lineno + self.appendPathNode(name) + self.dispatch_list(node.body) + + +class McCabeChecker(object): + """McCabe cyclomatic complexity checker.""" + name = 'mccabe' + version = __version__ + _code = 'C901' + _error_tmpl = "C901 %r is too complex (%d)" + max_complexity = 0 + + def __init__(self, tree, filename): + self.tree = tree + + @classmethod + def add_options(cls, parser): + parser.add_option('--max-complexity', default=-1, action='store', + type='int', help="McCabe complexity threshold") + parser.config_options.append('max-complexity') + + @classmethod + def parse_options(cls, options): + cls.max_complexity = options.max_complexity + + def run(self): + if self.max_complexity < 0: + return + visitor = PathGraphingAstVisitor() + visitor.preorder(self.tree, visitor) + for graph in visitor.graphs.values(): + if graph.complexity() >= self.max_complexity: + text = self._error_tmpl % (graph.entity, graph.complexity()) + yield graph.lineno, 0, text, type(self) + + +def get_code_complexity(code, threshold=7, filename='stdin'): try: - ast = parse(code) - except AttributeError: + tree = compile(code, filename, "exec", ast.PyCF_ONLY_AST) + except SyntaxError: e = sys.exc_info()[1] sys.stderr.write("Unable to parse %s: %s\n" % (filename, e)) return 0 - visitor = PathGraphingAstVisitor() - visitor.preorder(ast, visitor) - for graph in visitor.graphs.values(): - if graph is None: - # ? - continue - if graph.complexity() >= min: - complex.append(dict( - type='W', - lnum=graph.lineno, - text='%s %r is too complex (%d)' % ( - WARNING_CODE, - graph.entity, - graph.complexity(), - ) - )) - - return complex - - -def get_module_complexity(module_path, min=7): + complx = [] + McCabeChecker.max_complexity = threshold + for lineno, offset, text, _ in McCabeChecker(tree, filename).run(): + complx.append(dict( + type=McCabeChecker._code, + lnum=lineno, + text=text, + )) + + return complx + + +def get_module_complexity(module_path, threshold=7): """Returns the complexity of a module""" - code = open(module_path, "rU").read() + '\n\n' - return get_code_complexity(code, min, filename=module_path) + with open(module_path, "rU") as mod: + code = mod.read() + return get_code_complexity(code, threshold, filename=module_path) + + +def main(argv): + opar = optparse.OptionParser() + opar.add_option("-d", "--dot", dest="dot", + help="output a graphviz dot file", action="store_true") + opar.add_option("-m", "--min", dest="threshold", + help="minimum complexity for output", type="int", + default=2) + + options, args = opar.parse_args(argv) + + with open(args[0], "rU") as mod: + code = mod.read() + tree = compile(code, args[0], "exec", ast.PyCF_ONLY_AST) + visitor = PathGraphingAstVisitor() + visitor.preorder(tree, visitor) + + if options.dot: + print('graph {') + for graph in visitor.graphs.values(): + if graph.complexity() >= options.threshold: + graph.to_dot() + print('}') + else: + for graph in visitor.graphs.values(): + if graph.complexity() >= options.threshold: + print(graph.name, graph.complexity()) -# pymode:lint=0 +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/pylibs/pylama/pep8.py b/pylibs/pylama/pep8.py index bbff9107..888df59d 100644 --- a/pylibs/pylama/pep8.py +++ b/pylibs/pylama/pep8.py @@ -45,7 +45,7 @@ 700 statements 900 syntax error """ -__version__ = '1.4.5' +__version__ = '1.4.6a0' import os import sys @@ -54,6 +54,7 @@ import inspect import keyword import tokenize +from optparse import OptionParser from fnmatch import fnmatch try: from configparser import RawConfigParser @@ -68,7 +69,7 @@ else: DEFAULT_CONFIG = os.path.join(os.getenv('XDG_CONFIG_HOME') or os.path.expanduser('~/.config'), 'pep8') -PROJECT_CONFIG = ('.pep8', 'tox.ini', 'setup.cfg') +PROJECT_CONFIG = ('setup.cfg', 'tox.ini', '.pep8') TESTSUITE_PATH = os.path.join(os.path.dirname(__file__), 'testsuite') MAX_LINE_LENGTH = 79 REPORT_FORMAT = { @@ -214,9 +215,7 @@ def maximum_line_length(physical_line, max_line_length): """ line = physical_line.rstrip() length = len(line) - if length > max_line_length: - if noqa(line): - return + if length > max_line_length and not noqa(line): if hasattr(line, 'decode'): # Python 2 # The line could contain multi-byte characters try: @@ -382,7 +381,7 @@ def indentation(logical_line, previous_logical, indent_char, yield 0, "E113 unexpected indentation" -def continuation_line_indentation(logical_line, tokens, indent_level, verbose): +def continued_indentation(logical_line, tokens, indent_level, noqa, verbose): r""" Continuation lines should align wrapped elements either vertically using Python's implicit line joining inside parentheses, brackets and braces, or @@ -410,7 +409,7 @@ def continuation_line_indentation(logical_line, tokens, indent_level, verbose): """ first_row = tokens[0][2][0] nrows = 1 + tokens[-1][2][0] - first_row - if nrows == 1 or noqa(tokens[0][4]): + if noqa or nrows == 1: return # indent_next tells us whether the next block is indented; assuming @@ -564,11 +563,9 @@ def whitespace_before_parameters(logical_line, tokens): E211: dict ['key'] = list[index] E211: dict['key'] = list [index] """ - prev_type = tokens[0][0] - prev_text = tokens[0][1] - prev_end = tokens[0][3] + prev_type, prev_text, __, prev_end, __ = tokens[0] for index in range(1, len(tokens)): - token_type, text, start, end, line = tokens[index] + token_type, text, start, end, __ = tokens[index] if (token_type == tokenize.OP and text in '([' and start != prev_end and @@ -893,7 +890,7 @@ def explicit_line_join(logical_line, tokens): parens -= 1 -def comparison_to_singleton(logical_line): +def comparison_to_singleton(logical_line, noqa): """ Comparisons to singletons like None should always be done with "is" or "is not", never the equality operators. @@ -907,7 +904,7 @@ def comparison_to_singleton(logical_line): set to some other value. The other value might have a type (such as a container) that could be false in a boolean context! """ - match = COMPARE_SINGLETON_REGEX.search(logical_line) + match = not noqa and COMPARE_SINGLETON_REGEX.search(logical_line) if match: same = (match.group(1) == '==') singleton = match.group(2) @@ -1261,10 +1258,14 @@ def build_tokens_line(self): """ self.mapping = [] logical = [] + comments = [] length = 0 previous = None for token in self.tokens: token_type, text = token[0:2] + if token_type == tokenize.COMMENT: + comments.append(text) + continue if token_type in SKIP_TOKENS: continue if token_type == tokenize.STRING: @@ -1287,6 +1288,7 @@ def build_tokens_line(self): length += len(text) previous = token self.logical_line = ''.join(logical) + self.noqa = comments and noqa(''.join(comments)) # With Python 2, if the line ends with '\r\r\n' the assertion fails # assert self.logical_line.strip() == self.logical_line @@ -1553,19 +1555,8 @@ def __init__(self, *args, **kwargs): parse_argv = kwargs.pop('parse_argv', False) config_file = kwargs.pop('config_file', None) parser = kwargs.pop('parser', None) - - class options: - exclude = [] - select = [] - ignore = [] - testsuite = None - doctest = None - verbose = False - max_line_length = 79 - - self.paths = [] - # options, self.paths = process_options( - # parse_argv=parse_argv, config_file=config_file, parser=parser) + options, self.paths = process_options( + parse_argv=parse_argv, config_file=config_file, parser=parser) if args or kwargs: # build options from dict options_dict = dict(*args, **kwargs) @@ -1683,6 +1674,62 @@ def get_checks(self, argument_name): return sorted(checks) +def get_parser(prog='pep8', version=__version__): + parser = OptionParser(prog=prog, version=version, + usage="%prog [options] input ...") + parser.config_options = [ + 'exclude', 'filename', 'select', 'ignore', 'max-line-length', 'count', + 'format', 'quiet', 'show-pep8', 'show-source', 'statistics', 'verbose'] + parser.add_option('-v', '--verbose', default=0, action='count', + help="print status messages, or debug with -vv") + parser.add_option('-q', '--quiet', default=0, action='count', + help="report only file names, or nothing with -qq") + parser.add_option('-r', '--repeat', default=True, action='store_true', + help="(obsolete) show all occurrences of the same error") + parser.add_option('--first', action='store_false', dest='repeat', + help="show first occurrence of each error") + parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE, + help="exclude files or directories which match these " + "comma separated patterns (default: %default)") + parser.add_option('--filename', metavar='patterns', default='*.py', + help="when parsing directories, only check filenames " + "matching these comma separated patterns " + "(default: %default)") + parser.add_option('--select', metavar='errors', default='', + help="select errors and warnings (e.g. E,W6)") + parser.add_option('--ignore', metavar='errors', default='', + help="skip errors and warnings (e.g. E4,W)") + parser.add_option('--show-source', action='store_true', + help="show source code for each error") + parser.add_option('--show-pep8', action='store_true', + help="show text of PEP 8 for each error " + "(implies --first)") + parser.add_option('--statistics', action='store_true', + help="count errors and warnings") + parser.add_option('--count', action='store_true', + help="print total number of errors and warnings " + "to standard error and set exit code to 1 if " + "total is not null") + parser.add_option('--max-line-length', type='int', metavar='n', + default=MAX_LINE_LENGTH, + help="set maximum allowed line length " + "(default: %default)") + parser.add_option('--format', metavar='format', default='default', + help="set the error format [default|pylint|]") + parser.add_option('--diff', action='store_true', + help="report only lines changed according to the " + "unified diff received on STDIN") + group = parser.add_option_group("Testing Options") + if os.path.exists(TESTSUITE_PATH): + group.add_option('--testsuite', metavar='dir', + help="run regression tests from dir") + group.add_option('--doctest', action='store_true', + help="run doctest on myself") + group.add_option('--benchmark', action='store_true', + help="measure processing speed") + return parser + + def read_config(options, args, arglist, parser): """Read both user configuration and local configuration.""" config = RawConfigParser() @@ -1695,17 +1742,11 @@ def read_config(options, args, arglist, parser): parent = tail = args and os.path.abspath(os.path.commonprefix(args)) while tail: - for name in PROJECT_CONFIG: - local_conf = os.path.join(parent, name) - if os.path.isfile(local_conf): - break - else: - parent, tail = os.path.split(parent) - continue - if options.verbose: - print('local configuration: %s' % local_conf) - config.read(local_conf) - break + if config.read([os.path.join(parent, fn) for fn in PROJECT_CONFIG]): + if options.verbose: + print('local configuration: in %s' % parent) + break + parent, tail = os.path.split(parent) pep8_section = parser.prog if config.has_section(pep8_section): @@ -1738,3 +1779,74 @@ def read_config(options, args, arglist, parser): options, _ = parser.parse_args(arglist, values=new_options) options.doctest = options.testsuite = False return options + + +def process_options(arglist=None, parse_argv=False, config_file=None, + parser=None): + """Process options passed either via arglist or via command line args.""" + if not arglist and not parse_argv: + # Don't read the command line if the module is used as a library. + arglist = [] + if not parser: + parser = get_parser() + if not parser.has_option('--config'): + if config_file is True: + config_file = DEFAULT_CONFIG + group = parser.add_option_group("Configuration", description=( + "The project options are read from the [%s] section of the " + "tox.ini file or the setup.cfg file located in any parent folder " + "of the path(s) being processed. Allowed options are: %s." % + (parser.prog, ', '.join(parser.config_options)))) + group.add_option('--config', metavar='path', default=config_file, + help="user config file location (default: %default)") + options, args = parser.parse_args(arglist) + options.reporter = None + + if options.ensure_value('testsuite', False): + args.append(options.testsuite) + elif not options.ensure_value('doctest', False): + if parse_argv and not args: + if options.diff or any(os.path.exists(name) + for name in PROJECT_CONFIG): + args = ['.'] + else: + parser.error('input not specified') + options = read_config(options, args, arglist, parser) + options.reporter = parse_argv and options.quiet == 1 and FileReport + + options.filename = options.filename and options.filename.split(',') + options.exclude = options.exclude.split(',') + options.select = options.select and options.select.split(',') + options.ignore = options.ignore and options.ignore.split(',') + + if options.diff: + options.reporter = DiffReport + stdin = stdin_get_value() + options.selected_lines = parse_udiff(stdin, options.filename, args[0]) + args = sorted(options.selected_lines) + + return options, args + + +def _main(): + """Parse options and run checks on Python source.""" + pep8style = StyleGuide(parse_argv=True, config_file=True) + options = pep8style.options + if options.doctest or options.testsuite: + from testsuite.support import run_tests + report = run_tests(pep8style) + else: + report = pep8style.check_files() + if options.statistics: + report.print_statistics() + if options.benchmark: + report.print_benchmark() + if options.testsuite and not options.quiet: + report.print_results() + if report.total_errors: + if options.count: + sys.stderr.write(str(report.total_errors) + '\n') + sys.exit(1) + +if __name__ == '__main__': + _main() diff --git a/pylibs/pylama/pylint.rc b/pylibs/pylama/pylint.rc new file mode 100644 index 00000000..c58c4d0e --- /dev/null +++ b/pylibs/pylama/pylint.rc @@ -0,0 +1,25 @@ +[MESSAGES CONTROL] +# Disable the message(s) with the given id(s). +# http://pylint-messages.wikidot.com/all-codes +# +# C0103: Invalid name "%s" (should match %s) +# C0111: Missing docstring +# E1101: %s %r has no %r member +# R0901: Too many ancestors (%s/%s) +# R0902: Too many instance attributes (%s/%s) +# R0903: Too few public methods (%s/%s) +# R0904: Too many public methods (%s/%s) +# R0913: Too many arguments (%s/%s) +# R0915: Too many statements (%s/%s) +# W0141: Used builtin function %r +# W0142: Used * or ** magic +# W0221: Arguments number differs from %s method +# W0232: Class has no __init__ method +# W0401: Wildcard import %s +# W0613: Unused argument %r +# W0631: Using possibly undefined loop variable %r +# +disable = C0103,C0111,E1101,R0901,R0902,R0903,R0904,R0913,R0915,W0141,W0142,W0221,W0232,W0401,W0613,W0631 + +[TYPECHECK] +generated-members = REQUEST,acl_users,aq_parent,objects,DoesNotExist,_meta,status_code,content,context diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py index a5c3ba47..1ab481fb 100644 --- a/pylibs/pylama/utils.py +++ b/pylibs/pylama/utils.py @@ -1,4 +1,5 @@ import _ast +from os import path as op, environ from .mccabe import get_code_complexity from .pep8 import BaseReport, StyleGuide @@ -7,6 +8,8 @@ __all__ = 'pep8', 'mccabe', 'pyflakes', 'pylint' +PYLINT_RC = op.abspath(op.join(op.dirname(__file__), 'pylint.rc')) + class PEP8Report(BaseReport): @@ -54,7 +57,7 @@ def pyflakes(path, code=None, **meta): errors = [] tree = compile(code, path, "exec", _ast.PyCF_ONLY_AST) w = checker.Checker(tree, path) - w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno)) + w.messages = sorted(w.messages, key=lambda m: m.lineno) for w in w.messages: errors.append(dict( lnum=w.lineno, @@ -64,6 +67,12 @@ def pyflakes(path, code=None, **meta): def pylint(path, **meta): + from sys import version_info + if version_info > (2, 8): + import logging + logging.warn("Pylint don't supported python3 and will be disabled.") + return [] + from .pylint.lint import Run from .pylint.reporters import BaseReporter @@ -88,7 +97,11 @@ def add_message(self, msg_id, location, msg): type=msg_id[0] )) - attrs = meta.get('pylint', []) + pylintrc = op.join(environ.get('HOME', ''), '.pylintrc') + defattrs = '-r n' + if not op.exists(pylintrc): + defattrs += ' --rcfile={0}'.format(PYLINT_RC) + attrs = meta.get('pylint', defattrs.split()) runner = Run( [path] + attrs, reporter=Reporter(), exit=False) From 8ca1ee9fb77e914e97f7684ff043c42e3e79e51b Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 1 Apr 2013 10:38:50 +0800 Subject: [PATCH 173/513] Update changelog. --- Changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog.rst b/Changelog.rst index a3061325..3fd43e23 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,10 @@ Changelog ========= +## 2013-04-01 0.6.15 +-------------------- +* Bugfix release + ## 2013-03-16 0.6.14 -------------------- * Update `PEP8` to version 1.4.5; From 0f6f357c8071bb199879899426764ae1056e8de4 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 1 Apr 2013 10:40:31 +0800 Subject: [PATCH 174/513] Update version. --- README.rst | 2 +- doc/pymode.txt | 6 +++--- ftplugin/python/init-pymode.vim | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 96b5e61c..9f9ab6e7 100644 --- a/README.rst +++ b/README.rst @@ -96,7 +96,7 @@ Then rebuild **helptags** in vim:: Troubleshooting =============== -If your python-mode dont work, type command: :: +If your python-mode dont work, open any python file and type command: :: :call pymode#troubleshooting#Test() diff --git a/doc/pymode.txt b/doc/pymode.txt index 998925c1..c4b19c3f 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.14 + Version: 0.6.15 ============================================================================== CONTENTS *Python-mode-contents* @@ -452,8 +452,8 @@ iM Operation with inner function or method. Python-mode doesn't work ------------------------ -Run ":call pymode#troubleshooting#Test()" and fix the warning or send me the -output. +Open any python file and run ":call pymode#troubleshooting#Test()", +fix the warning or send me the output. Rope completion is very slow diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index 381feac8..1bf07e4c 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -3,7 +3,7 @@ if exists('did_init_pymode_vim') endif let did_init_pymode_vim = 1 -let g:pymode_version = "0.6.14" +let g:pymode_version = "0.6.15" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From a04d82c53cb858a9cd2a2180be7d28aac8766ed6 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Mon, 8 Apr 2013 14:47:15 +0200 Subject: [PATCH 175/513] Set commentstring in pymode_options. This setting is used in vanilla vim for adding fold markers, but also by vim-commentary for using the appropriate comment marker for specific filetypes. --- ftplugin/python/pymode.vim | 1 + 1 file changed, 1 insertion(+) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index ffcf0803..2ca6f33e 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -26,6 +26,7 @@ if pymode#Option('options') endif setlocal nowrap setlocal textwidth=79 + setlocal commentstring=#%s endif " }}} From 5c308c24069fa7d27123aafbb037a94d0b3b7db6 Mon Sep 17 00:00:00 2001 From: Denis Kasak Date: Mon, 8 Apr 2013 14:51:22 +0200 Subject: [PATCH 176/513] Update documentation regarding commentstring. --- doc/pymode.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/pymode.txt b/doc/pymode.txt index c4b19c3f..f37796f5 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -371,6 +371,7 @@ buffers: > setlocal number setlocal nowrap setlocal textwidth=80 + setlocal commentstring=#%s < ------------------------------------------------------------------------------ *'pymode_motion'* From dd880f273c7f6d0ea9bd0e1a4c88521abbfb3b1f Mon Sep 17 00:00:00 2001 From: ikame Date: Tue, 9 Apr 2013 12:03:35 +0200 Subject: [PATCH 177/513] keyword match pythonClass --- syntax/python.vim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/syntax/python.vim b/syntax/python.vim index ba2b9f92..7a8d06d3 100644 --- a/syntax/python.vim +++ b/syntax/python.vim @@ -24,8 +24,10 @@ call pymode#Default('g:pymode_syntax_all', 1) syn keyword pythonStatement global assert syn keyword pythonStatement lambda yield syn keyword pythonStatement with as - syn keyword pythonStatement def class nextgroup=pythonFunction skipwhite + syn keyword pythonStatement def nextgroup=pythonFunction skipwhite syn match pythonFunction "[a-zA-Z_][a-zA-Z0-9_]*" display contained + syn keyword pythonStatement class nextgroup=pythonClass skipwhite + syn match pythonClass "[a-zA-Z_][a-zA-Z0-9_]*" display contained syn keyword pythonRepeat for while syn keyword pythonConditional if elif else syn keyword pythonInclude import from From 0dd203fa89d05d35d6b84399662228649a938bce Mon Sep 17 00:00:00 2001 From: lee Date: Sat, 13 Apr 2013 20:04:52 +0800 Subject: [PATCH 178/513] fix Chinese runtime error fix Chinese runtime error --- autoload/pymode/run.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index f4bcab8b..df971178 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -31,7 +31,7 @@ fun! pymode#run#Run(line1, line2) "{{{ python << EOF if out: vim.command("call pymode#TempBuffer()") - vim.current.buffer.append([x.encode(enc) for x in out.split('\n')], 0) + vim.current.buffer.append([x.decode("utf-8").encode(enc) for x in out.split('\n')], 0) vim.command("wincmd p") else: vim.command('call pymode#WideMessage("No output.")') From 54ef9d57b56b87eafd66b368e4fd27485f1eeacf Mon Sep 17 00:00:00 2001 From: alvinfrancis Date: Sat, 20 Apr 2013 18:15:38 +0800 Subject: [PATCH 179/513] Include decorators when folding --- autoload/pymode/folding.vim | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/autoload/pymode/folding.vim b/autoload/pymode/folding.vim index 8ac4b625..682081b8 100644 --- a/autoload/pymode/folding.vim +++ b/autoload/pymode/folding.vim @@ -3,6 +3,7 @@ let s:blank_regex = '^\s*$' let s:def_regex = '^\s*\(class\|def\) \w\+' +let s:decorator_regex = '^\s*@' fun! pymode#folding#text() " {{{ @@ -31,12 +32,13 @@ fun! pymode#folding#expr(lnum) "{{{ let line = getline(a:lnum) let indent = indent(a:lnum) - if line =~ s:def_regex - return ">".(indent / &shiftwidth + 1) - endif - - if line =~ '^\s*@' - return -1 + if line =~ s:def_regex || line =~ s:decorator_regex + let prev_line = getline(a:lnum - 1) + if prev_line =~ s:decorator_regex + return '=' + else + return ">".(indent / &shiftwidth + 1) + endif endif if line =~ s:blank_regex From 195d1ce7e6ee0cf42067889c1d565dee935a341a Mon Sep 17 00:00:00 2001 From: alvinfrancis Date: Sat, 20 Apr 2013 20:53:21 +0800 Subject: [PATCH 180/513] Refactor folding.vim --- autoload/pymode/folding.vim | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/autoload/pymode/folding.vim b/autoload/pymode/folding.vim index 682081b8..c6cbda19 100644 --- a/autoload/pymode/folding.vim +++ b/autoload/pymode/folding.vim @@ -2,7 +2,7 @@ let s:blank_regex = '^\s*$' -let s:def_regex = '^\s*\(class\|def\) \w\+' +let s:def_regex = '^\s*\%(class\|def\) \w\+' let s:decorator_regex = '^\s*@' @@ -31,9 +31,9 @@ fun! pymode#folding#expr(lnum) "{{{ let line = getline(a:lnum) let indent = indent(a:lnum) + let prev_line = getline(a:lnum - 1) if line =~ s:def_regex || line =~ s:decorator_regex - let prev_line = getline(a:lnum - 1) if prev_line =~ s:decorator_regex return '=' else @@ -42,11 +42,10 @@ fun! pymode#folding#expr(lnum) "{{{ endif if line =~ s:blank_regex - let prev_line = getline(a:lnum - 1) if prev_line =~ s:blank_regex return -1 else - return foldlevel(prevnonblank(a:lnum)) + return '=' endif endif From a3b3d91ff1c315c8f1832f85803318ca96bd735d Mon Sep 17 00:00:00 2001 From: alvinfrancis Date: Sat, 20 Apr 2013 20:56:45 +0800 Subject: [PATCH 181/513] Add folding for docstrings --- autoload/pymode/folding.vim | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/autoload/pymode/folding.vim b/autoload/pymode/folding.vim index c6cbda19..84992394 100644 --- a/autoload/pymode/folding.vim +++ b/autoload/pymode/folding.vim @@ -4,6 +4,9 @@ let s:blank_regex = '^\s*$' let s:def_regex = '^\s*\%(class\|def\) \w\+' let s:decorator_regex = '^\s*@' +let s:doc_begin_regex = '^\s*\%("""\|''''''\)' +let s:doc_end_regex = '\%("""\|''''''\)\s*$' +let s:doc_line_regex = '^\s*\("""\|''''''\).\+\1\s*$' fun! pymode#folding#text() " {{{ @@ -41,6 +44,17 @@ fun! pymode#folding#expr(lnum) "{{{ endif endif + if line =~ s:doc_begin_regex + \ && line !~ s:doc_line_regex + \ && prev_line =~ s:def_regex + return ">".(indent / &shiftwidth + 1) + endif + + if line =~ s:doc_end_regex + \ && line !~ s:doc_line_regex + return "<".(indent / &shiftwidth + 1) + endif + if line =~ s:blank_regex if prev_line =~ s:blank_regex return -1 From 96491ce6e5df0f48312bd769da7370bb05fd261e Mon Sep 17 00:00:00 2001 From: alvinfrancis Date: Sat, 20 Apr 2013 21:03:09 +0800 Subject: [PATCH 182/513] Modify foldtext for docstring folding --- autoload/pymode/folding.vim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autoload/pymode/folding.vim b/autoload/pymode/folding.vim index 84992394..7d23d852 100644 --- a/autoload/pymode/folding.vim +++ b/autoload/pymode/folding.vim @@ -11,7 +11,7 @@ let s:doc_line_regex = '^\s*\("""\|''''''\).\+\1\s*$' fun! pymode#folding#text() " {{{ let fs = v:foldstart - while getline(fs) =~ '^\s*@' + while getline(fs) =~ '\%(^\s*@\)\|\%(^\s*\%("""\|''''''\)\s*$\)' let fs = nextnonblank(fs + 1) endwhile let line = getline(fs) @@ -25,6 +25,7 @@ fun! pymode#folding#text() " {{{ let line = substitute(line, '\t', onetab, 'g') let line = strpart(line, 0, windowwidth - 2 -len(foldedlinecount)) + let line = substitute(line, '\%("""\|''''''\)', '', '') let fillcharcount = windowwidth - len(line) - len(foldedlinecount) return line . '…' . repeat(" ",fillcharcount) . foldedlinecount . '…' . ' ' endfunction "}}} From 66418dc35fa47ead4f57988b9a7f2161afc4f050 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 26 Apr 2013 16:33:18 +0800 Subject: [PATCH 183/513] Update changelog --- Changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog.rst b/Changelog.rst index 3fd43e23..68cf7a56 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,10 @@ Changelog ========= +## 2013-04-26 0.6.16 +-------------------- +* Improvement folding (thanks @alvinfrancis); + ## 2013-04-01 0.6.15 -------------------- * Bugfix release From f9179917f0942256e9c48efef633bc639def7119 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 26 Apr 2013 16:34:13 +0800 Subject: [PATCH 184/513] Update version --- doc/pymode.txt | 2 +- ftplugin/python/init-pymode.vim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pymode.txt b/doc/pymode.txt index c4b19c3f..c2bcb5b9 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.15 + Version: 0.6.16 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index 1bf07e4c..e2733857 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -3,7 +3,7 @@ if exists('did_init_pymode_vim') endif let did_init_pymode_vim = 1 -let g:pymode_version = "0.6.15" +let g:pymode_version = "0.6.16" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From e209126502f9dcb75b916626faa72a586732a4df Mon Sep 17 00:00:00 2001 From: David Vogt Date: Mon, 29 Apr 2013 19:03:23 +0200 Subject: [PATCH 185/513] Fix typo in option filtering This also fixes an error where pymode_lint_ignore was not interpreted correctly anymore. --- pylibs/pymode/lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index b36c3abf..e2240876 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -25,7 +25,7 @@ def check_file(): s for s in ( get_option('lint_select').split(',') + get_var('lint_select').split(',')) - if i + if s ]) buffer = get_current_buffer() From f82867b8e0b5a9d406fb64a1297eb7a0edbce9a7 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 3 May 2013 18:30:38 +0800 Subject: [PATCH 186/513] Update pylama --- Changelog.rst | 5 + pylibs/pylama/__init__.py | 2 +- pylibs/pylama/mccabe.py | 2 + pylibs/pylama/pep8.py | 60 +++-- pylibs/pylama/pyflakes/__init__.py | 2 +- pylibs/pylama/pyflakes/checker.py | 255 +++++++++++++----- pylibs/pylama/pyflakes/messages.py | 105 ++++---- pylibs/pylama/pylint/__pkginfo__.py | 8 +- pylibs/pylama/pylint/checkers/__init__.py | 2 +- pylibs/pylama/pylint/checkers/base.py | 65 ++++- pylibs/pylama/pylint/checkers/format.py | 121 +-------- pylibs/pylama/pylint/checkers/imports.py | 31 +-- pylibs/pylama/pylint/checkers/logging.py | 6 +- .../checkers/{string_format.py => strings.py} | 141 +++++++++- pylibs/pylama/pylint/checkers/typecheck.py | 2 +- pylibs/pylama/pylint/checkers/utils.py | 3 + pylibs/pylama/pylint/lint.py | 30 ++- .../pylama/pylint/logilab/astng/__init__.py | 8 +- .../pylint/logilab/astng/__pkginfo__.py | 6 +- .../pylama/pylint/logilab/astng/as_string.py | 82 ++++-- pylibs/pylama/pylint/logilab/astng/bases.py | 82 ++---- .../pylint/logilab/astng/brain/py2stdlib.py | 8 +- pylibs/pylama/pylint/logilab/astng/builder.py | 9 +- .../pylama/pylint/logilab/astng/exceptions.py | 16 +- .../pylama/pylint/logilab/astng/inference.py | 35 +-- pylibs/pylama/pylint/logilab/astng/manager.py | 7 +- pylibs/pylama/pylint/logilab/astng/mixins.py | 18 +- .../pylint/logilab/astng/node_classes.py | 44 ++- pylibs/pylama/pylint/logilab/astng/nodes.py | 4 +- .../pylama/pylint/logilab/astng/protocols.py | 6 +- .../pylint/logilab/astng/raw_building.py | 24 +- .../pylama/pylint/logilab/astng/rebuilder.py | 5 +- .../pylint/logilab/astng/scoped_nodes.py | 42 ++- pylibs/pylama/pylint/logilab/astng/utils.py | 9 +- pylibs/pylama/pylint/reporters/__init__.py | 5 +- pylibs/pylama/pylint/reporters/text.py | 4 +- pylibs/pylama/pylint/utils.py | 46 +++- pylibs/pylama/utils.py | 2 +- 38 files changed, 760 insertions(+), 542 deletions(-) rename pylibs/pylama/pylint/checkers/{string_format.py => strings.py} (56%) diff --git a/Changelog.rst b/Changelog.rst index 68cf7a56..a9984140 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2013-05-03 0.6.17 +-------------------- +* Update `Pylint` to version 0.28.0; +* Update `pyflakes` to version 0.7.3; + ## 2013-04-26 0.6.16 -------------------- * Improvement folding (thanks @alvinfrancis); diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index 28159700..cebb3212 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -1,6 +1,6 @@ " pylama -- Python code audit. " -version_info = (0, 3, 0) +version_info = (0, 3, 1) __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/mccabe.py b/pylibs/pylama/mccabe.py index b52671bc..432f4c1e 100644 --- a/pylibs/pylama/mccabe.py +++ b/pylibs/pylama/mccabe.py @@ -310,3 +310,5 @@ def main(argv): if __name__ == '__main__': main(sys.argv[1:]) + +# lint=0 diff --git a/pylibs/pylama/pep8.py b/pylibs/pylama/pep8.py index 888df59d..e586c1d7 100644 --- a/pylibs/pylama/pep8.py +++ b/pylibs/pylama/pep8.py @@ -457,17 +457,17 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose): # an unbracketed continuation line (ie, backslash) open_row = 0 hang = rel_indent[row] - rel_indent[open_row] - visual_indent = indent_chances.get(start[1]) - - if token_type == tokenize.OP and text in ']})': - # this line starts with a closing bracket - if indent[depth]: - if start[1] != indent[depth]: - yield (start, "E124 closing bracket does not match " - "visual indentation") - elif hang: - yield (start, "E123 closing bracket does not match " - "indentation of opening bracket's line") + close_bracket = (token_type == tokenize.OP and text in ']})') + visual_indent = not close_bracket and indent_chances.get(start[1]) + + if close_bracket and indent[depth]: + # closing bracket for visual indent + if start[1] != indent[depth]: + yield (start, "E124 closing bracket does not match " + "visual indentation") + elif close_bracket and not hang: + # closing bracket matches indentation of opening bracket's line + pass elif visual_indent is True: # visual indent is verified if not indent[depth]: @@ -481,7 +481,9 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose): "under-indented for visual indent") elif hang == 4 or (indent_next and rel_indent[row] == 8): # hanging indent is verified - pass + if close_bracket: + yield (start, "E123 closing bracket does not match " + "indentation of opening bracket's line") else: # indent is broken if hang <= 0: @@ -1015,6 +1017,7 @@ def readlines(filename): finally: f.close() + BOM_UTF8 = '\xef\xbb\xbf' isidentifier = re.compile(r'[a-zA-Z_]\w*').match stdin_get_value = sys.stdin.read else: @@ -1033,6 +1036,7 @@ def readlines(filename): finally: f.close() + BOM_UTF8 = '\ufeff' isidentifier = str.isidentifier def stdin_get_value(): @@ -1198,14 +1202,19 @@ def __init__(self, filename=None, lines=None, self.lines = [] else: self.lines = lines + if self.lines and self.lines[0].startswith(BOM_UTF8): + self.lines[0] = self.lines[0][len(BOM_UTF8):] self.report = report or options.report self.report_error = self.report.error def report_invalid_syntax(self): exc_type, exc = sys.exc_info()[:2] - offset = exc.args[1] - if len(offset) > 2: - offset = offset[1:3] + if len(exc.args) > 1: + offset = exc.args[1] + if len(offset) > 2: + offset = offset[1:3] + else: + offset = (1, 0) self.report_error(offset[0], offset[1] or 0, 'E901 %s: %s' % (exc_type.__name__, exc.args[0]), self.report_invalid_syntax) @@ -1322,7 +1331,7 @@ def check_logical(self): def check_ast(self): try: tree = compile(''.join(self.lines), '', 'exec', PyCF_ONLY_AST) - except SyntaxError: + except (SyntaxError, TypeError): return self.report_invalid_syntax() for name, cls, _ in self._ast_checks: checker = cls(tree, self.filename) @@ -1579,7 +1588,7 @@ def __init__(self, *args, **kwargs): options.ignore = tuple(DEFAULT_IGNORE.split(',')) else: # Ignore all checks which are not explicitly selected - options.ignore = tuple(options.ignore or options.select and ('',)) + options.ignore = ('',) if options.select else tuple(options.ignore) options.benchmark_keys = BENCHMARK_KEYS[:] options.ignore_code = self.ignore_code options.physical_checks = self.get_checks('physical_line') @@ -1632,23 +1641,26 @@ def input_dir(self, dirname): print('directory ' + root) counters['directories'] += 1 for subdir in sorted(dirs): - if self.excluded(os.path.join(root, subdir)): + if self.excluded(subdir, root): dirs.remove(subdir) for filename in sorted(files): # contain a pattern that matches? if ((filename_match(filename, filepatterns) and - not self.excluded(filename))): + not self.excluded(filename, root))): runner(os.path.join(root, filename)) - def excluded(self, filename): + def excluded(self, filename, parent=None): """ Check if options.exclude contains a pattern that matches filename. """ + if not self.options.exclude: + return False basename = os.path.basename(filename) - return any((filename_match(filename, self.options.exclude, - default=False), - filename_match(basename, self.options.exclude, - default=False))) + if filename_match(basename, self.options.exclude): + return True + if parent: + filename = os.path.join(parent, filename) + return filename_match(filename, self.options.exclude) def ignore_code(self, code): """ diff --git a/pylibs/pylama/pyflakes/__init__.py b/pylibs/pylama/pyflakes/__init__.py index 7a2d0cd1..ca95838a 100644 --- a/pylibs/pylama/pyflakes/__init__.py +++ b/pylibs/pylama/pyflakes/__init__.py @@ -1,2 +1,2 @@ -__version__ = '0.6.1' +__version__ = '0.7.3a0' diff --git a/pylibs/pylama/pyflakes/checker.py b/pylibs/pylama/pyflakes/checker.py index 7e5286e2..3d4c6475 100644 --- a/pylibs/pylama/pyflakes/checker.py +++ b/pylibs/pylama/pyflakes/checker.py @@ -1,13 +1,17 @@ -# -*- test-case-name: pyflakes -*- -# (c) 2005-2010 Divmod, Inc. -# See LICENSE file for details - -import os.path +""" +Main module. + +Implement the central Checker class. +Also, it models the Bindings and Scopes. +""" +import doctest +import os +import sys try: - import builtins + builtin_vars = dir(__import__('builtins')) PY2 = False except ImportError: - import __builtin__ as builtins + builtin_vars = dir(__import__('__builtin__')) PY2 = True try: @@ -44,6 +48,15 @@ def iter_child_nodes(node): from . import messages +if PY2: + def getNodeType(node_class): + # workaround str.upper() which is locale-dependent + return str(unicode(node_class.__name__).upper()) +else: + def getNodeType(node_class): + return node_class.__name__.upper() + + class Binding(object): """ Represents the binding of a value to a name. @@ -71,10 +84,6 @@ def __repr__(self): id(self)) -class UnBinding(Binding): - """Created by the 'del' operator.""" - - class Importation(Binding): """ A binding created by an import statement. @@ -147,10 +156,10 @@ def names(self): class Scope(dict): importStarred = False # set to True when import * is found - usesLocals = False def __repr__(self): - return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self)) + scope_cls = self.__class__.__name__ + return '<%s at 0x%x %s>' % (scope_cls, id(self), dict.__repr__(self)) class ClassScope(Scope): @@ -163,9 +172,24 @@ class FunctionScope(Scope): @ivar globals: Names declared 'global' in this function. """ + usesLocals = False + alwaysUsed = set(['__tracebackhide__', + '__traceback_info__', '__traceback_supplement__']) + def __init__(self): super(FunctionScope, self).__init__() - self.globals = {} + # Simplify: manage the special locals as globals + self.globals = self.alwaysUsed.copy() + + def unusedAssignments(self): + """ + Return a generator for the assignments which have not been used. + """ + for name, binding in self.items(): + if (not binding.used and name not in self.globals + and not self.usesLocals + and isinstance(binding, Assignment)): + yield name, binding class ModuleScope(Scope): @@ -199,10 +223,18 @@ class Checker(object): """ nodeDepth = 0 + offset = None traceTree = False - builtIns = set(dir(builtins)) | set(_MAGIC_GLOBALS) + withDoctest = ('PYFLAKES_NODOCTEST' not in os.environ) + + builtIns = set(builtin_vars).union(_MAGIC_GLOBALS) + _customBuiltIns = os.environ.get('PYFLAKES_BUILTINS') + if _customBuiltIns: + builtIns.update(_customBuiltIns.split(',')) + del _customBuiltIns def __init__(self, tree, filename='(none)', builtins=None): + self._nodeHandlers = {} self._deferredFunctions = [] self._deferredAssignments = [] self.deadScopes = [] @@ -211,6 +243,7 @@ def __init__(self, tree, filename='(none)', builtins=None): if builtins: self.builtIns = self.builtIns.union(builtins) self.scopeStack = [ModuleScope()] + self.exceptHandlers = [()] self.futuresAllowed = True self.root = tree self.handleChildren(tree) @@ -235,21 +268,22 @@ def deferFunction(self, callable): `callable` is called, the scope at the time this is called will be restored, however it will contain any new bindings added to it. """ - self._deferredFunctions.append((callable, self.scopeStack[:])) + self._deferredFunctions.append((callable, self.scopeStack[:], self.offset)) def deferAssignment(self, callable): """ Schedule an assignment handler to be called just after deferred function handlers. """ - self._deferredAssignments.append((callable, self.scopeStack[:])) + self._deferredAssignments.append((callable, self.scopeStack[:], self.offset)) def runDeferred(self, deferred): """ Run the callables in C{deferred} using their associated scope stack. """ - for handler, scope in deferred: + for handler, scope, offset in deferred: self.scopeStack = scope + self.offset = offset handler() @property @@ -268,12 +302,13 @@ def checkDeadScopes(self): export = isinstance(scope.get('__all__'), ExportBinding) if export: all = scope['__all__'].names() - if not scope.importStarred and os.path.basename(self.filename) != '__init__.py': + if not scope.importStarred and \ + os.path.basename(self.filename) != '__init__.py': # Look for possible mistakes in the export list undefined = set(all) - set(scope) for name in undefined: self.report(messages.UndefinedExport, - scope['__all__'].source.lineno, name) + scope['__all__'].source, name) else: all = [] @@ -282,7 +317,7 @@ def checkDeadScopes(self): if isinstance(importation, Importation): if not importation.used and importation.name not in all: self.report(messages.UnusedImport, - importation.source.lineno, importation.name) + importation.source, importation.name) def pushFunctionScope(self): self.scopeStack.append(FunctionScope()) @@ -358,12 +393,13 @@ def addBinding(self, node, value, reportRedef=True): existing = scope.get(value.name) if (isinstance(existing, Importation) and not existing.used - and (not isinstance(value, Importation) or value.fullName == existing.fullName) + and (not isinstance(value, Importation) or + value.fullName == existing.fullName) and reportRedef and not self.differentForks(node, existing.source)): redefinedWhileUnused = True self.report(messages.RedefinedWhileUnused, - node.lineno, value.name, existing.source.lineno) + node, value.name, existing.source) existing = self.scope.get(value.name) if not redefinedWhileUnused and self.hasParent(value.source, ast.ListComp): @@ -371,41 +407,44 @@ def addBinding(self, node, value, reportRedef=True): and not self.hasParent(existing.source, (ast.For, ast.ListComp)) and not self.differentForks(node, existing.source)): self.report(messages.RedefinedInListComp, - node.lineno, value.name, existing.source.lineno) + node, value.name, existing.source) - if isinstance(value, UnBinding): - try: - del self.scope[value.name] - except KeyError: - self.report(messages.UndefinedName, node.lineno, value.name) - elif (isinstance(existing, Definition) - and not existing.used - and not self.differentForks(node, existing.source)): + if (isinstance(existing, Definition) + and not existing.used + and not self.differentForks(node, existing.source)): self.report(messages.RedefinedWhileUnused, - node.lineno, value.name, existing.source.lineno) + node, value.name, existing.source) else: self.scope[value.name] = value + def getNodeHandler(self, node_class): + try: + return self._nodeHandlers[node_class] + except KeyError: + nodeType = getNodeType(node_class) + self._nodeHandlers[node_class] = handler = getattr(self, nodeType) + return handler + def handleNodeLoad(self, node): name = getNodeName(node) if not name: return # try local scope - importStarred = self.scope.importStarred try: - self.scope[name].used = (self.scope, node.lineno) + self.scope[name].used = (self.scope, node) except KeyError: pass else: return # try enclosing function scopes + importStarred = self.scope.importStarred for scope in self.scopeStack[-2:0:-1]: importStarred = importStarred or scope.importStarred if not isinstance(scope, FunctionScope): continue try: - scope[name].used = (self.scope, node.lineno) + scope[name].used = (self.scope, node) except KeyError: pass else: @@ -414,14 +453,22 @@ def handleNodeLoad(self, node): # try global scope importStarred = importStarred or self.scopeStack[0].importStarred try: - self.scopeStack[0][name].used = (self.scope, node.lineno) + self.scopeStack[0][name].used = (self.scope, node) except KeyError: - if not importStarred and name not in self.builtIns: - if (os.path.basename(self.filename) == '__init__.py' and name == '__path__'): - # the special name __path__ is valid only in packages - pass - else: - self.report(messages.UndefinedName, node.lineno, name) + pass + else: + return + + # look in the built-ins + if importStarred or name in self.builtIns: + return + if name == '__path__' and os.path.basename(self.filename) == '__init__.py': + # the special name __path__ is valid only in packages + return + + # protected with a NameError handler? + if 'NameError' not in self.exceptHandlers[-1]: + self.report(messages.UndefinedName, node, name) def handleNodeStore(self, node): name = getNodeName(node) @@ -436,17 +483,18 @@ def handleNodeStore(self, node): # if the name was defined in that scope, and the name has # been accessed already in the current scope, and hasn't # been declared global - if (name in scope and scope[name].used and scope[name].used[0] is self.scope - and name not in self.scope.globals): + used = name in scope and scope[name].used + if used and used[0] is self.scope and name not in self.scope.globals: # then it's probably a mistake self.report(messages.UndefinedLocal, - scope[name].used[1], name, scope[name].source.lineno) + scope[name].used[1], name, scope[name].source) break parent = getattr(node, 'parent', None) if isinstance(parent, (ast.For, ast.comprehension, ast.Tuple, ast.List)): binding = Binding(name, node) - elif parent is not None and name == '__all__' and isinstance(self.scope, ModuleScope): + elif (parent is not None and name == '__all__' and + isinstance(self.scope, ModuleScope)): binding = ExportBinding(name, parent.value) else: binding = Assignment(name, node) @@ -459,9 +507,12 @@ def handleNodeDelete(self, node): if not name: return if isinstance(self.scope, FunctionScope) and name in self.scope.globals: - del self.scope.globals[name] + self.scope.globals.remove(name) else: - self.addBinding(node, UnBinding(name, node)) + try: + del self.scope[name] + except KeyError: + self.report(messages.UndefinedName, node, name) def handleChildren(self, tree): for node in iter_child_nodes(tree): @@ -475,32 +526,72 @@ def isDocstring(self, node): return isinstance(node, ast.Str) or (isinstance(node, ast.Expr) and isinstance(node.value, ast.Str)) + def getDocstring(self, node): + if isinstance(node, ast.Expr): + node = node.value + if not isinstance(node, ast.Str): + return (None, None) + # Computed incorrectly if the docstring has backslash + doctest_lineno = node.lineno - node.s.count('\n') - 1 + return (node.s, doctest_lineno) + def handleNode(self, node, parent): if node is None: return - node.parent = parent + if self.offset and getattr(node, 'lineno', None) is not None: + node.lineno += self.offset[0] + node.col_offset += self.offset[1] if self.traceTree: print(' ' * self.nodeDepth + node.__class__.__name__) - self.nodeDepth += 1 if self.futuresAllowed and not (isinstance(node, ast.ImportFrom) or self.isDocstring(node)): self.futuresAllowed = False - nodeType = node.__class__.__name__.upper() + self.nodeDepth += 1 node.level = self.nodeDepth + node.parent = parent try: - handler = getattr(self, nodeType) + handler = self.getNodeHandler(node.__class__) handler(node) finally: self.nodeDepth -= 1 if self.traceTree: print(' ' * self.nodeDepth + 'end ' + node.__class__.__name__) + _getDoctestExamples = doctest.DocTestParser().get_examples + + def handleDoctests(self, node): + try: + docstring, node_lineno = self.getDocstring(node.body[0]) + if not docstring: + return + examples = self._getDoctestExamples(docstring) + except (ValueError, IndexError): + # e.g. line 6 of the docstring for has inconsistent + # leading whitespace: ... + return + node_offset = self.offset or (0, 0) + self.pushFunctionScope() + for example in examples: + try: + tree = compile(example.source, "", "exec", ast.PyCF_ONLY_AST) + except SyntaxError: + e = sys.exc_info()[1] + position = (node_lineno + example.lineno + e.lineno, + example.indent + 4 + e.offset) + self.report(messages.DoctestSyntaxError, node, position) + else: + self.offset = (node_offset[0] + node_lineno + example.lineno, + node_offset[1] + example.indent + 4) + self.handleChildren(tree) + self.offset = node_offset + self.popScope() + def ignore(self, node): pass # "stmt" type nodes RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = \ - TRYEXCEPT = TRYFINALLY = TRY = ASSERT = EXEC = EXPR = handleChildren + TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren CONTINUE = BREAK = PASS = ignore @@ -530,7 +621,7 @@ def GLOBAL(self, node): Keep track of globals declarations. """ if isinstance(self.scope, FunctionScope): - self.scope.globals.update(dict.fromkeys(node.names)) + self.scope.globals.update(node.names) NONLOCAL = GLOBAL @@ -579,7 +670,7 @@ def collectLoopVars(n): # unused ones will get an unused import warning and self.scope[varn].used): self.report(messages.ImportShadowedByLoopVar, - node.lineno, varn, self.scope[varn].source.lineno) + node, varn, self.scope[varn].source) self.handleChildren(node) @@ -587,12 +678,13 @@ def NAME(self, node): """ Handle occurrence of Name (which can be a load/store/delete access.) """ - if node.id == 'locals' and isinstance(node.parent, ast.Call): - # we are doing locals() call in current scope - self.scope.usesLocals = True # Locate the name in locals / function / globals scopes. if isinstance(node.ctx, (ast.Load, ast.AugLoad)): self.handleNodeLoad(node) + if (node.id == 'locals' and isinstance(self.scope, FunctionScope) + and isinstance(node.parent, ast.Call)): + # we are doing locals() call in current scope + self.scope.usesLocals = True elif isinstance(node.ctx, (ast.Store, ast.AugStore)): self.handleNodeStore(node) elif isinstance(node.ctx, ast.Del): @@ -607,6 +699,8 @@ def FUNCTIONDEF(self, node): self.handleNode(deco, node) self.addBinding(node, FunctionDefinition(node.name, node)) self.LAMBDA(node) + if self.withDoctest: + self.deferFunction(lambda: self.handleDoctests(node)) def LAMBDA(self, node): args = [] @@ -619,7 +713,7 @@ def addArgs(arglist): else: if arg.id in args: self.report(messages.DuplicateArgument, - node.lineno, arg.id) + node, arg.id) args.append(arg.id) addArgs(node.args.args) defaults = node.args.defaults @@ -627,7 +721,7 @@ def addArgs(arglist): for arg in node.args.args + node.args.kwonlyargs: if arg.arg in args: self.report(messages.DuplicateArgument, - node.lineno, arg.arg) + node, arg.arg) args.append(arg.arg) self.handleNode(arg.annotation, node) if hasattr(node, 'returns'): # Only for FunctionDefs @@ -641,7 +735,7 @@ def addArgs(arglist): if not wildcard: continue if wildcard in args: - self.report(messages.DuplicateArgument, node.lineno, wildcard) + self.report(messages.DuplicateArgument, node, wildcard) args.append(wildcard) for default in defaults: self.handleNode(default, node) @@ -663,12 +757,8 @@ def checkUnusedAssignments(): """ Check to see if any assignments have not been used. """ - for name, binding in self.scope.items(): - if (not binding.used and name not in self.scope.globals - and not self.scope.usesLocals - and isinstance(binding, Assignment)): - self.report(messages.UnusedVariable, - binding.source.lineno, name) + for name, binding in self.scope.unusedAssignments(): + self.report(messages.UnusedVariable, binding.source, name) self.deferAssignment(checkUnusedAssignments) self.popScope() @@ -688,6 +778,8 @@ def CLASSDEF(self, node): for keywordNode in node.keywords: self.handleNode(keywordNode, node) self.pushClassScope() + if self.withDoctest: + self.deferFunction(lambda: self.handleDoctests(node)) for stmt in node.body: self.handleNode(stmt, node) self.popScope() @@ -713,21 +805,42 @@ def IMPORTFROM(self, node): if node.module == '__future__': if not self.futuresAllowed: self.report(messages.LateFutureImport, - node.lineno, [n.name for n in node.names]) + node, [n.name for n in node.names]) else: self.futuresAllowed = False for alias in node.names: if alias.name == '*': self.scope.importStarred = True - self.report(messages.ImportStarUsed, node.lineno, node.module) + self.report(messages.ImportStarUsed, node, node.module) continue name = alias.asname or alias.name importation = Importation(name, node) if node.module == '__future__': - importation.used = (self.scope, node.lineno) + importation.used = (self.scope, node) self.addBinding(node, importation) + def TRY(self, node): + handler_names = [] + # List the exception handlers + for handler in node.handlers: + if isinstance(handler.type, ast.Tuple): + for exc_type in handler.type.elts: + handler_names.append(getNodeName(exc_type)) + elif handler.type: + handler_names.append(getNodeName(handler.type)) + # Memorize the except handlers and process the body + self.exceptHandlers.append(handler_names) + for child in node.body: + self.handleNode(child, node) + self.exceptHandlers.pop() + # Process the other nodes: "except:", "else:", "finally:" + for child in iter_child_nodes(node): + if child not in node.body: + self.handleNode(child, node) + + TRYEXCEPT = TRY + def EXCEPTHANDLER(self, node): # 3.x: in addition to handling children, we must handle the name of # the exception, which is not a Name node, but a simple string. diff --git a/pylibs/pylama/pyflakes/messages.py b/pylibs/pylama/pyflakes/messages.py index b15fad63..a4c31985 100644 --- a/pylibs/pylama/pyflakes/messages.py +++ b/pylibs/pylama/pyflakes/messages.py @@ -1,103 +1,118 @@ -# (c) 2005 Divmod, Inc. See LICENSE file for details +""" +Provide the class Message and its subclasses. +""" class Message(object): message = '' message_args = () - def __init__(self, filename, lineno): + def __init__(self, filename, loc): self.filename = filename - self.lineno = lineno + self.lineno = loc.lineno + self.col = getattr(loc, 'col_offset', 0) def __str__(self): - return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args) + return '%s:%s: %s' % (self.filename, self.lineno, + self.message % self.message_args) class UnusedImport(Message): - message = 'W402 %r imported but unused' + message = 'W0611 %r imported but unused' - def __init__(self, filename, lineno, name): - Message.__init__(self, filename, lineno) + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) self.message_args = (name,) class RedefinedWhileUnused(Message): - message = 'W801 redefinition of unused %r from line %r' + message = 'W0404 redefinition of unused %r from line %r' - def __init__(self, filename, lineno, name, orig_lineno): - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) class RedefinedInListComp(Message): - message = 'W801 rlist comprehension redefines %r from line %r' + message = 'W0621 list comprehension redefines %r from line %r' - def __init__(self, filename, lineno, name, orig_lineno): - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) class ImportShadowedByLoopVar(Message): - message = 'W403 iimport %r from line %r shadowed by loop variable' + message = 'W0621 import %r from line %r shadowed by loop variable' - def __init__(self, filename, lineno, name, orig_lineno): - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) class ImportStarUsed(Message): - message = "W404 ''from %s import *' used; unable to detect undefined names" + message = "W0401 'from %s import *' used; unable to detect undefined names" - def __init__(self, filename, lineno, modname): - Message.__init__(self, filename, lineno) + def __init__(self, filename, loc, modname): + Message.__init__(self, filename, loc) self.message_args = (modname,) class UndefinedName(Message): - message = 'W802 undefined name %r' + message = 'E0602 undefined name %r' - def __init__(self, filename, lineno, name): - Message.__init__(self, filename, lineno) + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) self.message_args = (name,) +class DoctestSyntaxError(Message): + message = 'W0511 syntax error in doctest' + + def __init__(self, filename, loc, position=None): + Message.__init__(self, filename, loc) + if position: + (self.lineno, self.col) = position + self.message_args = () + + class UndefinedExport(Message): - message = 'W803 undefined name %r in __all__' + message = 'E0603 undefined name %r in __all__' - def __init__(self, filename, lineno, name): - Message.__init__(self, filename, lineno) + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) self.message_args = (name,) class UndefinedLocal(Message): - message = "W804 local variable %r (defined in enclosing scope on line %r) referenced before assignment" + message = ('E0602 local variable %r (defined in enclosing scope on line %r) ' + 'referenced before assignment') - def __init__(self, filename, lineno, name, orig_lineno): - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) class DuplicateArgument(Message): - message = 'W805 duplicate argument %r in function definition' + message = 'E1122 duplicate argument %r in function definition' - def __init__(self, filename, lineno, name): - Message.__init__(self, filename, lineno) + def __init__(self, filename, loc, name): + Message.__init__(self, filename, loc) self.message_args = (name,) class Redefined(Message): - message = 'W806 redefinition of %r from line %r' + message = 'W0621 redefinition of %r from line %r' - def __init__(self, filename, lineno, name, orig_lineno): - Message.__init__(self, filename, lineno) - self.message_args = (name, orig_lineno) + def __init__(self, filename, loc, name, orig_loc): + Message.__init__(self, filename, loc) + self.message_args = (name, orig_loc.lineno) class LateFutureImport(Message): - message = 'W405 future import(s) %r after other statements' + message = 'W0410 future import(s) %r after other statements' - def __init__(self, filename, lineno, names): - Message.__init__(self, filename, lineno) + def __init__(self, filename, loc, names): + Message.__init__(self, filename, loc) self.message_args = (names,) @@ -106,8 +121,8 @@ class UnusedVariable(Message): Indicates that a variable has been explicity assigned to but not actually used. """ - message = 'W806 local variable %r is assigned to but never used' + message = 'W0612 local variable %r is assigned to but never used' - def __init__(self, filename, lineno, names): - Message.__init__(self, filename, lineno) + def __init__(self, filename, loc, names): + Message.__init__(self, filename, loc) self.message_args = (names,) diff --git a/pylibs/pylama/pylint/__pkginfo__.py b/pylibs/pylama/pylint/__pkginfo__.py index 5604692c..a018f934 100644 --- a/pylibs/pylama/pylint/__pkginfo__.py +++ b/pylibs/pylama/pylint/__pkginfo__.py @@ -18,16 +18,14 @@ modname = distname = 'pylint' -numversion = (0, 27, 0) +numversion = (0, 28, 0) version = '.'.join([str(num) for num in numversion]) -install_requires = ['logilab-common >= 0.53.0', 'logilab-astng >= 0.21.1'] +install_requires = ['logilab-common >= 0.53.0', 'logilab-astng >= 0.24.3'] license = 'GPL' -copyright = 'Logilab S.A.' description = "python code static checker" -web = "http://www.logilab.org/project/%s" % distname -ftp = "ftp://ftp.logilab.org/pub/%s" % modname +web = 'http://www.pylint.org' mailinglist = "mailto://python-projects@lists.logilab.org" author = 'Logilab' author_email = 'python-projects@lists.logilab.org' diff --git a/pylibs/pylama/pylint/checkers/__init__.py b/pylibs/pylama/pylint/checkers/__init__.py index 6c98c9eb..bc722279 100644 --- a/pylibs/pylama/pylint/checkers/__init__.py +++ b/pylibs/pylama/pylint/checkers/__init__.py @@ -30,7 +30,7 @@ 12: logging 13: string_format 14: string_constant -14-50: not yet used: reserved for future internal checkers. +15-50: not yet used: reserved for future internal checkers. 51-99: perhaps used: reserved for external checkers The raw_metrics checker has no number associated since it doesn't emit any diff --git a/pylibs/pylama/pylint/checkers/base.py b/pylibs/pylama/pylint/checkers/base.py index e85dba5a..495076f8 100644 --- a/pylibs/pylama/pylint/checkers/base.py +++ b/pylibs/pylama/pylint/checkers/base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # Copyright (c) 2009-2010 Arista Networks, Inc. # http://www.logilab.fr/ -- mailto:contact@logilab.fr # This program is free software; you can redistribute it and/or modify it under @@ -66,6 +66,19 @@ def in_nested_list(nested_list, obj): return True return False +def _loop_exits_early(loop): + """Returns true if a loop has a break statement in its body.""" + loop_nodes = (astng.For, astng.While) + # Loop over body explicitly to avoid matching break statements + # in orelse. + for child in loop.body: + if isinstance(child, loop_nodes): + continue + for _ in child.nodes_of_class(astng.Break, skip_klass=loop_nodes): + return True + return False + + def report_by_type_stats(sect, stats, old_stats): """make a report of @@ -160,6 +173,15 @@ class BasicErrorChecker(_BasicChecker): 'nonexistent-operator', "Used when you attempt to use the C-style pre-increment or" "pre-decrement operator -- and ++, which doesn't exist in Python."), + 'E0108': ('Duplicate argument name %s in function definition', + 'duplicate-argument-name', + 'Duplicate argument names in function definitions are syntax' + ' errors.'), + 'W0120': ('Else clause on loop without a break statement', + 'useless-else-on-loop', + 'Loops should only have an else clause if they can exit early ' + 'with a break statement, otherwise the statements under else ' + 'should be on the same scope as the loop itself.'), } def __init__(self, linter): @@ -169,7 +191,7 @@ def __init__(self, linter): def visit_class(self, node): self._check_redefinition('class', node) - @check_messages('E0100', 'E0101', 'E0102', 'E0106') + @check_messages('E0100', 'E0101', 'E0102', 'E0106', 'E0108') def visit_function(self, node): if not redefined_by_decorator(node): self._check_redefinition(node.is_method() and 'method' or 'function', node) @@ -192,6 +214,13 @@ def visit_function(self, node): retnode.value.value is not None: self.add_message('E0106', node=node, line=retnode.fromlineno) + args = set() + for name in node.argnames(): + if name in args: + self.add_message('E0108', node=node, args=(name,)) + else: + args.add(name) + @check_messages('E0104') def visit_return(self, node): @@ -200,7 +229,7 @@ def visit_return(self, node): @check_messages('E0105') def visit_yield(self, node): - if not isinstance(node.frame(), astng.Function): + if not isinstance(node.frame(), (astng.Function, astng.Lambda)): self.add_message('E0105', node=node) @check_messages('E0103') @@ -211,6 +240,14 @@ def visit_continue(self, node): def visit_break(self, node): self._check_in_loop(node, 'break') + @check_messages('W0120') + def visit_for(self, node): + self._check_else_on_loop(node) + + @check_messages('W0120') + def visit_while(self, node): + self._check_else_on_loop(node) + @check_messages('E0107') def visit_unaryop(self, node): """check use of the non-existent ++ adn -- operator operator""" @@ -219,6 +256,15 @@ def visit_unaryop(self, node): (node.operand.op == node.op)): self.add_message('E0107', node=node, args=node.op*2) + def _check_else_on_loop(self, node): + """Check that any loop with an else clause has a break statement.""" + if node.orelse and not _loop_exits_early(node): + self.add_message('W0120', node=node, + # This is not optimal, but the line previous + # to the first statement in the else clause + # will usually be the one that contains the else:. + line=node.orelse[0].lineno - 1) + def _check_in_loop(self, node, node_name): """check that a node is inside a for or while loop""" _node = node.parent @@ -448,18 +494,15 @@ def visit_function(self, node): value = default.infer().next() except astng.InferenceError: continue - if isinstance(value, (astng.Dict, astng.List)): + if (isinstance(value, astng.Instance) and + value.qname() in ('__builtin__.set', '__builtin__.dict', '__builtin__.list')): if value is default: msg = default.as_string() + elif type(value) is astng.Instance: + msg = '%s (%s)' % (default.as_string(), value.qname()) else: msg = '%s (%s)' % (default.as_string(), value.as_string()) self.add_message('W0102', node=node, args=(msg,)) - if value.qname() == '__builtin__.set': - if isinstance(default, astng.CallFunc): - msg = default.as_string() - else: - msg = '%s (%s)' % (default.as_string(), value.qname()) - self.add_message('W0102', node=node, args=(msg,)) @check_messages('W0101', 'W0150') def visit_return(self, node): @@ -534,7 +577,7 @@ def visit_assert(self, node): """check the use of an assert statement on a tuple.""" if node.fail is None and isinstance(node.test, astng.Tuple) and \ len(node.test.elts) == 2: - self.add_message('W0199', line=node.fromlineno, node=node) + self.add_message('W0199', node=node) @check_messages('W0109') def visit_dict(self, node): diff --git a/pylibs/pylama/pylint/checkers/format.py b/pylibs/pylama/pylint/checkers/format.py index 8e165cb8..83da2613 100644 --- a/pylibs/pylama/pylint/checkers/format.py +++ b/pylibs/pylama/pylint/checkers/format.py @@ -1,6 +1,4 @@ -# Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). -# Copyright 2012 Google Inc. +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # # 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 @@ -34,6 +32,7 @@ from ..interfaces import IRawChecker, IASTNGChecker from ..checkers import BaseRawChecker from ..checkers.utils import check_messages +from ..utils import WarningScope MSGS = { 'C0301': ('Line too long (%s/%s)', @@ -57,18 +56,22 @@ isn\'t necessary (that\'s python, not C ;).'), 'C0321': ('More than one statement on a single line', 'multiple-statements', - 'Used when more than on statement are found on the same line.'), + 'Used when more than on statement are found on the same line.', + {'scope': WarningScope.NODE}), 'C0322': ('Operator not preceded by a space\n%s', 'no-space-before-operator', 'Used when one of the following operator (!= | <= | == | >= | < ' - '| > | = | \\+= | -= | \\*= | /= | %) is not preceded by a space.'), + '| > | = | \\+= | -= | \\*= | /= | %) is not preceded by a space.', + {'scope': WarningScope.NODE}), 'C0323': ('Operator not followed by a space\n%s', 'no-space-after-operator', 'Used when one of the following operator (!= | <= | == | >= | < ' - '| > | = | \\+= | -= | \\*= | /= | %) is not followed by a space.'), + '| > | = | \\+= | -= | \\*= | /= | %) is not followed by a space.', + {'scope': WarningScope.NODE}), 'C0324': ('Comma not followed by a space\n%s', 'no-space-after-comma', - 'Used when a comma (",") is not followed by a space.'), + 'Used when a comma (",") is not followed by a space.', + {'scope': WarningScope.NODE}), } if sys.version_info < (3, 0): @@ -86,7 +89,8 @@ 'W0333': ('Use of the `` operator', 'backtick', 'Used when the deprecated "``" (backtick) operator is used ' - 'instead of the str() function.'), + 'instead of the str() function.', + {'scope': WarningScope.NODE}), }) # simple quoted string rgx @@ -128,7 +132,6 @@ 'C0324'), ) -_PY3K = sys.version_info >= (3, 0) def get_string_coords(line): """return a list of string positions (tuple (start, end)) in the line @@ -371,106 +374,6 @@ def check_indent_level(self, string, expected, line_num): expected * unit_size)) -class StringConstantChecker(BaseRawChecker): - """Check string literals""" - - msgs = { - 'W1401': ('Anomalous backslash in string: \'%s\'. ' - 'String constant might be missing an r prefix.', - 'anomalous-backslash-in-string', - 'Used when a backslash is in a literal string but not as an ' - 'escape.'), - 'W1402': ('Anomalous Unicode escape in byte string: \'%s\'. ' - 'String constant might be missing an r or u prefix.', - 'anomalous-unicode-escape-in-string', - 'Used when an escape like \\u is encountered in a byte ' - 'string where it has no effect.'), - } - name = 'string_constant' - __implements__ = (IRawChecker, IASTNGChecker) - - # Characters that have a special meaning after a backslash in either - # Unicode or byte strings. - ESCAPE_CHARACTERS = 'abfnrtvx\n\r\t\\\'\"01234567' - - # TODO(mbp): Octal characters are quite an edge case today; people may - # prefer a separate warning where they occur. \0 should be allowed. - - # Characters that have a special meaning after a backslash but only in - # Unicode strings. - UNICODE_ESCAPE_CHARACTERS = 'uUN' - - def process_tokens(self, tokens): - for (tok_type, token, (start_row, start_col), _, _) in tokens: - if tok_type == tokenize.STRING: - # 'token' is the whole un-parsed token; we can look at the start - # of it to see whether it's a raw or unicode string etc. - self.process_string_token(token, start_row, start_col) - - def process_string_token(self, token, start_row, start_col): - for i, c in enumerate(token): - if c in '\'\"': - quote_char = c - break - prefix = token[:i].lower() # markers like u, b, r. - after_prefix = token[i:] - if after_prefix[:3] == after_prefix[-3:] == 3 * quote_char: - string_body = after_prefix[3:-3] - else: - string_body = after_prefix[1:-1] # Chop off quotes - # No special checks on raw strings at the moment. - if 'r' not in prefix: - self.process_non_raw_string_token(prefix, string_body, - start_row, start_col) - - def process_non_raw_string_token(self, prefix, string_body, start_row, - start_col): - """check for bad escapes in a non-raw string. - - prefix: lowercase string of eg 'ur' string prefix markers. - string_body: the un-parsed body of the string, not including the quote - marks. - start_row: integer line number in the source. - start_col: integer column number in the source. - """ - # Walk through the string; if we see a backslash then escape the next - # character, and skip over it. If we see a non-escaped character, - # alert, and continue. - # - # Accept a backslash when it escapes a backslash, or a quote, or - # end-of-line, or one of the letters that introduce a special escape - # sequence - # - # TODO(mbp): Maybe give a separate warning about the rarely-used - # \a \b \v \f? - # - # TODO(mbp): We could give the column of the problem character, but - # add_message doesn't seem to have a way to pass it through at present. - i = 0 - while True: - i = string_body.find('\\', i) - if i == -1: - break - # There must be a next character; having a backslash at the end - # of the string would be a SyntaxError. - next_char = string_body[i+1] - match = string_body[i:i+2] - if next_char in self.UNICODE_ESCAPE_CHARACTERS: - if 'u' in prefix: - pass - elif _PY3K and 'b' not in prefix: - pass # unicode by default - else: - self.add_message('W1402', line=start_row, args=(match, )) - elif next_char not in self.ESCAPE_CHARACTERS: - self.add_message('W1401', line=start_row, args=(match, )) - # Whether it was a valid escape or not, backslash followed by - # another character can always be consumed whole: the second - # character can never be the start of a new backslash escape. - i += 2 - - def register(linter): """required method to auto register this checker """ linter.register_checker(FormatChecker(linter)) - linter.register_checker(StringConstantChecker(linter)) diff --git a/pylibs/pylama/pylint/checkers/imports.py b/pylibs/pylama/pylint/checkers/imports.py index b94e3685..e243ff6b 100644 --- a/pylibs/pylama/pylint/checkers/imports.py +++ b/pylibs/pylama/pylint/checkers/imports.py @@ -52,21 +52,6 @@ def get_first_import(node, context, name, base, level): # utilities to represents import dependencies as tree and dot graph ########### -def filter_dependencies_info(dep_info, package_dir, mode='external'): - """filter external or internal dependencies from dep_info (return a - new dictionary containing the filtered modules only) - """ - if mode == 'external': - filter_func = lambda x: not is_standard_module(x, (package_dir,)) - else: - assert mode == 'internal' - filter_func = lambda x: is_standard_module(x, (package_dir,)) - result = {} - for importee, importers in dep_info.iteritems(): - if filter_func(importee): - result[importee] = importers - return result - def make_tree_defs(mod_files_list): """get a list of 2-uple (module, list_of_files_which_import_this_module), it will return a dictionary to represent this as a tree @@ -313,7 +298,7 @@ def _add_imported_module(self, node, importedmodname): importedmodname, set()) if not context_name in importedmodnames: importedmodnames.add(context_name) - if is_standard_module( importedmodname, (self.package_dir(),) ): + if is_standard_module(importedmodname, (self.package_dir(),)): # update import graph mgraph = self.import_graph.setdefault(context_name, set()) if not importedmodname in mgraph: @@ -373,8 +358,11 @@ def _external_dependencies_info(self): cache them """ if self.__ext_dep_info is None: - self.__ext_dep_info = filter_dependencies_info( - self.stats['dependencies'], self.package_dir(), 'external') + package = self.linter.base_name + self.__ext_dep_info = result = {} + for importee, importers in self.stats['dependencies'].iteritems(): + if not importee.startswith(package): + result[importee] = importers return self.__ext_dep_info def _internal_dependencies_info(self): @@ -382,8 +370,11 @@ def _internal_dependencies_info(self): cache them """ if self.__int_dep_info is None: - self.__int_dep_info = filter_dependencies_info( - self.stats['dependencies'], self.package_dir(), 'internal') + package = self.linter.base_name + self.__int_dep_info = result = {} + for importee, importers in self.stats['dependencies'].iteritems(): + if importee.startswith(package): + result[importee] = importers return self.__int_dep_info diff --git a/pylibs/pylama/pylint/checkers/logging.py b/pylibs/pylama/pylint/checkers/logging.py index 4c78d8f2..bb95a539 100644 --- a/pylibs/pylama/pylint/checkers/logging.py +++ b/pylibs/pylama/pylint/checkers/logging.py @@ -85,9 +85,9 @@ def visit_callfunc(self, node): try: logger_class = [inferred for inferred in node.func.expr.infer() if ( isinstance(inferred, astng.Instance) - and [ancestor for ancestor in inferred._proxied.ancestors() if ( - ancestor.name == 'Logger' - and ancestor.parent.name == 'logging')])] + and any(ancestor for ancestor in inferred._proxied.ancestors() if ( + ancestor.name == 'Logger' + and ancestor.parent.name == 'logging')))] except astng.exceptions.InferenceError: return if (node.func.expr.name != self._logging_name and not logger_class): diff --git a/pylibs/pylama/pylint/checkers/string_format.py b/pylibs/pylama/pylint/checkers/strings.py similarity index 56% rename from pylibs/pylama/pylint/checkers/string_format.py rename to pylibs/pylama/pylint/checkers/strings.py index fdec7022..3c755dd7 100644 --- a/pylibs/pylama/pylint/checkers/string_format.py +++ b/pylibs/pylama/pylint/checkers/strings.py @@ -1,5 +1,7 @@ # Copyright (c) 2009-2010 Arista Networks, Inc. - James Lingard -# Copyright (c) 2004-2010 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE). +# Copyright 2012 Google Inc. +# # http://www.logilab.fr/ -- mailto:contact@logilab.fr # 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 @@ -16,11 +18,16 @@ """Checker for string formatting operations. """ +import sys +import tokenize + from ..logilab import astng -from ..interfaces import IASTNGChecker -from ..checkers import BaseChecker + +from ..interfaces import IRawChecker, IASTNGChecker +from ..checkers import BaseChecker, BaseRawChecker from ..checkers import utils +_PY3K = sys.version_info >= (3, 0) MSGS = { 'E1300': ("Unsupported format character %r (%#02x) at index %d", @@ -75,7 +82,7 @@ class StringFormatChecker(BaseChecker): """ __implements__ = (IASTNGChecker,) - name = 'string_format' + name = 'string' msgs = MSGS def visit_binop(self, node): @@ -158,6 +165,132 @@ def visit_binop(self, node): self.add_message('E1306', node=node) +class StringMethodsChecker(BaseChecker): + __implements__ = (IASTNGChecker,) + name = 'string' + msgs = { + 'E1310': ("Suspicious argument in %s.%s call", + "bad-str-strip-call", + "The argument to a str.{l,r,}strip call contains a" + " duplicate character, "), + } + + def visit_callfunc(self, node): + func = utils.safe_infer(node.func) + if (isinstance(func, astng.BoundMethod) + and isinstance(func.bound, astng.Instance) + and func.bound.name in ('str', 'unicode', 'bytes') + and func.name in ('strip', 'lstrip', 'rstrip') + and node.args): + arg = utils.safe_infer(node.args[0]) + if not isinstance(arg, astng.Const): + return + if len(arg.value) != len(set(arg.value)): + self.add_message('E1310', node=node, + args=(func.bound.name, func.name)) + + +class StringConstantChecker(BaseRawChecker): + """Check string literals""" + __implements__ = (IRawChecker, IASTNGChecker) + name = 'string_constant' + msgs = { + 'W1401': ('Anomalous backslash in string: \'%s\'. ' + 'String constant might be missing an r prefix.', + 'anomalous-backslash-in-string', + 'Used when a backslash is in a literal string but not as an ' + 'escape.'), + 'W1402': ('Anomalous Unicode escape in byte string: \'%s\'. ' + 'String constant might be missing an r or u prefix.', + 'anomalous-unicode-escape-in-string', + 'Used when an escape like \\u is encountered in a byte ' + 'string where it has no effect.'), + } + + # Characters that have a special meaning after a backslash in either + # Unicode or byte strings. + ESCAPE_CHARACTERS = 'abfnrtvx\n\r\t\\\'\"01234567' + + # TODO(mbp): Octal characters are quite an edge case today; people may + # prefer a separate warning where they occur. \0 should be allowed. + + # Characters that have a special meaning after a backslash but only in + # Unicode strings. + UNICODE_ESCAPE_CHARACTERS = 'uUN' + + def process_tokens(self, tokens): + for (tok_type, token, (start_row, start_col), _, _) in tokens: + if tok_type == tokenize.STRING: + # 'token' is the whole un-parsed token; we can look at the start + # of it to see whether it's a raw or unicode string etc. + self.process_string_token(token, start_row, start_col) + + def process_string_token(self, token, start_row, start_col): + for i, c in enumerate(token): + if c in '\'\"': + quote_char = c + break + prefix = token[:i].lower() # markers like u, b, r. + after_prefix = token[i:] + if after_prefix[:3] == after_prefix[-3:] == 3 * quote_char: + string_body = after_prefix[3:-3] + else: + string_body = after_prefix[1:-1] # Chop off quotes + # No special checks on raw strings at the moment. + if 'r' not in prefix: + self.process_non_raw_string_token(prefix, string_body, + start_row, start_col) + + def process_non_raw_string_token(self, prefix, string_body, start_row, + start_col): + """check for bad escapes in a non-raw string. + + prefix: lowercase string of eg 'ur' string prefix markers. + string_body: the un-parsed body of the string, not including the quote + marks. + start_row: integer line number in the source. + start_col: integer column number in the source. + """ + # Walk through the string; if we see a backslash then escape the next + # character, and skip over it. If we see a non-escaped character, + # alert, and continue. + # + # Accept a backslash when it escapes a backslash, or a quote, or + # end-of-line, or one of the letters that introduce a special escape + # sequence + # + # TODO(mbp): Maybe give a separate warning about the rarely-used + # \a \b \v \f? + # + # TODO(mbp): We could give the column of the problem character, but + # add_message doesn't seem to have a way to pass it through at present. + i = 0 + while True: + i = string_body.find('\\', i) + if i == -1: + break + # There must be a next character; having a backslash at the end + # of the string would be a SyntaxError. + next_char = string_body[i+1] + match = string_body[i:i+2] + if next_char in self.UNICODE_ESCAPE_CHARACTERS: + if 'u' in prefix: + pass + elif _PY3K and 'b' not in prefix: + pass # unicode by default + else: + self.add_message('W1402', line=start_row, args=(match, )) + elif next_char not in self.ESCAPE_CHARACTERS: + self.add_message('W1401', line=start_row, args=(match, )) + # Whether it was a valid escape or not, backslash followed by + # another character can always be consumed whole: the second + # character can never be the start of a new backslash escape. + i += 2 + + + def register(linter): """required method to auto register this checker """ linter.register_checker(StringFormatChecker(linter)) + linter.register_checker(StringMethodsChecker(linter)) + linter.register_checker(StringConstantChecker(linter)) diff --git a/pylibs/pylama/pylint/checkers/typecheck.py b/pylibs/pylama/pylint/checkers/typecheck.py index c6309aea..4cdc6006 100644 --- a/pylibs/pylama/pylint/checkers/typecheck.py +++ b/pylibs/pylama/pylint/checkers/typecheck.py @@ -63,7 +63,7 @@ 'unexpected-keyword-arg', 'Used when a function call passes a keyword argument that \ doesn\'t correspond to one of the function\'s parameter names.'), - 'E1124': ('Multiple values passed for parameter %r in function call', + 'E1124': ('Parameter %r passed as both positional and keyword argument', 'redundant-keyword-arg', 'Used when a function call would result in assigning multiple \ values to a function parameter, one value from a positional \ diff --git a/pylibs/pylama/pylint/checkers/utils.py b/pylibs/pylama/pylint/checkers/utils.py index 193f6cd9..34d335bb 100644 --- a/pylibs/pylama/pylint/checkers/utils.py +++ b/pylibs/pylama/pylint/checkers/utils.py @@ -20,9 +20,11 @@ import re import string + from ..logilab import astng from ..logilab.astng import scoped_nodes from ..logilab.common.compat import builtins + BUILTINS_NAME = builtins.__name__ COMP_NODE_TYPES = astng.ListComp, astng.SetComp, astng.DictComp, astng.GenExpr @@ -365,6 +367,7 @@ def is_super_call(expr): return (isinstance(expr, astng.CallFunc) and isinstance(expr.func, astng.Name) and expr.func.name == 'super') + def is_attr_private(attrname): """Check that attribute name is private (at least two leading underscores, at most one trailing underscore) diff --git a/pylibs/pylama/pylint/lint.py b/pylibs/pylama/pylint/lint.py index e1cc8b6f..60a9784b 100644 --- a/pylibs/pylama/pylint/lint.py +++ b/pylibs/pylama/pylint/lint.py @@ -1,5 +1,4 @@ -# Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -48,7 +47,8 @@ from .logilab.astng.__pkginfo__ import version as astng_version from .utils import (PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, - ReportsHandlerMixIn, MSG_TYPES, expand_modules) + ReportsHandlerMixIn, MSG_TYPES, expand_modules, + WarningScope) from .interfaces import ILinter, IRawChecker, IASTNGChecker from .checkers import (BaseRawChecker, EmptyReport, table_lines_from_stats) @@ -508,11 +508,16 @@ def collect_block_lines(self, node, msg_state): for lineno, state in lines.items(): original_lineno = lineno if first <= lineno <= last: - if lineno > firstchildlineno: - state = True - # set state for all lines for this block - first, last = node.block_range(lineno) - for line in xrange(first, last+1): + # Set state for all lines for this block, if the + # warning is applied to nodes. + if self._messages[msgid].scope == WarningScope.NODE: + if lineno > firstchildlineno: + state = True + first_, last_ = node.block_range(lineno) + else: + first_ = lineno + last_ = last + for line in xrange(first_, last_+1): # do not override existing entries if not line in self._module_msgs_state.get(msgid, ()): if line in lines: # state change in the same block @@ -624,7 +629,6 @@ def set_current_module(self, modname, filepath=None): # messages which are only detected in the .close step) if modname: self._module_msgs_state = {} - self._module_msg_cats_state = {} self._raw_module_msgs_state = {} self._ignored_msgs = {} @@ -707,8 +711,8 @@ def _add_suppression_messages(self): if not enable and (warning, line) not in self._ignored_msgs: self.add_message('I0021', line, None, (self.get_msg_display_string(warning),)) - - for (warning, from_), lines in self._ignored_msgs.iteritems(): + # don't use iteritems here, _ignored_msgs may be modified by add_message + for (warning, from_), lines in self._ignored_msgs.items(): for line in lines: self.add_message('I0020', line, None, (self.get_msg_display_string(warning), from_)) @@ -856,8 +860,8 @@ def __init__(self, args, reporter=None, exit=True): 'rcfile': (self.cb_set_rcfile, True), 'load-plugins': (self.cb_add_plugins, True), }) - except ArgumentPreprocessingError, e: - print >> sys.stderr, 'Argument %s expects a value.' % (e.args[0],) + except ArgumentPreprocessingError, ex: + print >> sys.stderr, 'Argument %s expects a value.' % (ex.args[0],) sys.exit(32) self.linter = linter = self.LinterClass(( diff --git a/pylibs/pylama/pylint/logilab/astng/__init__.py b/pylibs/pylama/pylint/logilab/astng/__init__.py index 6c4a87f1..555838e3 100644 --- a/pylibs/pylama/pylint/logilab/astng/__init__.py +++ b/pylibs/pylama/pylint/logilab/astng/__init__.py @@ -1,7 +1,5 @@ -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -44,10 +42,6 @@ __doctype__ = "restructuredtext en" import sys -if sys.version_info >= (3, 0): - BUILTINS_MODULE = 'builtins' -else: - BUILTINS_MODULE = '__builtin__' # WARNING: internal imports order matters ! diff --git a/pylibs/pylama/pylint/logilab/astng/__pkginfo__.py b/pylibs/pylama/pylint/logilab/astng/__pkginfo__.py index f5097f48..31de45d0 100644 --- a/pylibs/pylama/pylint/logilab/astng/__pkginfo__.py +++ b/pylibs/pylama/pylint/logilab/astng/__pkginfo__.py @@ -1,7 +1,5 @@ -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -24,7 +22,7 @@ modname = 'astng' subpackage_of = 'logilab' -numversion = (0, 24, 2) +numversion = (0, 24, 3) version = '.'.join([str(num) for num in numversion]) install_requires = ['logilab-common >= 0.53.0'] diff --git a/pylibs/pylama/pylint/logilab/astng/as_string.py b/pylibs/pylama/pylint/logilab/astng/as_string.py index 0a42668d..c21144e5 100644 --- a/pylibs/pylama/pylint/logilab/astng/as_string.py +++ b/pylibs/pylama/pylint/logilab/astng/as_string.py @@ -1,7 +1,5 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -17,29 +15,63 @@ # # You should have received a copy of the GNU Lesser General Public License along # with logilab-astng. If not, see . -"""This module renders ASTNG nodes to string representation. +"""This module renders ASTNG nodes as string: -It will probably not work on bare _ast trees. +* :func:`to_code` function return equivalent (hopefuly valid) python string + +* :func:`dump` function return an internal representation of nodes found + in the tree, useful for debugging or understanding the tree structure """ -import sys +import sys INDENT = ' ' # 4 spaces ; keep indentation variable -def _import_string(names): - """return a list of (name, asname) formatted as a string""" - _names = [] - for name, asname in names: - if asname is not None: - _names.append('%s as %s' % (name, asname)) +def dump(node, ids=False): + """print a nice astng tree representation. + + :param ids: if true, we also print the ids (usefull for debugging) + """ + result = [] + _repr_tree(node, result, ids=ids) + return "\n".join(result) + +def _repr_tree(node, result, indent='', _done=None, ids=False): + """built a tree representation of a node as a list of lines""" + if _done is None: + _done = set() + if not hasattr(node, '_astng_fields'): # not a astng node + return + if node in _done: + result.append( indent + 'loop in tree: %s' % node ) + return + _done.add(node) + node_str = str(node) + if ids: + node_str += ' . \t%x' % id(node) + result.append( indent + node_str ) + indent += INDENT + for field in node._astng_fields: + value = getattr(node, field) + if isinstance(value, (list, tuple) ): + result.append( indent + field + " = [" ) + for child in value: + if isinstance(child, (list, tuple) ): + # special case for Dict # FIXME + _repr_tree(child[0], result, indent, _done, ids) + _repr_tree(child[1], result, indent, _done, ids) + result.append(indent + ',') + else: + _repr_tree(child, result, indent, _done, ids) + result.append( indent + "]" ) else: - _names.append(name) - return ', '.join(_names) + result.append( indent + field + " = " ) + _repr_tree(value, result, indent, _done, ids) class AsStringVisitor(object): - """Visitor to render an ASTNG node as string """ + """Visitor to render an ASTNG node as a valid python code string""" def __call__(self, node): """Makes this visitor behave as a simple function""" @@ -385,7 +417,11 @@ def visit_with(self, node): # 'with' without 'as' is possible def visit_yield(self, node): """yield an ast.Yield node as string""" yi_val = node.value and (" " + node.value.accept(self)) or "" - return 'yield' + yi_val + expr = 'yield' + yi_val + if node.parent.is_statement: + return expr + else: + return "(%s)" % (expr,) class AsStringVisitor3k(AsStringVisitor): @@ -419,9 +455,21 @@ def visit_starred(self, node): """return Starred node as string""" return "*" + node.value.accept(self) + +def _import_string(names): + """return a list of (name, asname) formatted as a string""" + _names = [] + for name, asname in names: + if asname is not None: + _names.append('%s as %s' % (name, asname)) + else: + _names.append(name) + return ', '.join(_names) + + if sys.version_info >= (3, 0): AsStringVisitor = AsStringVisitor3k # this visitor is stateless, thus it can be reused -as_string = AsStringVisitor() +to_code = AsStringVisitor() diff --git a/pylibs/pylama/pylint/logilab/astng/bases.py b/pylibs/pylama/pylint/logilab/astng/bases.py index 42d34438..6331dc8e 100644 --- a/pylibs/pylama/pylint/logilab/astng/bases.py +++ b/pylibs/pylama/pylint/logilab/astng/bases.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -24,20 +21,23 @@ __docformat__ = "restructuredtext en" +import sys from contextlib import contextmanager -from ..common.compat import builtins +from .exceptions import (InferenceError, ASTNGError, + NotFoundError, UnresolvableName) -from . import BUILTINS_MODULE -from .exceptions import InferenceError, ASTNGError, \ - NotFoundError, UnresolvableName -from .as_string import as_string -BUILTINS_NAME = builtins.__name__ +if sys.version_info >= (3, 0): + BUILTINS = 'builtins' +else: + BUILTINS = '__builtin__' + class Proxy(object): """a simple proxy object""" - _proxied = None + + _proxied = None # proxied object may be set by class or by instance def __init__(self, proxied=None): if proxied is not None: @@ -188,7 +188,7 @@ def _wrap_attr(self, attrs, context=None): """wrap bound methods of attrs in a InstanceMethod proxies""" for attr in attrs: if isinstance(attr, UnboundMethod): - if BUILTINS_NAME + '.property' in attr.decoratornames(): + if BUILTINS + '.property' in attr.decoratornames(): for infered in attr.infer_call_result(self, context): yield infered else: @@ -253,7 +253,7 @@ def infer_call_result(self, caller, context): # If we're unbound method __new__ of builtin object, the result is an # instance of the class given as first argument. if (self._proxied.name == '__new__' and - self._proxied.parent.frame().qname() == '%s.object' % BUILTINS_MODULE): + self._proxied.parent.frame().qname() == '%s.object' % BUILTINS): return (x is YES and x or Instance(x) for x in caller.args[0].infer()) return self._proxied.infer_call_result(caller, context) @@ -274,12 +274,15 @@ def infer_call_result(self, caller, context): class Generator(Instance): - """a special node representing a generator""" + """a special node representing a generator. + + Proxied class is set once for all in raw_building. + """ def callable(self): - return True + return False def pytype(self): - return '%s.generator' % BUILTINS_MODULE + return '%s.generator' % BUILTINS def display_type(self): return 'Generator' @@ -563,15 +566,12 @@ def eq(self, value): return False def as_string(self): - return as_string(self) + from .as_string import to_code + return to_code(self) def repr_tree(self, ids=False): - """print a nice astng tree representation. - - :param ids: if true, we also print the ids (usefull for debugging)""" - result = [] - _repr_tree(self, result, ids=ids) - return "\n".join(result) + from .as_string import dump + return dump(self) class Statement(NodeNG): @@ -593,39 +593,3 @@ def previous_sibling(self): index = stmts.index(self) if index >= 1: return stmts[index -1] - -INDENT = " " - -def _repr_tree(node, result, indent='', _done=None, ids=False): - """built a tree representation of a node as a list of lines""" - if _done is None: - _done = set() - if not hasattr(node, '_astng_fields'): # not a astng node - return - if node in _done: - result.append( indent + 'loop in tree: %s' % node ) - return - _done.add(node) - node_str = str(node) - if ids: - node_str += ' . \t%x' % id(node) - result.append( indent + node_str ) - indent += INDENT - for field in node._astng_fields: - value = getattr(node, field) - if isinstance(value, (list, tuple) ): - result.append( indent + field + " = [" ) - for child in value: - if isinstance(child, (list, tuple) ): - # special case for Dict # FIXME - _repr_tree(child[0], result, indent, _done, ids) - _repr_tree(child[1], result, indent, _done, ids) - result.append(indent + ',') - else: - _repr_tree(child, result, indent, _done, ids) - result.append( indent + "]" ) - else: - result.append( indent + field + " = " ) - _repr_tree(value, result, indent, _done, ids) - - diff --git a/pylibs/pylama/pylint/logilab/astng/brain/py2stdlib.py b/pylibs/pylama/pylint/logilab/astng/brain/py2stdlib.py index fb3ccdc1..d7b19532 100644 --- a/pylibs/pylama/pylint/logilab/astng/brain/py2stdlib.py +++ b/pylibs/pylama/pylint/logilab/astng/brain/py2stdlib.py @@ -15,11 +15,17 @@ def hashlib_transform(module): class md5(object): def __init__(self, value): pass + def digest(): + return u'' + def update(self, value): pass def hexdigest(self): return u'' class sha1(object): def __init__(self, value): pass + def digest(): + return u'' + def update(self, value): pass def hexdigest(self): return u'' @@ -99,7 +105,7 @@ def cleanup_resources(force=False): def urlparse_transform(module): fake = ASTNGBuilder(MANAGER).string_build(''' -def urlparse(urlstring, default_scheme='', allow_fragments=True): +def urlparse(url, scheme='', allow_fragments=True): return ParseResult() class ParseResult(object): diff --git a/pylibs/pylama/pylint/logilab/astng/builder.py b/pylibs/pylama/pylint/logilab/astng/builder.py index 190d5886..ae0c7d01 100644 --- a/pylibs/pylama/pylint/logilab/astng/builder.py +++ b/pylibs/pylama/pylint/logilab/astng/builder.py @@ -1,7 +1,5 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -25,8 +23,8 @@ __docformat__ = "restructuredtext en" -import sys, re -from os.path import splitext, basename, dirname, exists, abspath +import sys +from os.path import splitext, basename, exists, abspath from ..common.modutils import modpath_from_file @@ -88,6 +86,7 @@ class ASTNGBuilder(InspectBuilder): rebuilder = TreeRebuilder() def __init__(self, manager=None): + InspectBuilder.__init__(self) self._manager = manager or MANAGER def module_build(self, module, modname=None): diff --git a/pylibs/pylama/pylint/logilab/astng/exceptions.py b/pylibs/pylama/pylint/logilab/astng/exceptions.py index 7dd6135e..db33f8b9 100644 --- a/pylibs/pylama/pylint/logilab/astng/exceptions.py +++ b/pylibs/pylama/pylint/logilab/astng/exceptions.py @@ -1,19 +1,5 @@ -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # diff --git a/pylibs/pylama/pylint/logilab/astng/inference.py b/pylibs/pylama/pylint/logilab/astng/inference.py index faf8bfb2..fda25cf8 100644 --- a/pylibs/pylama/pylint/logilab/astng/inference.py +++ b/pylibs/pylama/pylint/logilab/astng/inference.py @@ -1,7 +1,5 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -23,14 +21,13 @@ __doctype__ = "restructuredtext en" from itertools import chain -import sys from . import nodes from .manager import ASTNGManager -from .exceptions import (ASTNGBuildingException, ASTNGError, +from .exceptions import (ASTNGError, InferenceError, NoDefault, NotFoundError, UnresolvableName) -from .bases import YES, Instance, InferenceContext, Generator, \ +from .bases import YES, Instance, InferenceContext, \ _infer_stmts, copy_context, path_wrapper, raise_if_nothing_infered from .protocols import _arguments_infer_argname @@ -137,7 +134,7 @@ def infer_end(self, context=None): nodes.List.infer = infer_end nodes.Tuple.infer = infer_end nodes.Dict.infer = infer_end - +nodes.Set.infer = infer_end def infer_name(self, context=None): """infer a Name: use name lookup rules""" @@ -238,15 +235,19 @@ def infer_global(self, context=None): def infer_subscript(self, context=None): """infer simple subscription such as [1,2,3][0] or (1,2,3)[-1]""" - if isinstance(self.slice, nodes.Index): - index = self.slice.value.infer(context).next() - if index is YES: - yield YES - return + value = self.value.infer(context).next() + if value is YES: + yield YES + return + + index = self.slice.infer(context).next() + if index is YES: + yield YES + return + + if isinstance(index, nodes.Const): try: - # suppose it's a Tuple/List node (attribute error else) - # XXX infer self.value? - assigned = self.value.getitem(index.value, context) + assigned = value.getitem(index.value, context) except AttributeError: raise InferenceError() except (IndexError, TypeError): @@ -381,3 +382,7 @@ def infer_empty_node(self, context=None): yield YES nodes.EmptyNode.infer = path_wrapper(infer_empty_node) + +def infer_index(self, context=None): + return self.value.infer(context) +nodes.Index.infer = infer_index diff --git a/pylibs/pylama/pylint/logilab/astng/manager.py b/pylibs/pylama/pylint/logilab/astng/manager.py index 513ad708..d090f80b 100644 --- a/pylibs/pylama/pylint/logilab/astng/manager.py +++ b/pylibs/pylama/pylint/logilab/astng/manager.py @@ -1,7 +1,5 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -24,9 +22,8 @@ __docformat__ = "restructuredtext en" -import sys import os -from os.path import dirname, basename, abspath, join, isdir, exists +from os.path import dirname, join, isdir, exists from ..common.modutils import NoSourceFile, is_python_source, \ file_from_modpath, load_module_from_name, modpath_from_file, \ diff --git a/pylibs/pylama/pylint/logilab/astng/mixins.py b/pylibs/pylama/pylint/logilab/astng/mixins.py index 207dc70b..5d4a865c 100644 --- a/pylibs/pylama/pylint/logilab/astng/mixins.py +++ b/pylibs/pylama/pylint/logilab/astng/mixins.py @@ -1,19 +1,5 @@ -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -55,6 +41,7 @@ def _elsed_block_range(self, lineno, orelse, last=None): return lineno, orelse[0].fromlineno - 1 return lineno, last or self.tolineno + class FilterStmtsMixin(object): """Mixin for statement filtering and assignment type""" @@ -92,7 +79,6 @@ def ass_type(self): return self.parent.ass_type() - class FromImportMixIn(FilterStmtsMixin): """MixIn for From and Import Nodes""" diff --git a/pylibs/pylama/pylint/logilab/astng/node_classes.py b/pylibs/pylama/pylint/logilab/astng/node_classes.py index ef8c5e18..a7976774 100644 --- a/pylibs/pylama/pylint/logilab/astng/node_classes.py +++ b/pylibs/pylama/pylint/logilab/astng/node_classes.py @@ -1,7 +1,5 @@ -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -22,10 +20,9 @@ import sys -from . import BUILTINS_MODULE from .exceptions import NoDefault from .bases import (NodeNG, Statement, Instance, InferenceContext, - _infer_stmts, YES) + _infer_stmts, YES, BUILTINS) from .mixins import BlockRangeMixIn, AssignTypeMixin, \ ParentAssignTypeMixin, FromImportMixIn @@ -495,7 +492,7 @@ def __init__(self, items=None): for k,v in items.iteritems()] def pytype(self): - return '%s.dict' % BUILTINS_MODULE + return '%s.dict' % BUILTINS def get_children(self): """get children of a Dict node""" @@ -513,14 +510,16 @@ def last_child(self): def itered(self): return self.items[::2] - def getitem(self, key, context=None): - for i in xrange(0, len(self.items), 2): - for inferedkey in self.items[i].infer(context): + def getitem(self, lookup_key, context=None): + for key, value in self.items: + for inferedkey in key.infer(context): if inferedkey is YES: continue - if isinstance(inferedkey, Const) and inferedkey.value == key: - return self.items[i+1] - raise IndexError(key) + if isinstance(inferedkey, Const) and inferedkey.value == lookup_key: + return value + # This should raise KeyError, but all call sites only catch + # IndexError. Let's leave it like that for now. + raise IndexError(lookup_key) class Discard(Statement): @@ -670,7 +669,7 @@ def __init__(self, elts=None): self.elts = [const_factory(e) for e in elts] def pytype(self): - return '%s.list' % BUILTINS_MODULE + return '%s.list' % BUILTINS def getitem(self, index, context=None): return self.elts[index] @@ -737,7 +736,7 @@ def __init__(self, elts=None): self.elts = [const_factory(e) for e in elts] def pytype(self): - return '%s.set' % BUILTINS_MODULE + return '%s.set' % BUILTINS def itered(self): return self.elts @@ -819,7 +818,7 @@ def __init__(self, elts=None): self.elts = [const_factory(e) for e in elts] def pytype(self): - return '%s.tuple' % BUILTINS_MODULE + return '%s.tuple' % BUILTINS def getitem(self, index, context=None): return self.elts[index] @@ -891,18 +890,15 @@ def _update_const_classes(): def const_factory(value): """return an astng node for a python value""" - # since const_factory is called to evaluate content of container (eg list, - # tuple), it may be called with some node as argument that should be left - # untouched - if isinstance(value, NodeNG): - return value + # XXX we should probably be stricter here and only consider stuff in + # CONST_CLS or do better treatment: in case where value is not in CONST_CLS, + # we should rather recall the builder on this value than returning an empty + # node (another option being that const_factory shouldn't be called with something + # not in CONST_CLS) + assert not isinstance(value, NodeNG) try: return CONST_CLS[value.__class__](value) except (KeyError, AttributeError): - # some constants (like from gtk._gtk) don't have their class in - # CONST_CLS, though we can "assert isinstance(value, tuple(CONST_CLS))" - if isinstance(value, tuple(CONST_CLS)): - return Const(value) node = EmptyNode() node.object = value return node diff --git a/pylibs/pylama/pylint/logilab/astng/nodes.py b/pylibs/pylama/pylint/logilab/astng/nodes.py index 39ab0412..b475b902 100644 --- a/pylibs/pylama/pylint/logilab/astng/nodes.py +++ b/pylibs/pylama/pylint/logilab/astng/nodes.py @@ -1,7 +1,5 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # diff --git a/pylibs/pylama/pylint/logilab/astng/protocols.py b/pylibs/pylama/pylint/logilab/astng/protocols.py index 00d6be81..05feb246 100644 --- a/pylibs/pylama/pylint/logilab/astng/protocols.py +++ b/pylibs/pylama/pylint/logilab/astng/protocols.py @@ -1,7 +1,5 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -26,7 +24,7 @@ from .exceptions import InferenceError, NoDefault from .node_classes import unpack_infer from .bases import copy_context, \ - raise_if_nothing_infered, yes_if_nothing_infered, Instance, Generator, YES + raise_if_nothing_infered, yes_if_nothing_infered, Instance, YES from .nodes import const_factory from . import nodes diff --git a/pylibs/pylama/pylint/logilab/astng/raw_building.py b/pylibs/pylama/pylint/logilab/astng/raw_building.py index 5f308cae..076f28f3 100644 --- a/pylibs/pylama/pylint/logilab/astng/raw_building.py +++ b/pylibs/pylama/pylint/logilab/astng/raw_building.py @@ -1,7 +1,5 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -28,11 +26,10 @@ from inspect import (getargspec, isdatadescriptor, isfunction, ismethod, ismethoddescriptor, isclass, isbuiltin) -from . import BUILTINS_MODULE from .node_classes import CONST_CLS from .nodes import (Module, Class, Const, const_factory, From, - Function, EmptyNode, Name, Arguments, Dict, List, Set, Tuple) -from .bases import Generator + Function, EmptyNode, Name, Arguments) +from .bases import BUILTINS, Generator from .manager import ASTNGManager MANAGER = ASTNGManager() @@ -277,7 +274,7 @@ def object_build(self, node, obj): elif isdatadescriptor(member): assert isinstance(member, object) object_build_datadescriptor(node, member, name) - elif isinstance(member, _CONSTANTS): + elif type(member) in _CONSTANTS: attach_const_node(node, name, member) else: # create an empty node so that the name is actually defined @@ -301,7 +298,7 @@ def imported_member(self, node, member, name): # Python 2.5.1 (r251:54863, Sep 1 2010, 22:03:14) # >>> print object.__new__.__module__ # None - modname = BUILTINS_MODULE + modname = BUILTINS else: attach_dummy_node(node, name, member) return True @@ -319,21 +316,21 @@ def imported_member(self, node, member, name): ### astng boot strapping ################################################### ### +ASTNG_BUILDER = InspectBuilder() _CONST_PROXY = {} def astng_boot_strapping(): """astng boot strapping the builtins module""" # this boot strapping is necessary since we need the Const nodes to # inspect_build builtins, and then we can proxy Const - builder = InspectBuilder() from ..common.compat import builtins - astng_builtin = builder.inspect_build(builtins) + astng_builtin = ASTNG_BUILDER.inspect_build(builtins) for cls, node_cls in CONST_CLS.items(): if cls is type(None): proxy = build_class('NoneType') proxy.parent = astng_builtin else: - proxy = astng_builtin.getattr(cls.__name__)[0] # XXX + proxy = astng_builtin.getattr(cls.__name__)[0] if cls in (dict, list, set, tuple): node_cls._proxied = proxy else: @@ -348,6 +345,7 @@ def _set_proxied(const): return _CONST_PROXY[const.value.__class__] Const._proxied = property(_set_proxied) -# FIXME : is it alright that Generator._proxied is not a astng node? -Generator._proxied = MANAGER.infer_astng_from_something(type(a for a in ())) +from types import GeneratorType +Generator._proxied = Class(GeneratorType.__name__, GeneratorType.__doc__) +ASTNG_BUILDER.object_build(Generator._proxied, GeneratorType) diff --git a/pylibs/pylama/pylint/logilab/astng/rebuilder.py b/pylibs/pylama/pylint/logilab/astng/rebuilder.py index f007bc90..03ee6128 100644 --- a/pylibs/pylama/pylint/logilab/astng/rebuilder.py +++ b/pylibs/pylama/pylint/logilab/astng/rebuilder.py @@ -32,7 +32,6 @@ Eq, Gt, GtE, In, Is, IsNot, Lt, LtE, NotEq, NotIn, ) -from .exceptions import ASTNGBuildingException from . import nodes as new @@ -698,7 +697,7 @@ def visit_return(self, node, parent): return newnode def visit_set(self, node, parent): - """visit a Tuple node by returning a fresh instance of it""" + """visit a Set node by returning a fresh instance of it""" newnode = new.Set() _lineno_parent(node, newnode, parent) newnode.elts = [self.visit(child, newnode) for child in node.elts] @@ -879,6 +878,8 @@ def visit_try(self, node, parent): newnode.set_line_info(newnode.last_child()) return newnode + def visit_yieldfrom(self, node, parent): + return self.visit_yield(node, parent) if sys.version_info >= (3, 0): TreeRebuilder = TreeRebuilder3k diff --git a/pylibs/pylama/pylint/logilab/astng/scoped_nodes.py b/pylibs/pylama/pylint/logilab/astng/scoped_nodes.py index 5f61511b..8e049775 100644 --- a/pylibs/pylama/pylint/logilab/astng/scoped_nodes.py +++ b/pylibs/pylama/pylint/logilab/astng/scoped_nodes.py @@ -1,7 +1,5 @@ -# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -31,15 +29,14 @@ from ..common.compat import builtins from ..common.decorators import cached -from . import BUILTINS_MODULE -from .exceptions import NotFoundError, NoDefault, \ +from .exceptions import NotFoundError, \ ASTNGBuildingException, InferenceError from .node_classes import Const, DelName, DelAttr, \ - Dict, From, List, Name, Pass, Raise, Return, Tuple, Yield, \ - are_exclusive, LookupMixIn, const_factory as cf, unpack_infer + Dict, From, List, Pass, Raise, Return, Tuple, Yield, \ + LookupMixIn, const_factory as cf, unpack_infer from .bases import NodeNG, InferenceContext, Instance,\ YES, Generator, UnboundMethod, BoundMethod, _infer_stmts, copy_context, \ - BUILTINS_NAME + BUILTINS from .mixins import FilterStmtsMixin from .bases import Statement from .manager import ASTNGManager @@ -268,7 +265,7 @@ def scope_lookup(self, node, name, offset=0): return self._scope_lookup(node, name, offset) def pytype(self): - return '%s.module' % BUILTINS_MODULE + return '%s.module' % BUILTINS def display_type(self): return 'Module' @@ -481,8 +478,8 @@ def __init__(self): def pytype(self): if 'method' in self.type: - return '%s.instancemethod' % BUILTINS_MODULE - return '%s.function' % BUILTINS_MODULE + return '%s.instancemethod' % BUILTINS + return '%s.function' % BUILTINS def display_type(self): if 'method' in self.type: @@ -608,14 +605,14 @@ def is_generator(self): """return true if this is a generator function""" # XXX should be flagged, not computed try: - return self.nodes_of_class(Yield, skip_klass=Function).next() + return self.nodes_of_class(Yield, skip_klass=(Function, Lambda)).next() except StopIteration: return False def infer_call_result(self, caller, context=None): """infer what a function is returning when called""" if self.is_generator(): - yield Generator(self) + yield Generator() return returns = self.nodes_of_class(Return, skip_klass=Function) for returnnode in returns: @@ -740,8 +737,8 @@ def block_range(self, lineno): def pytype(self): if self.newstyle: - return '%s.type' % BUILTINS_MODULE - return '%s.classobj' % BUILTINS_MODULE + return '%s.type' % BUILTINS + return '%s.classobj' % BUILTINS def display_type(self): return 'Class' @@ -872,15 +869,14 @@ def getattr(self, name, context=None): if name in self.special_attributes: if name == '__module__': return [cf(self.root().qname())] + values - # FIXME : what is expected by passing the list of ancestors to cf: - # you can just do [cf(tuple())] + values without breaking any test + # FIXME: do we really need the actual list of ancestors? + # returning [Tuple()] + values don't break any test # this is ticket http://www.logilab.org/ticket/52785 - if name == '__bases__': - return [cf(tuple(self.ancestors(recurs=False, context=context)))] + values # XXX need proper meta class handling + MRO implementation - if name == '__mro__' and self.newstyle: - # XXX mro is read-only but that's not our job to detect that - return [cf(tuple(self.ancestors(recurs=True, context=context)))] + values + if name == '__bases__' or (name == '__mro__' and self.newstyle): + node = Tuple() + node.items = self.ancestors(recurs=True, context=context) + return [node] + values return std_special_attributes(self, name) # don't modify the list in self.locals! values = list(values) @@ -932,7 +928,7 @@ def has_dynamic_getattr(self, context=None): #if self.newstyle: XXX cause an infinite recursion error try: getattribute = self.getattr('__getattribute__', context)[0] - if getattribute.root().name != BUILTINS_NAME: + if getattribute.root().name != BUILTINS: # class has a custom __getattribute__ defined return True except NotFoundError: diff --git a/pylibs/pylama/pylint/logilab/astng/utils.py b/pylibs/pylama/pylint/logilab/astng/utils.py index 73203299..325535b0 100644 --- a/pylibs/pylama/pylint/logilab/astng/utils.py +++ b/pylibs/pylama/pylint/logilab/astng/utils.py @@ -1,7 +1,5 @@ -# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# copyright 2003-2010 Sylvain Thenault, all rights reserved. -# contact mailto:thenault@gmail.com # # This file is part of logilab-astng. # @@ -24,6 +22,7 @@ __docformat__ = "restructuredtext en" from .exceptions import ASTNGBuildingException +from .builder import parse class ASTWalker: @@ -128,10 +127,6 @@ def _check_children(node): _check_children(child) -from _ast import PyCF_ONLY_AST -def parse(string): - return compile(string, "", 'exec', PyCF_ONLY_AST) - class TreeTester(object): '''A helper class to see _ast tree and compare with astng tree diff --git a/pylibs/pylama/pylint/reporters/__init__.py b/pylibs/pylama/pylint/reporters/__init__.py index 99cf2b7c..fa09c1ad 100644 --- a/pylibs/pylama/pylint/reporters/__init__.py +++ b/pylibs/pylama/pylint/reporters/__init__.py @@ -82,7 +82,10 @@ def encode(string): encoding = (getattr(self.out, 'encoding', None) or locale.getdefaultlocale()[1] or sys.getdefaultencoding()) - return string.encode(encoding) + # errors=replace, we don't want to crash when attempting to show + # source code line that can't be encoded with the current locale + # settings + return string.encode(encoding, 'replace') self.encode = encode def writeln(self, string=''): diff --git a/pylibs/pylama/pylint/reporters/text.py b/pylibs/pylama/pylint/reporters/text.py index 5b94dd69..c498e4ca 100644 --- a/pylibs/pylama/pylint/reporters/text.py +++ b/pylibs/pylama/pylint/reporters/text.py @@ -1,5 +1,4 @@ -# Copyright (c) 2003-2007 Sylvain Thenault (thenault@gmail.com). -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # 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 @@ -23,7 +22,6 @@ """ import os -import sys from ..logilab.common.ureports import TextWriter from ..logilab.common.textutils import colorize_ansi diff --git a/pylibs/pylama/pylint/utils.py b/pylibs/pylama/pylint/utils.py index 532b686f..8cb79ed5 100644 --- a/pylibs/pylama/pylint/utils.py +++ b/pylibs/pylama/pylint/utils.py @@ -1,5 +1,4 @@ -# Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -22,8 +21,9 @@ from warnings import warn from os.path import dirname, basename, splitext, exists, isdir, join, normpath +from .logilab.common.interface import implements from .logilab.common.modutils import modpath_from_file, get_module_files, \ - file_from_modpath + file_from_modpath from .logilab.common.textutils import normalize_text from .logilab.common.configuration import rest_format_section from .logilab.common.ureports import Section @@ -31,6 +31,7 @@ from .logilab.astng import nodes, Module from .checkers import EmptyReport +from .interfaces import IRawChecker class UnknownMessage(Exception): @@ -60,6 +61,15 @@ class UnknownMessage(Exception): MSG_STATE_SCOPE_CONFIG = 0 MSG_STATE_SCOPE_MODULE = 1 + +# The line/node distinction does not apply to fatal errors and reports. +_SCOPE_EXEMPT = 'FR' + +class WarningScope(object): + LINE = 'line-based-msg' + NODE = 'node-based-msg' + + def sort_msgs(msgids): """sort message identifiers according to their category first""" msgs = {} @@ -95,7 +105,7 @@ def category_id(id): class Message: - def __init__(self, checker, msgid, msg, descr, symbol): + def __init__(self, checker, msgid, msg, descr, symbol, scope): assert len(msgid) == 5, 'Invalid message id %s' % msgid assert msgid[0] in MSG_TYPES, \ 'Bad message type %s in %r' % (msgid[0], msgid) @@ -104,6 +114,7 @@ def __init__(self, checker, msgid, msg, descr, symbol): self.descr = descr self.checker = checker self.symbol = symbol + self.scope = scope class MessagesHandlerMixIn: """a mix-in class containing all the messages related methods for the main @@ -121,6 +132,7 @@ def __init__(self): self._msgs_by_category = {} self.msg_status = 0 self._ignored_msgs = {} + self._suppression_mapping = {} def register_messages(self, checker): """register a dictionary of messages @@ -133,11 +145,18 @@ def register_messages(self, checker): """ msgs_dict = checker.msgs chkid = None + for msgid, msg_tuple in msgs_dict.iteritems(): - if len(msg_tuple) == 3: - (msg, msgsymbol, msgdescr) = msg_tuple + if implements(checker, IRawChecker): + scope = WarningScope.LINE + else: + scope = WarningScope.NODE + if len(msg_tuple) > 2: + (msg, msgsymbol, msgdescr) = msg_tuple[:3] assert msgsymbol not in self._messages_by_symbol, \ 'Message symbol %r is already defined' % msgsymbol + if len(msg_tuple) > 3 and 'scope' in msg_tuple[3]: + scope = msg_tuple[3]['scope'] else: # messages should have a symbol, but for backward compatibility # they may not. @@ -151,7 +170,7 @@ def register_messages(self, checker): assert chkid is None or chkid == msgid[1:3], \ 'Inconsistent checker part in message id %r' % msgid chkid = msgid[1:3] - msg = Message(checker, msgid, msg, msgdescr, msgsymbol) + msg = Message(checker, msgid, msg, msgdescr, msgsymbol, scope) self._messages[msgid] = msg self._messages_by_symbol[msgsymbol] = msg self._msgs_by_category.setdefault(msgid[0], []).append(msgid) @@ -320,6 +339,17 @@ def add_message(self, msgid, line=None, node=None, args=None): astng checkers should provide the node argument, raw checkers should provide the line argument. """ + msg_info = self._messages[msgid] + # Fatal messages and reports are special, the node/scope distinction + # does not apply to them. + if msgid[0] not in _SCOPE_EXEMPT: + if msg_info.scope == WarningScope.LINE: + assert node is None and line is not None, ( + 'Message %s must only provide line, got line=%s, node=%s' % (msgid, line, node)) + elif msg_info.scope == WarningScope.NODE: + # Node-based warnings may provide an override line. + assert node is not None, 'Message %s must provide Node, got None' + if line is None and node is not None: line = node.fromlineno if hasattr(node, 'col_offset'): @@ -340,8 +370,8 @@ def add_message(self, msgid, line=None, node=None, args=None): self.stats['by_msg'][msgid] += 1 except KeyError: self.stats['by_msg'][msgid] = 1 - msg = self._messages[msgid].msg # expand message ? + msg = msg_info.msg if args: msg %= args # get module and object diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py index 1ab481fb..ec98e4e2 100644 --- a/pylibs/pylama/utils.py +++ b/pylibs/pylama/utils.py @@ -68,7 +68,7 @@ def pyflakes(path, code=None, **meta): def pylint(path, **meta): from sys import version_info - if version_info > (2, 8): + if version_info > (3, 0): import logging logging.warn("Pylint don't supported python3 and will be disabled.") return [] From 1c364b39c8e476bfe87b2dad2ac04c83fc801179 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 3 May 2013 18:30:47 +0800 Subject: [PATCH 187/513] Update autopep8 --- pylibs/autopep8.py | 521 +++++++++++++++++++++++++++++---------------- 1 file changed, 332 insertions(+), 189 deletions(-) diff --git a/pylibs/autopep8.py b/pylibs/autopep8.py index c024f21c..01d79ffe 100644 --- a/pylibs/autopep8.py +++ b/pylibs/autopep8.py @@ -22,7 +22,16 @@ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""Automatically formats Python code to conform to the PEP 8 style guide.""" +"""Automatically formats Python code to conform to the PEP 8 style guide. + +Fixes that only need be done once can be added by adding a function of the form +"fix_(source)" to this module. They should return the fixed source code. +These fixes are picked up by apply_global_fixes(). + +Fixes that depend on pep8 should be added as methods to FixPEP8. See the class +documentation for more information. + +""" from __future__ import print_function from __future__ import division @@ -33,6 +42,7 @@ import inspect import os import re +import signal import sys try: from StringIO import StringIO @@ -44,7 +54,7 @@ import difflib import tempfile -from pylama import pep8 +import pep8 try: @@ -61,6 +71,9 @@ CRLF = '\r\n' +PYTHON_SHEBANG_REGEX = re.compile(r'^#!.*\bpython[23]?\b') + + # For generating line shortening candidates. SHORTEN_OPERATOR_GROUPS = frozenset([ frozenset([',']), @@ -70,6 +83,10 @@ ]) +DEFAULT_IGNORE = ','.join([pep8.DEFAULT_IGNORE, + 'W6']) + + def open_with_encoding(filename, encoding=None, mode='r'): """Return opened file with a specific encoding.""" if not encoding: @@ -102,6 +119,23 @@ def read_from_filename(filename, readlines=False): return input_file.readlines() if readlines else input_file.read() +def extended_blank_lines(logical_line, + blank_lines, + indent_level, + previous_logical): + """Check for missing blank lines after class declaration.""" + if (previous_logical.startswith('class ')): + if (logical_line.startswith(('def ', 'class ', '@')) or + pep8.DOCSTRING_REGEX.match(logical_line)): + if indent_level: + if not blank_lines: + yield (0, 'E301 expected 1 blank line, found 0') + elif previous_logical.startswith('def '): + if blank_lines and pep8.DOCSTRING_REGEX.match(logical_line): + yield (0, 'E303 too many blank lines ({0})'.format(blank_lines)) +pep8.register_check(extended_blank_lines) + + class FixPEP8(object): """Fix invalid code. @@ -122,7 +156,7 @@ class FixPEP8(object): [fixed method list] - e111 - - e121,e122,e123,e124,e125,e126,e127,e128 + - e121,e122,e123,e124,e125,e126,e127,e128,e129 - e201,e202,e203 - e211 - e221,e222,e223,e224,e225 @@ -135,10 +169,8 @@ class FixPEP8(object): - e502 - e701,e702 - e711 - - e721 - w291,w293 - w391 - - w602,w603,w604 """ @@ -152,11 +184,11 @@ def __init__(self, filename, options, contents=None): self.newline = find_newline(self.source) self.options = options self.indent_word = _get_indentword(unicode().join(self.source)) - self.logical_start = None - self.logical_end = None + # method definition self.fix_e111 = self.fix_e101 self.fix_e128 = self.fix_e127 + self.fix_e129 = self.fix_e125 self.fix_e202 = self.fix_e201 self.fix_e203 = self.fix_e201 self.fix_e211 = self.fix_e201 @@ -253,10 +285,8 @@ def fix_e101(self, _): else: return [] - def find_logical(self, force=False): + def _find_logical(self): # make a variable which is the index of all the starts of lines - if not force and self.logical_start is not None: - return logical_start = [] logical_end = [] last_newline = True @@ -281,8 +311,7 @@ def find_logical(self, force=False): parens += 1 elif t[1] in '}])': parens -= 1 - self.logical_start = logical_start - self.logical_end = logical_end + return (logical_start, logical_end) def _get_logical(self, result): """Return the logical line corresponding to the result. @@ -291,7 +320,7 @@ def _get_logical(self, result): """ try: - self.find_logical() + (logical_start, logical_end) = self._find_logical() except (IndentationError, tokenize.TokenError): return None @@ -299,33 +328,30 @@ def _get_logical(self, result): col = result['column'] - 1 ls = None le = None - for i in range(0, len(self.logical_start), 1): - x = self.logical_end[i] + for i in range(0, len(logical_start), 1): + x = logical_end[i] if x[0] > row or (x[0] == row and x[1] > col): le = x - ls = self.logical_start[i] + ls = logical_start[i] break if ls is None: return None original = self.source[ls[0]:le[0] + 1] return ls, le, original - def _fix_reindent(self, result, logical, fix_distinct=False): + def _fix_reindent(self, result, logical): """Fix a badly indented line. This is done by adding or removing from its initial indent only. """ - if not logical: - return [] + assert logical ls, _, original = logical - try: - rewrapper = Wrapper(original) - except (tokenize.TokenError, IndentationError): - return [] + + rewrapper = Wrapper(original) valid_indents = rewrapper.pep8_expected() if not rewrapper.rel_indent: - return [] + return [] # pragma: no cover if result['line'] > ls[0]: # got a valid continuation line number from pep8 row = result['line'] - ls[0] - 1 @@ -333,22 +359,10 @@ def _fix_reindent(self, result, logical, fix_distinct=False): valid = valid_indents[row] got = rewrapper.rel_indent[row] else: - # Line number from pep8 isn't a continuation line. Instead, - # compare our own function's result, look for the first mismatch, - # and just hope that we take fewer than 100 iterations to finish. - for row in range(0, len(original), 1): - valid = valid_indents[row] - got = rewrapper.rel_indent[row] - if valid != got: - break + return [] # pragma: no cover line = ls[0] + row # always pick the expected indent, for now. indent_to = valid[0] - if fix_distinct and indent_to == 4: - if len(valid) == 1: - return [] - else: - indent_to = valid[1] if got != indent_to: orig_line = self.source[line] @@ -359,7 +373,7 @@ def _fix_reindent(self, result, logical, fix_distinct=False): self.source[line] = new_line return [line + 1] # Line indexed at 1 else: - return [] + return [] # pragma: no cover def fix_e121(self, result, logical): """Fix indentation to be a multiple of four.""" @@ -374,8 +388,7 @@ def fix_e122(self, result, logical): def fix_e123(self, result, logical): """Align closing bracket to match opening bracket.""" # Fix by deleting whitespace to the correct level. - if not logical: - return [] + assert logical logical_lines = logical[2] line_index = result['line'] - 1 original_line = self.source[line_index] @@ -396,7 +409,7 @@ def fix_e124(self, result, logical): def fix_e125(self, result, logical): """Indent to distinguish line from next logical line.""" # Fix by indenting the line in error to the next stop. - modified_lines = self._fix_reindent(result, logical, fix_distinct=True) + modified_lines = self._fix_reindent(result, logical) if modified_lines: return modified_lines else: @@ -408,8 +421,7 @@ def fix_e125(self, result, logical): def fix_e126(self, result, logical): """Fix over-indented hanging indentation.""" # fix by deleting whitespace to the left - if not logical: - return [] + assert logical logical_lines = logical[2] line_index = result['line'] - 1 original = self.source[line_index] @@ -418,7 +430,7 @@ def fix_e126(self, result, logical): self.indent_word + original.lstrip()) if fixed == original: # Fall back to slower method. - return self._fix_reindent(result, logical) + return self._fix_reindent(result, logical) # pragma: no cover else: self.source[line_index] = fixed @@ -438,8 +450,7 @@ def _align_visual_indent(self, result, logical): This includes over (E127) and under (E128) indented lines. """ - if not logical: - return [] + assert logical logical_lines = logical[2] line_index = result['line'] - 1 original = self.source[line_index] @@ -449,11 +460,17 @@ def _align_visual_indent(self, result, logical): fixed = (_get_indentation(logical_lines[0]) + self.indent_word + original.lstrip()) else: + start_index = None for symbol in '([{': if symbol in logical_lines[0]: - fixed = logical_lines[0].find( - symbol) * ' ' + original.lstrip() - break + found_index = logical_lines[0].find(symbol) + if start_index is None: + start_index = found_index + else: + start_index = min(start_index, found_index) + + if start_index is not None: + fixed = start_index * ' ' + original.lstrip() if fixed == original: return [] @@ -506,6 +523,15 @@ def fix_e225(self, result): def fix_e231(self, result): """Add missing whitespace.""" + # Optimize for comma case. This will fix all commas in the full source + # code in one pass. + if ',' in result['info']: + original = ''.join(self.source) + new = refactor(original, ['ws_comma']) + if original.strip() != new.strip(): + self.source = [new] + return range(1, 1 + len(original)) + line_index = result['line'] - 1 target = self.source[line_index] offset = result['column'] @@ -598,9 +624,7 @@ def fix_e303(self, result): cnt = 0 line = result['line'] - 2 modified_lines = [] - while cnt < delete_linenum: - if line < 0: - break + while cnt < delete_linenum and line >= 0: if not self.source[line].strip(): self.source[line] = '' modified_lines.append(1 + line) # Line indexed at 1 @@ -669,12 +693,12 @@ def fix_e501(self, result): try: tokens = list(tokenize.generate_tokens(sio.readline)) except (tokenize.TokenError, IndentationError): - multi_line_candidate = break_multi_line( + multiline_candidate = break_multiline( target, newline=self.newline, indent_word=self.indent_word) - if multi_line_candidate: - self.source[line_index] = multi_line_candidate + if multiline_candidate: + self.source[line_index] = multiline_candidate return else: return [] @@ -695,11 +719,7 @@ def fix_e501(self, result): file=sys.stderr) for _candidate in candidates: - if _candidate is None: - continue - - if _candidate == target: - continue + assert _candidate is not None if (get_longest_length(_candidate, self.newline) >= get_longest_length(target, self.newline)): @@ -808,10 +828,6 @@ def fix_e712(self, result): self.source[line_index] = left + new_right - def fix_e721(self, _): - """Switch to use isinstance().""" - return self.refactor('idioms') - def fix_w291(self, result): """Remove trailing whitespace.""" fixed_line = self.source[result['line'] - 1].rstrip() @@ -836,47 +852,91 @@ def fix_w391(self, _): self.source = self.source[:original_length - blank_count] return range(1, 1 + original_length) - def refactor(self, fixer_name, ignore=None): - """Return refactored code using lib2to3. - Skip if ignore string is produced in the refactored code. +def fix_e26(source): + """Format block comments.""" + if '#' not in source: + # Optimization. + return source - """ - from lib2to3 import pgen2 - try: - new_text = refactor_with_2to3(''.join(self.source), - fixer_name=fixer_name) - except (pgen2.parse.ParseError, - UnicodeDecodeError, UnicodeEncodeError): - return [] + string_line_numbers = multiline_string_lines(source, + include_docstrings=True) + fixed_lines = [] + sio = StringIO(source) + for (line_number, line) in enumerate(sio.readlines(), start=1): + if (line.lstrip().startswith('#') and + line_number not in string_line_numbers): - original = unicode().join(self.source).strip() - if original == new_text.strip(): - return [] + indentation = _get_indentation(line) + line = line.lstrip() + + # Normalize beginning if not a shebang. + if len(line) > 1: + # Leave multiple spaces like '# ' alone. + if line.count('#') > 1 or line[1].isalnum(): + line = '# ' + line.lstrip('# \t') + + fixed_lines.append(indentation + line) else: - if ignore: - if ignore in new_text and ignore not in ''.join(self.source): - return [] - original_length = len(self.source) - self.source = [new_text] - return range(1, 1 + original_length) + fixed_lines.append(line) + + return ''.join(fixed_lines) - def fix_w601(self, _): - """Replace the {}.has_key() form with 'in'.""" - return self.refactor('has_key') - def fix_w602(self, _): - """Fix deprecated form of raising exception.""" - return self.refactor('raise', - ignore='with_traceback') +def refactor(source, fixer_names, ignore=None): + """Return refactored code using lib2to3. - def fix_w603(self, _): - """Replace <> with !=.""" - return self.refactor('ne') + Skip if ignore string is produced in the refactored code. - def fix_w604(self, _): - """Replace backticks with repr().""" - return self.refactor('repr') + """ + from lib2to3 import pgen2 + try: + new_text = refactor_with_2to3(source, + fixer_names=fixer_names) + except (pgen2.parse.ParseError, + UnicodeDecodeError, + UnicodeEncodeError, + IndentationError): + return source + + if ignore: + if ignore in new_text and ignore not in source: + return source + + return new_text + + +def fix_w602(source): + """Fix deprecated form of raising exception.""" + return refactor(source, ['raise'], + ignore='with_traceback') + + +def fix_w6(source): + """Fix various deprecated code (via lib2to3).""" + return refactor(source, + ['apply', + 'except', + 'exec', + 'execfile', + 'exitfunc', + 'has_key', + 'idioms', + 'import', + 'methodattrs', # Python >= 2.6 + 'ne', + 'numliterals', + 'operator', + 'paren', + 'reduce', + 'renames', + 'repr', + 'standarderror', + 'sys_exc', + 'throw', + 'tuple_params', + 'types', + 'xreadlines']) def find_newline(source): @@ -889,15 +949,13 @@ def find_newline(source): cr += 1 elif s.endswith(LF): lf += 1 - _max = max(cr, crlf, lf) + _max = max(lf, cr, crlf) if _max == lf: return LF elif _max == crlf: return CRLF - elif _max == cr: - return CR else: - return LF + return CR def _get_indentword(source): @@ -923,12 +981,24 @@ def _get_indentation(line): return '' -def _get_difftext(old, new, filename): +def get_diff_text(old, new, filename): + """Return text of unified diff between old and new.""" + newline = '\n' diff = difflib.unified_diff( old, new, 'original/' + filename, - 'fixed/' + filename) - return ''.join(diff) + 'fixed/' + filename, + lineterm=newline) + + text = '' + for line in diff: + text += line + + # Work around missing newline (http://bugs.python.org/issue2142). + if not line.endswith(newline): + text += newline + r'\ No newline at end of file' + newline + + return text def _priority_key(pep8_result): @@ -938,13 +1008,20 @@ def _priority_key(pep8_result): like indentation. """ - priority = ['e101', 'e111', 'w191', # Global fixes - 'e701', # Fix multiline colon-based before semicolon based - 'e702', # Break multiline statements early - 'e225', 'e231', # things that make lines longer - 'e201', # Remove extraneous whitespace before breaking lines - 'e501', # before we break lines - ] + priority = [ + # Global fixes. + 'e101', 'e111', 'w191', + # Fix multiline colon-based before semicolon based. + 'e701', + # Break multiline statements early. + 'e702', + # Things that make lines longer. + 'e225', 'e231', + # Remove extraneous whitespace before breaking lines. + 'e201', + # Before breaking lines. + 'e121', 'e122', 'e123', 'e124', 'e125', 'e126', 'e127', 'e128', 'e129', + ] key = pep8_result['id'].lower() if key in priority: return priority.index(key) @@ -1022,7 +1099,7 @@ def _shorten_line(tokens, source, indentation, indent_word, newline, fixed = first + newline + second # Only fix if syntax is okay. - if check_syntax(normalize_multiline(fixed) + if check_syntax(normalize_multiline(fixed, newline=newline) if aggressive else fixed): yield indentation + fixed @@ -1069,21 +1146,21 @@ def _shorten_line_at_tokens(tokens, source, indentation, indent_word, newline, assert fixed is not None - if check_syntax(normalize_multiline(fixed) + if check_syntax(normalize_multiline(fixed, newline=newline) if aggressive > 1 else fixed): return indentation + fixed else: return None -def normalize_multiline(line): +def normalize_multiline(line, newline): """Remove multiline-related code that will cause syntax error. This is for purposes of checking syntax. """ for quote in '\'"': - dict_pattern = r'^{q}[^{q}]*{q}\s*:\s*'.format(q=quote) + dict_pattern = r'^{q}[^{q}]*{q} *: *'.format(q=quote) if re.match(dict_pattern, line): if not line.strip().endswith('}'): line += '}' @@ -1091,7 +1168,8 @@ def normalize_multiline(line): if line.startswith('def ') and line.rstrip().endswith(':'): # Do not allow ':' to be alone. That is invalid. - if ':' not in line.split(): + split_line = [item.strip() for item in line.split(newline)] + if ':' not in split_line and 'def' not in split_line: return line[len('def'):].strip().rstrip(':') return line @@ -1166,7 +1244,7 @@ def __init__(self, input_text, newline): # Note that a line is all-blank iff it is a newline. self.lines = [] for line_number, line in enumerate(self.raw, start=1): - # Do not modify if inside a multi-line string. + # Do not modify if inside a multiline string. if line_number in self.string_content_line_numbers: self.lines.append(line) else: @@ -1212,7 +1290,7 @@ def run(self): # An indented comment line. If we saw the same # indentation before, reuse what it most recently # mapped to. - want = have2want.get(have, - 1) + want = have2want.get(have, -1) if want < 0: # Then it probably belongs to the next real stmt. for j in range(i + 1, len(stats) - 1): @@ -1395,21 +1473,20 @@ def pep8_expected(self): # What follows is an adjusted version of # pep8.py:continuation_line_indentation. All of the comments have been # stripped and the 'yield' statements replaced with 'pass'. - tokens = self.tokens - if not tokens: - return + if not self.tokens: + return # pragma: no cover - first_row = tokens[0][2][0] - nrows = 1 + tokens[-1][2][0] - first_row + first_row = self.tokens[0][2][0] + nrows = 1 + self.tokens[-1][2][0] - first_row # here are the return values valid_indents = [list()] * nrows - indent_level = tokens[0][2][1] + indent_level = self.tokens[0][2][1] valid_indents[0].append(indent_level) if nrows == 1: # bug, really. - return valid_indents + return valid_indents # pragma: no cover indent_next = self.logical_line.endswith(':') @@ -1435,7 +1512,7 @@ def pep8_expected(self): # use at the given indent level, and return the offset. This # algorithm is susceptible to "carried errors", but should # through repeated runs eventually solve indentation for - # multi-line expressions less than PEP8_PASSES_MAX lines long. + # multiline expressions less than PEP8_PASSES_MAX lines long. if depth: for open_row in range(row - 1, -1, -1): @@ -1558,22 +1635,20 @@ def _leading_space_count(line): return i -def refactor_with_2to3(source_text, fixer_name): +def refactor_with_2to3(source_text, fixer_names): """Use lib2to3 to refactor the source. Return the refactored source code. """ - from lib2to3 import refactor - fixers = ['lib2to3.fixes.fix_' + fixer_name] - tool = refactor.RefactoringTool( - fixer_names=fixers, - explicit=fixers) + from lib2to3.refactor import RefactoringTool + fixers = ['lib2to3.fixes.fix_' + name for name in fixer_names] + tool = RefactoringTool(fixer_names=fixers, explicit=fixers) return unicode(tool.refactor_string(source_text, name='')) -def break_multi_line(source_text, newline, indent_word): - """Break first line of multi-line code. +def break_multiline(source_text, newline, indent_word): + """Break first line of multiline code. Return None if a break is not possible. @@ -1583,9 +1658,10 @@ def break_multi_line(source_text, newline, indent_word): # Handle special case only. for symbol in '([{': # Only valid if symbol is not on a line by itself. - if (symbol in source_text - and source_text.rstrip().endswith(',') - and not source_text.lstrip().startswith(symbol)): + if (symbol in source_text and not source_text.strip() == symbol): + + if not source_text.rstrip()[-1] == ',': + continue index = 1 + source_text.find(symbol) @@ -1655,7 +1731,7 @@ def filter_results(source, results, aggressive=False): # Filter out incorrect E101 reports when there are no tabs. # pep8 will complain about this even if the tab indentation found - # elsewhere is in a multi-line string. + # elsewhere is in a multiline string. if issue_id == 'e101' and '\t' not in split_source[r['line']]: continue @@ -1719,34 +1795,13 @@ def shorten_comment(line, newline, max_line_length): initial_indent=indentation, subsequent_indent=indentation, width=max_line_length, - break_long_words=False) + break_long_words=False, + break_on_hyphens=False) return newline.join(split_lines) + newline else: return line + newline -def format_block_comments(source): - """Format block comments.""" - if '#' not in source: - # Optimization. - return source - - string_line_numbers = multiline_string_lines(source, - include_docstrings=True) - fixed_lines = [] - sio = StringIO(source) - for (line_number, line) in enumerate(sio.readlines(), start=1): - if (re.match(r'\s*#+\w+', line) and - line_number not in string_line_numbers): - fixed_lines.append(_get_indentation(line) + - '# ' + - line.lstrip().lstrip('#')) - else: - fixed_lines.append(line) - - return ''.join(fixed_lines) - - def normalize_line_endings(lines): """Return fixed line endings. @@ -1792,11 +1847,15 @@ def fix_lines(source_lines, options, filename=''): # Keep a history to break out of cycles. previous_hashes = set([hash(tmp_source)]) - fixed_source = tmp_source - if code_match('e26', select=options.select, ignore=options.ignore): - fixed_source = format_block_comments(fixed_source) + # Apply global fixes only once (for efficiency). + fixed_source = apply_global_fixes(tmp_source, options) + + passes = 0 + while True: + if options.pep8_passes >= 0 and passes > options.pep8_passes: + break + passes += 1 - for _ in range(-1, options.pep8_passes): tmp_source = copy.copy(fixed_source) fix = FixPEP8(filename, options, contents=tmp_source) @@ -1826,11 +1885,12 @@ def fix_file(filename, options=None, output=None): if options.diff: new = StringIO(fixed_source) new = new.readlines() - diff = _get_difftext(original_source, new, filename) + diff = get_diff_text(original_source, new, filename) if output: output.write(diff) + output.flush() else: - return output + return diff elif options.in_place: fp = open_with_encoding(filename, encoding=encoding, mode='w') @@ -1839,17 +1899,65 @@ def fix_file(filename, options=None, output=None): else: if output: output.write(fixed_source) + output.flush() else: return fixed_source +def global_fixes(): + """Yield multiple (code, function) tuples.""" + for function in globals().values(): + if inspect.isfunction(function): + arguments = inspect.getargspec(function)[0] + if arguments != ['source']: + continue + + code = extract_code_from_function(function) + if code: + yield (code, function) + + +def apply_global_fixes(source, options): + """Run global fixes on source code. + + Thsese are fixes that only need be done once (unlike those in FixPEP8, + which are dependent on pep8). + + """ + for (code, function) in global_fixes(): + if code_match(code, select=options.select, ignore=options.ignore): + if options.verbose: + print('---> Applying global fix for {0}'.format(code.upper()), + file=sys.stderr) + source = function(source) + + return source + + +def extract_code_from_function(function): + """Return code handled by function.""" + if not function.__name__.startswith('fix_'): + return None + + code = re.sub('^fix_', '', function.__name__) + if not code: + return None + + try: + int(code[1:]) + except ValueError: + return None + + return code + + def parse_args(args): """Parse command-line options.""" parser = OptionParser(usage='Usage: autopep8 [options] ' '[filename [filename ...]]' '\nUse filename \'-\' for stdin.', - version='autopep8: %s' % __version__, - description=__doc__, + version='%prog {0}'.format(__version__), + description=__doc__.split('\n')[0], prog='autopep8') parser.add_option('-v', '--verbose', action='count', dest='verbose', default=0, @@ -1866,9 +1974,9 @@ def parse_args(args): help='number of parallel jobs; ' 'match CPU count if value is less than 1') parser.add_option('-p', '--pep8-passes', metavar='n', - default=100, type=int, - help='maximum number of additional pep8 passes' - ' (default: %default)') + default=-1, type=int, + help='maximum number of additional pep8 passes ' + '(default: infinite)') parser.add_option('-a', '--aggressive', action='count', default=0, help='enable possibly unsafe changes (E711, E712); ' 'multiple -a result in more aggressive changes') @@ -1880,7 +1988,7 @@ def parse_args(args): 'used by --ignore and --select') parser.add_option('--ignore', metavar='errors', default='', help='do not fix these errors/warnings ' - '(default {0})'.format(pep8.DEFAULT_IGNORE)) + '(default: {0})'.format(DEFAULT_IGNORE)) parser.add_option('--select', metavar='errors', default='', help='fix only these errors/warnings (e.g. E4,W)') parser.add_option('--max-line-length', metavar='n', default=79, type=int, @@ -1920,8 +2028,12 @@ def parse_args(args): if options.ignore: options.ignore = options.ignore.split(',') - elif not options.select and pep8.DEFAULT_IGNORE: - options.ignore = pep8.DEFAULT_IGNORE.split(',') + elif not options.select: + if options.aggressive: + # Enable everything by default if aggressive. + options.select = ['E', 'W'] + else: + options.ignore = DEFAULT_IGNORE.split(',') if options.exclude: options.exclude = options.exclude.split(',') @@ -1954,6 +2066,10 @@ def supported_fixes(): re.sub(r'\s+', ' ', getattr(instance, attribute).__doc__)) + for (code, function) in global_fixes(): + yield (code.upper() + (4 - len(code)) * ' ', + re.sub(r'\s+', ' ', function.__doc__)) + def line_shortening_rank(candidate, newline, indent_word): """Return rank of candidate. @@ -1962,7 +2078,7 @@ def line_shortening_rank(candidate, newline, indent_word): """ rank = 0 - if candidate: + if candidate.strip(): lines = candidate.split(newline) offset = 0 @@ -2002,6 +2118,10 @@ def line_shortening_rank(candidate, newline, indent_word): if current_line.endswith('%'): rank -= 20 + + # Try to break list comprehensions at the "for". + if current_line.lstrip().startswith('for'): + rank -= 50 else: rank = 100000 @@ -2048,8 +2168,8 @@ def __init__(self, output): def write(self, s): self.__output.write(s.replace('\r\n', '\n').replace('\r', '\n')) - def __getattr__(self, key): - return getattr(self.__output, key) + def flush(self): + self.__output.flush() def temporary_file(): @@ -2062,16 +2182,16 @@ def temporary_file(): def match_file(filename, exclude): """Return True if file is okay for modifying/recursing.""" - if not filename.endswith('.py'): - return False - - if filename.startswith('.'): + if os.path.basename(filename).startswith('.'): return False for pattern in exclude: if fnmatch.fnmatch(filename, pattern): return False + if not is_python_file(filename): + return False + return True @@ -2083,9 +2203,8 @@ def find_files(filenames, recursive, exclude): for root, directories, children in os.walk(name): filenames += [os.path.join(root, f) for f in children if match_file(f, exclude)] - for d in directories: - if d.startswith('.'): - directories.remove(d) + directories[:] = [d for d in directories + if not d.startswith('.')] else: yield name @@ -2117,11 +2236,35 @@ def fix_multiple_files(filenames, options, output=None): _fix_file((name, options, output)) +def is_python_file(filename): + """Return True if filename is Python file.""" + if filename.endswith('.py'): + return True + + try: + with open_with_encoding(filename) as f: + first_line = f.readlines(1)[0] + except (IOError, IndexError): + return False + + if len(first_line) > 200: + # This is probably not even a text file. + return False + + if not PYTHON_SHEBANG_REGEX.match(first_line): + return False + + return True + + def main(): """Tool main.""" - if not pep8: - print('pep8 >= 1.3.2 required', file=sys.stderr) - return 1 + try: + # Exit on broken pipe. + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + except AttributeError: # pragma: no cover + # SIGPIPE is not available on Windows. + pass try: options, args = parse_args(sys.argv[1:]) @@ -2154,7 +2297,7 @@ def main(): fix_multiple_files(filenames, options, output) except KeyboardInterrupt: - return 1 + return 1 # pragma: no cover if __name__ == '__main__': From af87c01a62b7847a92227e5cabeb9456b7295c8c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 3 May 2013 18:56:36 +0800 Subject: [PATCH 188/513] Added AUTHORS file. --- AUTHORS | 30 ++++++++++++++++++++++++++++++ Changelog.rst | 2 ++ README.rst | 4 +++- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 AUTHORS diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..d072603a --- /dev/null +++ b/AUTHORS @@ -0,0 +1,30 @@ +Maintainer: + +* Kirill Klenov + + +Contributors: + +* Pedro Algarvio (s0undt3ch); +* Alvin Francis (alvinfrancis); +* Boris Filippov (frenzykryger); +* Denis Kasak (dkasak); +* Steve Losh (sjl); +* nixon; +* tramchamploo; +* Benjamin Ruston (bruston); +* Robert David Grant (bgrant); +* Florent Xicluna (florentx); +* Piet Delport (pjdelport); +* lee (loyalpartner); +* Mohammed (mbadran); +* Ronald Andreu Kaiser (cathoderay); +* David Vogt (winged); +* Igor Guerrero (igorgue); +* Martin Brochhaus (mbrochh); +* Sorin Ionescu (sorin-ionescu); +* Dirk Wallenstein (dirkwallenstein); +* Jonathan McCall (Jonnymcc); +* Fredrik Henrysson (fhenrysson); +* Lowe Thiderman (thiderman); +* Naoya Inada (naoina); diff --git a/Changelog.rst b/Changelog.rst index a9984140..2256a6e5 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -5,6 +5,8 @@ Changelog -------------------- * Update `Pylint` to version 0.28.0; * Update `pyflakes` to version 0.7.3; +* Fixed `lint_ignore` options bug; +* Fixed encoding problems when code running; ## 2013-04-26 0.6.16 -------------------- diff --git a/README.rst b/README.rst index 9f9ab6e7..e672e006 100644 --- a/README.rst +++ b/README.rst @@ -469,13 +469,15 @@ at https://github.com/klen/python-mode/issues Contributing ============ +See in the `AUTHORS` file. + Development of pylint-mode happens at github: https://github.com/klen/python-mode Copyright ========= -Copyright (C) 2012 Kirill Klenov (klen_) +Copyright © 2013 Kirill Klenov (klen_) **Rope** Copyright (C) 2006-2010 Ali Gholami Rudi From cbfc8cd8fa19fc3643ddbaa6991fb4527f6a3f6f Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 3 May 2013 19:43:41 +0800 Subject: [PATCH 189/513] Update version. --- README.rst | 16 ---------------- doc/pymode.txt | 2 +- ftplugin/python/init-pymode.vim | 2 +- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/README.rst b/README.rst index e672e006..2231f121 100644 --- a/README.rst +++ b/README.rst @@ -28,22 +28,6 @@ Another old presentation here: http://www.youtube.com/watch?v=YhqsjUUHj6g .. contents:: -Changelog -========= - -## 2013-03-15 0.6.12 --------------------- -* Update `PEP8` to version 1.4.5; -* Update `Pylint` to version 0.27.0; -* Update `autopep8` to version 0.8.7; -* Fix breakpoint definition; -* Update python syntax; -* Fixed run-time error when output non-ascii in multibyte locale; -* Move initialization into ftplugin as it is python specific; -* Pyrex (Cython) files support; -* Support `raw_input` in run python code; - - Requirements ============ diff --git a/doc/pymode.txt b/doc/pymode.txt index 9bf7d590..59cb3f94 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.16 + Version: 0.6.17 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index e2733857..b8ea91c6 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -3,7 +3,7 @@ if exists('did_init_pymode_vim') endif let did_init_pymode_vim = 1 -let g:pymode_version = "0.6.16" +let g:pymode_version = "0.6.17" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From e10c7f71db06e60be11a7ccf9acf5c92e4a83cba Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 6 May 2013 15:47:23 +0800 Subject: [PATCH 190/513] Refactoring code checking --- autoload/pymode/lint.vim | 1 + ftplugin/python/init-pymode.vim | 3 +- pylibs/pymode/interface.py | 2 +- pylibs/pymode/lint.py | 39 +++++++-------- pylibs/pymode/queue.py | 88 ++++++++++++++++++--------------- 5 files changed, 71 insertions(+), 62 deletions(-) diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 68782e18..082f80b8 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -15,6 +15,7 @@ fun! pymode#lint#Check() "{{{ let g:pymode_lint_buffer = bufnr('%') py from pymode import lint + py queue.stop_queue(False) py lint.check_file() endfunction " }}} diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index b8ea91c6..c948f191 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -1,7 +1,6 @@ -if exists('did_init_pymode_vim') +if pymode#Default('g:pymode_init', 1) finish endif -let did_init_pymode_vim = 1 let g:pymode_version = "0.6.17" diff --git a/pylibs/pymode/interface.py b/pylibs/pymode/interface.py index a7863bd0..3134882d 100644 --- a/pylibs/pymode/interface.py +++ b/pylibs/pymode/interface.py @@ -18,7 +18,7 @@ def get_current_buffer(): def show_message(message): - vim.command("call pymode#WideMessage('%s')" % message) + vim.command("call pymode#WideMessage('{0}')".format(message)) def command(cmd): diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index e2240876..6746332f 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -28,37 +28,36 @@ def check_file(): if s ]) - buffer = get_current_buffer() + buf = get_current_buffer() complexity = int(get_option('lint_mccabe_complexity') or 0) - add_task(run_checkers, checkers=checkers, ignore=ignore, - title='Code checking', - callback=parse_result, - buffer=buffer, - select=select, - complexity=complexity) + add_task( + run_checkers, + callback=parse_result, + title='Code checking', + checkers=checkers, + ignore=ignore, + buf=buf, + select=select, + complexity=complexity) -def run_checkers(task=None, checkers=None, ignore=None, - buffer=None, select=None, complexity=None): - buffer = (task and task.buffer) or buffer - filename = buffer.name +def run_checkers(checkers=None, ignore=None, buf=None, select=None, + complexity=None, callback=None): + + filename = buf.name result = [] pylint_options = '--rcfile={0} -r n'.format(get_var('lint_config')).split() - result = run(filename, ignore=ignore, select=select, linters=checkers, + return run(filename, ignore=ignore, select=select, linters=checkers, pylint=pylint_options, complexity=complexity) - if task: - task.result = result - task.finished = True - task.done = 100 - -def parse_result(result, bnum): - command(('let g:qf_list = {0}'.format(repr(result)).replace('\': u', '\': '))) - command('call pymode#lint#Parse({0})'.format(bnum)) +def parse_result(result, buf=None, **kwargs): + command(('let g:qf_list = {0}'.format(repr(result)).replace( + '\': u', '\': '))) + command('call pymode#lint#Parse({0})'.format(buf.number)) # pymode:lint_ignore=W0622 diff --git a/pylibs/pymode/queue.py b/pylibs/pymode/queue.py index ed90be8f..d1d087ae 100644 --- a/pylibs/pymode/queue.py +++ b/pylibs/pymode/queue.py @@ -1,60 +1,70 @@ import threading +from Queue import Queue, Empty + from .interface import show_message -import time + + +MAX_LIFE = 60 +CHECK_INTERVAL = .2 +RESULTS = Queue() class Task(threading.Thread): - def __init__(self, buffer, callback=None, title=None, *args, **kwargs): - self.buffer = buffer - self._stop = threading.Event() - self.result = None - self.callback = callback - self.done = 0 - self.finished = False - self.title = title + def __init__(self, *args, **kwargs): + self.stop = threading.Event() threading.Thread.__init__(self, *args, **kwargs) def run(self): - " Run tasks. " - self._Thread__target(task=self, *self._Thread__args, **self._Thread__kwargs) - - # Wait for result parsing - while not self.stopped(): - time.sleep(.2) + """ Run the task. + """ + try: + args, kwargs = self._Thread__args, self._Thread__kwargs + checking = self._Thread__target(*args, **kwargs) + if not self.stop.isSet(): + RESULTS.put((checking, args, kwargs)) - def stop(self): - " Stop task. " - self._stop.set() - - def stopped(self): - return self._stop.isSet() - - -def stop_queue(): - " Stop all tasks. " - for thread in threading.enumerate(): - if isinstance(thread, Task): - thread.stop() - show_message('%s stopped.' % thread.title) + except Exception as e: + if not self.stop.isSet(): + RESULTS.put(e) -def add_task(target, callback=None, buffer=None, title=None, *args, **kwargs): +def add_task(target, title=None, *args, **kwargs): " Add all tasks. " - task = Task(buffer, title=title, target=target, callback=callback, args=args, kwargs=kwargs) + task = Task(target=target, args=args, kwargs=kwargs) task.daemon = True task.start() - show_message('%s started.' % task.title) + show_message('{0} started.'.format(title)) -def check_task(): - " Check tasks for result. " +def stop_queue(message=True): + """ Stop all tasks. + """ + with RESULTS.mutex: + RESULTS.queue.clear() + for thread in threading.enumerate(): if isinstance(thread, Task): - if thread.finished: - thread.stop() - thread.callback(thread.result, thread.buffer.number) - else: - show_message('%s %s%%' % (thread.title, thread.done)) + thread.stop.set() + if message: + show_message("Task stopped.") + + +def check_task(): + """ Checking running tasks. + """ + try: + result = RESULTS.get(False) + assert isinstance(result, tuple) + except Empty: + return False + except AssertionError: + return False + result, _, kwargs = result + callback = kwargs.pop('callback') + callback(result, **kwargs) + + +# lint_ignore=W0703 From 29c8010f47a5d7b900c1d0ac81f4fa18df05b7c5 Mon Sep 17 00:00:00 2001 From: ikame Date: Fri, 10 May 2013 02:27:45 +0200 Subject: [PATCH 191/513] Several improvements Added option 'g:pymode_syntax_highlight_equal_operator' If set, assignment '=' operator is highlighted Added option 'g:pymode_syntax_highlight_stars_operator' If set, star '*' and double star '**' operators are highlighted Added option 'g:pymode_syntax_highlight_self' If set, 'self' and 'cls' are highlighted as keywords Added option 'g:pymode_syntax_builtin_types' If set, all built-in types are highlighted Some types like 'file' and 'super' were declared previously as built-in functions, but in reality they are also types. --- syntax/python.vim | 276 ++++++++++++++++++++++++++-------------------- 1 file changed, 155 insertions(+), 121 deletions(-) diff --git a/syntax/python.vim b/syntax/python.vim index 7a8d06d3..e570b632 100644 --- a/syntax/python.vim +++ b/syntax/python.vim @@ -18,46 +18,70 @@ call pymode#Default('g:pymode_syntax_all', 1) " Keywords {{{ " ============ - syn keyword pythonStatement break continue del - syn keyword pythonStatement exec return - syn keyword pythonStatement pass raise - syn keyword pythonStatement global assert - syn keyword pythonStatement lambda yield - syn keyword pythonStatement with as - syn keyword pythonStatement def nextgroup=pythonFunction skipwhite - syn match pythonFunction "[a-zA-Z_][a-zA-Z0-9_]*" display contained - syn keyword pythonStatement class nextgroup=pythonClass skipwhite - syn match pythonClass "[a-zA-Z_][a-zA-Z0-9_]*" display contained - syn keyword pythonRepeat for while - syn keyword pythonConditional if elif else - syn keyword pythonInclude import from - syn keyword pythonException try except finally - syn keyword pythonOperator and in is not or + syn keyword pythonStatement break continue del + syn keyword pythonStatement exec return + syn keyword pythonStatement pass raise + syn keyword pythonStatement global nonlocal assert + syn keyword pythonStatement lambda yield + syn keyword pythonStatement with as + + syn keyword pythonStatement def nextgroup=pythonFunction skipwhite + syn match pythonFunction "\%(\%(def\s\|@\)\s*\)\@<=\h\%(\w\|\.\)*" contained nextgroup=pythonVars + syn region pythonVars start="(" end=")" contained contains=pythonParameters transparent keepend + syn match pythonParameters "[^,]*" contained contains=pythonExtraOperator,pythonParam skipwhite + syn match pythonParam "=[^,]*" contained contains=pythonBuiltinObj,pythonBuiltinType,pythonConstant,pythonStatement,pythonNumber,pythonString,pythonNumber,pythonBrackets skipwhite + syn match pythonBrackets "[(|)]" contained skipwhite + + syn keyword pythonStatement class nextgroup=pythonClass skipwhite + syn match pythonClass "\%(\%(class\s\)\s*\)\@<=\h\%(\w\|\.\)*" contained nextgroup=pythonClassVars + syn region pythonClassVars start="(" end=")" contained contains=pythonClassParameters transparent keepend + syn match pythonClassParameters "[^,\*]*" contained contains=pythonBuiltin,pythonBuiltinObj,pythonBuiltinType,pythonExtraOperatorpythonStatement,pythonBrackets,pythonString skipwhite + + syn keyword pythonRepeat for while + syn keyword pythonConditional if elif else + syn keyword pythonInclude import from + syn keyword pythonException try except finally + syn keyword pythonOperator and in is not or + + syn match pythonExtraOperator "\%([~!^&|/%+-]\|\%(class\s*\)\@\|<=\|\%(<\|\>\|>=\|=\@\|\.\.\.\|\.\.\|::\)" + syn match pythonExtraPseudoOperator "\%(-=\|/=\|\*\*=\|\*=\|&&=\|&=\|&&\|||=\||=\|||\|%=\|+=\|!\~\|!=\)" if !pymode#Default("g:pymode_syntax_print_as_function", 0) || !g:pymode_syntax_print_as_function syn keyword pythonStatement print endif + if !pymode#Default('g:pymode_syntax_highlight_equal_operator', g:pymode_syntax_all) || g:pymode_syntax_highlight_equal_operator + syn match pythonExtraOperator "\%(=\)" + endif + + if !pymode#Default('g:pymode_syntax_highlight_stars_operator', g:pymode_syntax_all) || g:pymode_syntax_highlight_stars_operator + syn match pythonExtraOperator "\%(\*\|\*\*\)" + endif + + if !pymode#Default('g:pymode_syntax_highlight_self', g:pymode_syntax_all) || g:pymode_syntax_highlight_self + syn keyword pythonSelf self cls + endif + " }}} " Decorators {{{ " ============== - syn match pythonDecorator "@" display nextgroup=pythonDottedName skipwhite + syn match pythonDecorator "@" display nextgroup=pythonDottedName skipwhite syn match pythonDottedName "[a-zA-Z_][a-zA-Z0-9_]*\(\.[a-zA-Z_][a-zA-Z0-9_]*\)*" display contained syn match pythonDot "\." display containedin=pythonDottedName - + " }}} " Comments {{{ " ============ - syn match pythonComment "#.*$" display contains=pythonTodo,@Spell - syn match pythonRun "\%^#!.*$" - syn match pythonCoding "\%^.*\(\n.*\)\?#.*coding[:=]\s*[0-9A-Za-z-_.]\+.*$" - syn keyword pythonTodo TODO FIXME XXX contained + syn match pythonComment "#.*$" display contains=pythonTodo,@Spell + syn match pythonRun "\%^#!.*$" + syn match pythonCoding "\%^.*\(\n.*\)\?#.*coding[:=]\s*[0-9A-Za-z-_.]\+.*$" + syn keyword pythonTodo TODO FIXME XXX contained " }}} @@ -65,19 +89,19 @@ call pymode#Default('g:pymode_syntax_all', 1) " Errors {{{ " ========== - syn match pythonError "\<\d\+\D\+\>" display - syn match pythonError "[$?]" display - syn match pythonError "[&|]\{2,}" display - syn match pythonError "[=]\{3,}" display + syn match pythonError "\<\d\+\D\+\>" display + syn match pythonError "[$?]" display + syn match pythonError "[&|]\{2,}" display + syn match pythonError "[=]\{3,}" display " Indent errors (mix space and tabs) if !pymode#Default('g:pymode_syntax_indent_errors', g:pymode_syntax_all) || g:pymode_syntax_indent_errors - syn match pythonIndentError "^\s*\( \t\|\t \)\s*\S"me=e-1 display + syn match pythonIndentError "^\s*\( \t\|\t \)\s*\S"me=e-1 display endif " Trailing space errors if !pymode#Default('g:pymode_syntax_space_errors', g:pymode_syntax_all) || g:pymode_syntax_space_errors - syn match pythonSpaceError "\s\+$" display + syn match pythonSpaceError "\s\+$" display endif " }}} @@ -86,58 +110,58 @@ call pymode#Default('g:pymode_syntax_all', 1) " Strings {{{ " =========== - syn region pythonString start=+[bB]\='+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonEscape,pythonEscapeError,@Spell - syn region pythonString start=+[bB]\="+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonEscape,pythonEscapeError,@Spell - syn region pythonString start=+[bB]\="""+ end=+"""+ keepend contains=pythonEscape,pythonEscapeError,pythonDocTest2,pythonSpaceError,@Spell - syn region pythonString start=+[bB]\='''+ end=+'''+ keepend contains=pythonEscape,pythonEscapeError,pythonDocTest,pythonSpaceError,@Spell + syn region pythonString start=+[bB]\='+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonEscape,pythonEscapeError,@Spell + syn region pythonString start=+[bB]\="+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonEscape,pythonEscapeError,@Spell + syn region pythonString start=+[bB]\="""+ end=+"""+ keepend contains=pythonEscape,pythonEscapeError,pythonDocTest2,pythonSpaceError,@Spell + syn region pythonString start=+[bB]\='''+ end=+'''+ keepend contains=pythonEscape,pythonEscapeError,pythonDocTest,pythonSpaceError,@Spell - syn match pythonEscape +\\[abfnrtv'"\\]+ display contained - syn match pythonEscape "\\\o\o\=\o\=" display contained - syn match pythonEscapeError "\\\o\{,2}[89]" display contained - syn match pythonEscape "\\x\x\{2}" display contained - syn match pythonEscapeError "\\x\x\=\X" display contained - syn match pythonEscape "\\$" + syn match pythonEscape +\\[abfnrtv'"\\]+ display contained + syn match pythonEscape "\\\o\o\=\o\=" display contained + syn match pythonEscapeError "\\\o\{,2}[89]" display contained + syn match pythonEscape "\\x\x\{2}" display contained + syn match pythonEscapeError "\\x\x\=\X" display contained + syn match pythonEscape "\\$" " Unicode - syn region pythonUniString start=+[uU]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,@Spell - syn region pythonUniString start=+[uU]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,@Spell - syn region pythonUniString start=+[uU]"""+ end=+"""+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,pythonDocTest2,pythonSpaceError,@Spell - syn region pythonUniString start=+[uU]'''+ end=+'''+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,pythonDocTest,pythonSpaceError,@Spell + syn region pythonUniString start=+[uU]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,@Spell + syn region pythonUniString start=+[uU]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,@Spell + syn region pythonUniString start=+[uU]"""+ end=+"""+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,pythonDocTest2,pythonSpaceError,@Spell + syn region pythonUniString start=+[uU]'''+ end=+'''+ keepend contains=pythonEscape,pythonUniEscape,pythonEscapeError,pythonUniEscapeError,pythonDocTest,pythonSpaceError,@Spell - syn match pythonUniEscape "\\u\x\{4}" display contained + syn match pythonUniEscape "\\u\x\{4}" display contained syn match pythonUniEscapeError "\\u\x\{,3}\X" display contained - syn match pythonUniEscape "\\U\x\{8}" display contained + syn match pythonUniEscape "\\U\x\{8}" display contained syn match pythonUniEscapeError "\\U\x\{,7}\X" display contained - syn match pythonUniEscape "\\N{[A-Z ]\+}" display contained - syn match pythonUniEscapeError "\\N{[^A-Z ]\+}" display contained + syn match pythonUniEscape "\\N{[A-Z ]\+}" display contained + syn match pythonUniEscapeError "\\N{[^A-Z ]\+}" display contained " Raw strings - syn region pythonRawString start=+[rR]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonRawEscape,@Spell - syn region pythonRawString start=+[rR]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonRawEscape,@Spell - syn region pythonRawString start=+[rR]"""+ end=+"""+ keepend contains=pythonDocTest2,pythonSpaceError,@Spell - syn region pythonRawString start=+[rR]'''+ end=+'''+ keepend contains=pythonDocTest,pythonSpaceError,@Spell + syn region pythonRawString start=+[rR]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonRawEscape,@Spell + syn region pythonRawString start=+[rR]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonRawEscape,@Spell + syn region pythonRawString start=+[rR]"""+ end=+"""+ keepend contains=pythonDocTest2,pythonSpaceError,@Spell + syn region pythonRawString start=+[rR]'''+ end=+'''+ keepend contains=pythonDocTest,pythonSpaceError,@Spell - syn match pythonRawEscape +\\['"]+ display transparent contained + syn match pythonRawEscape +\\['"]+ display transparent contained " Unicode raw strings - syn region pythonUniRawString start=+[uU][rR]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonRawEscape,pythonUniRawEscape,pythonUniRawEscapeError,@Spell - syn region pythonUniRawString start=+[uU][rR]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonRawEscape,pythonUniRawEscape,pythonUniRawEscapeError,@Spell - syn region pythonUniRawString start=+[uU][rR]"""+ end=+"""+ keepend contains=pythonUniRawEscape,pythonUniRawEscapeError,pythonDocTest2,pythonSpaceError,@Spell - syn region pythonUniRawString start=+[uU][rR]'''+ end=+'''+ keepend contains=pythonUniRawEscape,pythonUniRawEscapeError,pythonDocTest,pythonSpaceError,@Spell + syn region pythonUniRawString start=+[uU][rR]'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end=+$+ keepend contains=pythonRawEscape,pythonUniRawEscape,pythonUniRawEscapeError,@Spell + syn region pythonUniRawString start=+[uU][rR]"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end=+$+ keepend contains=pythonRawEscape,pythonUniRawEscape,pythonUniRawEscapeError,@Spell + syn region pythonUniRawString start=+[uU][rR]"""+ end=+"""+ keepend contains=pythonUniRawEscape,pythonUniRawEscapeError,pythonDocTest2,pythonSpaceError,@Spell + syn region pythonUniRawString start=+[uU][rR]'''+ end=+'''+ keepend contains=pythonUniRawEscape,pythonUniRawEscapeError,pythonDocTest,pythonSpaceError,@Spell - syn match pythonUniRawEscape "\([^\\]\(\\\\\)*\)\@<=\\u\x\{4}" display contained - syn match pythonUniRawEscapeError "\([^\\]\(\\\\\)*\)\@<=\\u\x\{,3}\X" display contained + syn match pythonUniRawEscape "\([^\\]\(\\\\\)*\)\@<=\\u\x\{4}" display contained + syn match pythonUniRawEscapeError "\([^\\]\(\\\\\)*\)\@<=\\u\x\{,3}\X" display contained " String formatting if !pymode#Default('g:pymode_syntax_string_formatting', g:pymode_syntax_all) || g:pymode_syntax_string_formatting - syn match pythonStrFormatting "%\(([^)]\+)\)\=[-#0 +]*\d*\(\.\d\+\)\=[hlL]\=[diouxXeEfFgGcrs%]" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString - syn match pythonStrFormatting "%[-#0 +]*\(\*\|\d\+\)\=\(\.\(\*\|\d\+\)\)\=[hlL]\=[diouxXeEfFgGcrs%]" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrFormatting "%\(([^)]\+)\)\=[-#0 +]*\d*\(\.\d\+\)\=[hlL]\=[diouxXeEfFgGcrs%]" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrFormatting "%[-#0 +]*\(\*\|\d\+\)\=\(\.\(\*\|\d\+\)\)\=[hlL]\=[diouxXeEfFgGcrs%]" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString endif " Str.format syntax if !pymode#Default('g:pymode_syntax_string_format', g:pymode_syntax_all) || g:pymode_syntax_string_format syn match pythonStrFormat "{{\|}}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString - syn match pythonStrFormat "{\([a-zA-Z_][a-zA-Z0-9_]*\|\d\+\)\(\.[a-zA-Z_][a-zA-Z0-9_]*\|\[\(\d\+\|[^!:\}]\+\)\]\)*\(![rs]\)\=\(:\({\([a-zA-Z_][a-zA-Z0-9_]*\|\d\+\)}\|\([^}]\=[<>=^]\)\=[ +-]\=#\=0\=\d*\(\.\d\+\)\=[bcdeEfFgGnoxX%]\=\)\=\)\=}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString + syn match pythonStrFormat "{\([a-zA-Z0-9_]*\|\d\+\)\(\.[a-zA-Z_][a-zA-Z0-9_]*\|\[\(\d\+\|[^!:\}]\+\)\]\)*\(![rs]\)\=\(:\({\([a-zA-Z_][a-zA-Z0-9_]*\|\d\+\)}\|\([^}]\=[<>=^]\)\=[ +-]\=#\=0\=\d*\(\.\d\+\)\=[bcdeEfFgGnoxX%]\=\)\=\)\=}" contained containedin=pythonString,pythonUniString,pythonRawString,pythonUniRawString endif " String templates @@ -149,8 +173,8 @@ call pymode#Default('g:pymode_syntax_all', 1) " DocTests if !pymode#Default('g:pymode_syntax_doctests', g:pymode_syntax_all) || g:pymode_syntax_doctests - syn region pythonDocTest start="^\s*>>>" end=+'''+he=s-1 end="^\s*$" contained - syn region pythonDocTest2 start="^\s*>>>" end=+"""+he=s-1 end="^\s*$" contained + syn region pythonDocTest start="^\s*>>>" end=+'''+he=s-1 end="^\s*$" contained + syn region pythonDocTest2 start="^\s*>>>" end=+"""+he=s-1 end="^\s*$" contained endif " }}} @@ -158,16 +182,16 @@ call pymode#Default('g:pymode_syntax_all', 1) " Numbers {{{ " =========== - syn match pythonHexError "\<0[xX]\x*[g-zG-Z]\x*[lL]\=\>" display - syn match pythonHexNumber "\<0[xX]\x\+[lL]\=\>" display + syn match pythonHexError "\<0[xX]\x*[g-zG-Z]\x*[lL]\=\>" display + syn match pythonHexNumber "\<0[xX]\x\+[lL]\=\>" display syn match pythonOctNumber "\<0[oO]\o\+[lL]\=\>" display syn match pythonBinNumber "\<0[bB][01]\+[lL]\=\>" display - syn match pythonNumber "\<\d\+[lLjJ]\=\>" display - syn match pythonFloat "\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>" display - syn match pythonFloat "\<\d\+[eE][+-]\=\d\+[jJ]\=\>" display - syn match pythonFloat "\<\d\+\.\d*\([eE][+-]\=\d\+\)\=[jJ]\=" display - syn match pythonOctError "\<0[oO]\=\o*[8-9]\d*[lL]\=\>" display - syn match pythonBinError "\<0[bB][01]*[2-9]\d*[lL]\=\>" display + syn match pythonNumber "\<\d\+[lLjJ]\=\>" display + syn match pythonFloat "\.\d\+\([eE][+-]\=\d\+\)\=[jJ]\=\>" display + syn match pythonFloat "\<\d\+[eE][+-]\=\d\+[jJ]\=\>" display + syn match pythonFloat "\<\d\+\.\d*\([eE][+-]\=\d\+\)\=[jJ]\=" display + syn match pythonOctError "\<0[oO]\=\o*[8-9]\d*[lL]\=\>" display + syn match pythonBinError "\<0[bB][01]*[2-9]\d*[lL]\=\>" display " }}} @@ -178,53 +202,53 @@ call pymode#Default('g:pymode_syntax_all', 1) if !pymode#Default('g:pymode_syntax_builtin_objs', g:pymode_syntax_all) || g:pymode_syntax_builtin_objs syn keyword pythonBuiltinObj True False Ellipsis None NotImplemented syn keyword pythonBuiltinObj __debug__ __doc__ __file__ __name__ __package__ - syn keyword pythonBuiltinObj self + endif + + if !pymode#Default('g:pymode_syntax_builtin_types', g:pymode_syntax_all) || g:pymode_syntax_builtin_types + syn keyword pythonBuiltinType type object + syn keyword pythonBuiltinType str basestring unicode buffer bytearray bytes chr unichr + syn keyword pythonBuiltinType dict int long bool float complex set frozenset list tuple + syn keyword pythonBuiltinType file super endif " Builtin functions if !pymode#Default('g:pymode_syntax_builtin_funcs', g:pymode_syntax_all) || g:pymode_syntax_builtin_funcs - syn keyword pythonBuiltinFunc __import__ abs all any apply - syn keyword pythonBuiltinFunc basestring bin bool buffer bytearray bytes callable - syn keyword pythonBuiltinFunc chr classmethod cmp coerce compile complex - syn keyword pythonBuiltinFunc delattr dict dir divmod enumerate eval - syn keyword pythonBuiltinFunc execfile file filter float format frozenset getattr - syn keyword pythonBuiltinFunc globals hasattr hash help hex id - syn keyword pythonBuiltinFunc input int intern isinstance - syn keyword pythonBuiltinFunc issubclass iter len list locals long map max - syn keyword pythonBuiltinFunc min next object oct open ord - syn keyword pythonBuiltinFunc pow property range - syn keyword pythonBuiltinFunc raw_input reduce reload repr - syn keyword pythonBuiltinFunc reversed round set setattr - syn keyword pythonBuiltinFunc slice sorted staticmethod str sum super tuple - syn keyword pythonBuiltinFunc type unichr unicode vars xrange zip + syn keyword pythonBuiltinFunc __import__ abs all any apply + syn keyword pythonBuiltinFunc bin callable classmethod cmp coerce compile + syn keyword pythonBuiltinFunc delattr dir divmod enumerate eval execfile filter + syn keyword pythonBuiltinFunc format getattr globals locals hasattr hash help hex id + syn keyword pythonBuiltinFunc input intern isinstance issubclass iter len map max min + syn keyword pythonBuiltinFunc next oct open ord pow property range xrange + syn keyword pythonBuiltinFunc raw_input reduce reload repr reversed round setattr + syn keyword pythonBuiltinFunc slice sorted staticmethod sum vars zip if pymode#Default('g:pymode_syntax_print_as_function', 0) && g:pymode_syntax_print_as_function - syn keyword pythonBuiltinFunc print + syn keyword pythonBuiltinFunc print endif endif " Builtin exceptions and warnings if !pymode#Default('g:pymode_syntax_highlight_exceptions', g:pymode_syntax_all) || g:pymode_syntax_highlight_exceptions - syn keyword pythonExClass BaseException - syn keyword pythonExClass Exception StandardError ArithmeticError - syn keyword pythonExClass LookupError EnvironmentError - syn keyword pythonExClass AssertionError AttributeError BufferError EOFError - syn keyword pythonExClass FloatingPointError GeneratorExit IOError - syn keyword pythonExClass ImportError IndexError KeyError - syn keyword pythonExClass KeyboardInterrupt MemoryError NameError - syn keyword pythonExClass NotImplementedError OSError OverflowError - syn keyword pythonExClass ReferenceError RuntimeError StopIteration - syn keyword pythonExClass SyntaxError IndentationError TabError - syn keyword pythonExClass SystemError SystemExit TypeError - syn keyword pythonExClass UnboundLocalError UnicodeError - syn keyword pythonExClass UnicodeEncodeError UnicodeDecodeError - syn keyword pythonExClass UnicodeTranslateError ValueError VMSError - syn keyword pythonExClass WindowsError ZeroDivisionError - syn keyword pythonExClass Warning UserWarning BytesWarning DeprecationWarning - syn keyword pythonExClass PendingDepricationWarning SyntaxWarning - syn keyword pythonExClass RuntimeWarning FutureWarning - syn keyword pythonExClass ImportWarning UnicodeWarning + syn keyword pythonExClass BaseException + syn keyword pythonExClass Exception StandardError ArithmeticError + syn keyword pythonExClass LookupError EnvironmentError + syn keyword pythonExClass AssertionError AttributeError BufferError EOFError + syn keyword pythonExClass FloatingPointError GeneratorExit IOError + syn keyword pythonExClass ImportError IndexError KeyError + syn keyword pythonExClass KeyboardInterrupt MemoryError NameError + syn keyword pythonExClass NotImplementedError OSError OverflowError + syn keyword pythonExClass ReferenceError RuntimeError StopIteration + syn keyword pythonExClass SyntaxError IndentationError TabError + syn keyword pythonExClass SystemError SystemExit TypeError + syn keyword pythonExClass UnboundLocalError UnicodeError + syn keyword pythonExClass UnicodeEncodeError UnicodeDecodeError + syn keyword pythonExClass UnicodeTranslateError ValueError VMSError + syn keyword pythonExClass WindowsError ZeroDivisionError + syn keyword pythonExClass Warning UserWarning BytesWarning DeprecationWarning + syn keyword pythonExClass PendingDepricationWarning SyntaxWarning + syn keyword pythonExClass RuntimeWarning FutureWarning + syn keyword pythonExClass ImportWarning UnicodeWarning endif " }}} @@ -245,31 +269,40 @@ endif hi def link pythonStatement Statement hi def link pythonInclude Include - hi def link pythonFunction Function + hi def link pythonFunction Function + hi def link pythonClass Type + hi def link pythonParameters Normal + hi def link pythonParam Normal + hi def link pythonBrackets Normal + hi def link pythonClassParameters Normal + hi def link pythonSelf Identifier + hi def link pythonConditional Conditional hi def link pythonRepeat Repeat hi def link pythonException Exception - hi def link pythonOperator Operator + hi def link pythonOperator Operator + hi def link pythonExtraOperator Operator + hi def link pythonExtraPseudoOperator Operator hi def link pythonDecorator Define hi def link pythonDottedName Function hi def link pythonDot Normal - hi def link pythonComment Comment - hi def link pythonCoding Special - hi def link pythonRun Special - hi def link pythonTodo Todo + hi def link pythonComment Comment + hi def link pythonCoding Special + hi def link pythonRun Special + hi def link pythonTodo Todo - hi def link pythonError Error + hi def link pythonError Error hi def link pythonIndentError Error hi def link pythonSpaceError Error - hi def link pythonString String + hi def link pythonString String hi def link pythonUniString String hi def link pythonRawString String hi def link pythonUniRawString String - hi def link pythonEscape Special + hi def link pythonEscape Special hi def link pythonEscapeError Error hi def link pythonUniEscape Special hi def link pythonUniEscapeError Error @@ -280,21 +313,22 @@ endif hi def link pythonStrFormat Special hi def link pythonStrTemplate Special - hi def link pythonDocTest Special - hi def link pythonDocTest2 Special + hi def link pythonDocTest Special + hi def link pythonDocTest2 Special - hi def link pythonNumber Number + hi def link pythonNumber Number hi def link pythonHexNumber Number hi def link pythonOctNumber Number hi def link pythonBinNumber Number - hi def link pythonFloat Float - hi def link pythonOctError Error - hi def link pythonHexError Error - hi def link pythonBinError Error + hi def link pythonFloat Float + hi def link pythonOctError Error + hi def link pythonHexError Error + hi def link pythonBinError Error + hi def link pythonBuiltinType Type hi def link pythonBuiltinObj Structure hi def link pythonBuiltinFunc Function - hi def link pythonExClass Structure + hi def link pythonExClass Structure " }}} From ba340fb74a5d0c316ef77c030a37a7746b0f9bba Mon Sep 17 00:00:00 2001 From: alvinfrancis Date: Mon, 13 May 2013 00:48:29 +0800 Subject: [PATCH 192/513] Fix error on non-ascii characters in docstrings Fix for issue #211: 'Non-ascii characters in docstrings trigger unicode error'. Use _get_encoding to encode docs in show_doc. --- pylibs/ropevim.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 2912a170..4fd15c07 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -268,9 +268,8 @@ def _writedefs(self, locations, filename): def show_doc(self, docs, altview=False): if docs: - vim.command( - 'call pymode#ShowStr("{0}")'.format(docs.replace('"', '\\"')) - ) + docs = docs.encode(self._get_encoding()).replace('"', '\\"') + vim.command('call pymode#ShowStr("{0}")'.format(docs)) def preview_changes(self, diffs): echo(diffs) From b60cdf8601e9533122e2fe61082064fd0f819f47 Mon Sep 17 00:00:00 2001 From: alvinfrancis Date: Mon, 13 May 2013 00:50:11 +0800 Subject: [PATCH 193/513] Use _get_encoding for other instances of encode --- pylibs/ropevim.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pylibs/ropevim.py b/pylibs/ropevim.py index 4fd15c07..9692c828 100644 --- a/pylibs/ropevim.py +++ b/pylibs/ropevim.py @@ -379,8 +379,7 @@ def done(self): def echo(message): - if isinstance(message, unicode): - message = message.encode(vim.eval('&encoding')) + message = message.encode(VimUtils._get_encoding()) print message @@ -388,8 +387,7 @@ def status(message): if _rope_quiet: return - if isinstance(message, unicode): - message = message.encode(vim.eval('&encoding')) + message = message.encode(VimUtils._get_encoding()) vim.command('redraw | echon "{0}"'.format(message)) From 0a226b82555122d526bc9be79ac2474196e19e1f Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 15 May 2013 14:44:16 +0800 Subject: [PATCH 194/513] Update pylama --- pylibs/pylama/__init__.py | 2 +- pylibs/pylama/pep8.py | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index cebb3212..71308acd 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -1,6 +1,6 @@ " pylama -- Python code audit. " -version_info = (0, 3, 1) +version_info = (0, 3, 2) __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/pep8.py b/pylibs/pylama/pep8.py index e586c1d7..602466d9 100644 --- a/pylibs/pylama/pep8.py +++ b/pylibs/pylama/pep8.py @@ -842,19 +842,21 @@ def compound_statements(logical_line): line = logical_line last_char = len(line) - 1 found = line.find(':') - if -1 < found < last_char: + while -1 < found < last_char: before = line[:found] if (before.count('{') <= before.count('}') and # {'a': 1} (dict) before.count('[') <= before.count(']') and # [1:2] (slice) before.count('(') <= before.count(')') and # (Python 3 annotation) not LAMBDA_REGEX.search(before)): # lambda x: x yield found, "E701 multiple statements on one line (colon)" + found = line.find(':', found + 1) found = line.find(';') - if -1 < found: + while -1 < found: if found < last_char: yield found, "E702 multiple statements on one line (semicolon)" else: yield found, "E703 statement ends with a semicolon" + found = line.find(';', found + 1) def explicit_line_join(logical_line, tokens): @@ -1016,8 +1018,6 @@ def readlines(filename): return f.readlines() finally: f.close() - - BOM_UTF8 = '\xef\xbb\xbf' isidentifier = re.compile(r'[a-zA-Z_]\w*').match stdin_get_value = sys.stdin.read else: @@ -1035,8 +1035,6 @@ def readlines(filename): return f.readlines() finally: f.close() - - BOM_UTF8 = '\ufeff' isidentifier = str.isidentifier def stdin_get_value(): @@ -1202,8 +1200,13 @@ def __init__(self, filename=None, lines=None, self.lines = [] else: self.lines = lines - if self.lines and self.lines[0].startswith(BOM_UTF8): - self.lines[0] = self.lines[0][len(BOM_UTF8):] + if self.lines: + ord0 = ord(self.lines[0][0]) + if ord0 in (0xef, 0xfeff): # Strip the UTF-8 BOM + if ord0 == 0xfeff: + self.lines[0] = self.lines[0][1:] + elif self.lines[0][:3] == '\xef\xbb\xbf': + self.lines[0] = self.lines[0][3:] self.report = report or options.report self.report_error = self.report.error From 5e3cb8b9b7042ecf3e006fe6f7f7cfd88ce57125 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 15 May 2013 14:47:05 +0800 Subject: [PATCH 195/513] Update autopep8 --- pylibs/autopep8.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/pylibs/autopep8.py b/pylibs/autopep8.py index 01d79ffe..0cef7623 100644 --- a/pylibs/autopep8.py +++ b/pylibs/autopep8.py @@ -54,7 +54,7 @@ import difflib import tempfile -import pep8 +from pylama import pep8 try: @@ -63,7 +63,7 @@ unicode = str -__version__ = '0.8.7' +__version__ = '0.9' CR = '\r' @@ -83,8 +83,7 @@ ]) -DEFAULT_IGNORE = ','.join([pep8.DEFAULT_IGNORE, - 'W6']) +DEFAULT_IGNORE = 'E24,W6' def open_with_encoding(filename, encoding=None, mode='r'): @@ -716,7 +715,10 @@ def fix_e501(self, result): if self.options.verbose >= 4: print(('-' * 79 + '\n').join([''] + candidates + ['']), - file=sys.stderr) + file=codecs.getwriter('utf-8')(sys.stderr.buffer + if hasattr(sys.stderr, + 'buffer') + else sys.stderr)) for _candidate in candidates: assert _candidate is not None @@ -1877,9 +1879,16 @@ def fix_file(filename, options=None, output=None): fixed_source = original_source - if options.in_place: + if options.in_place or output: encoding = detect_encoding(filename) + if output: + output = codecs.getwriter(encoding)(output.buffer + if hasattr(output, 'buffer') + else output) + + output = LineEndingWrapper(output) + fixed_source = fix_lines(fixed_source, options, filename=filename) if options.diff: @@ -1978,7 +1987,7 @@ def parse_args(args): help='maximum number of additional pep8 passes ' '(default: infinite)') parser.add_option('-a', '--aggressive', action='count', default=0, - help='enable possibly unsafe changes (E711, E712); ' + help='enable non-whitespace changes; ' 'multiple -a result in more aggressive changes') parser.add_option('--exclude', metavar='globs', help='exclude files/directories that match these ' @@ -2066,7 +2075,7 @@ def supported_fixes(): re.sub(r'\s+', ' ', getattr(instance, attribute).__doc__)) - for (code, function) in global_fixes(): + for (code, function) in sorted(global_fixes()): yield (code.upper() + (4 - len(code)) * ' ', re.sub(r'\s+', ' ', function.__doc__)) @@ -2289,13 +2298,7 @@ def main(): else: filenames = args[:1] - output = codecs.getwriter('utf-8')(sys.stdout.buffer - if sys.version_info[0] >= 3 - else sys.stdout) - - output = LineEndingWrapper(output) - - fix_multiple_files(filenames, options, output) + fix_multiple_files(filenames, options, sys.stdout) except KeyboardInterrupt: return 1 # pragma: no cover From 70efc4c18a65a4d4347c28bff477485a5487dcee Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 15 May 2013 14:51:43 +0800 Subject: [PATCH 196/513] Update changelog --- Changelog.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changelog.rst b/Changelog.rst index 2256a6e5..8e1bc6ea 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,11 @@ Changelog ========= +## 2013-05-15 0.6.18 +-------------------- +* Fixed autopep8 (`PyLintAuto`) command; +* Fix error on non-ascii characters in docstrings; + ## 2013-05-03 0.6.17 -------------------- * Update `Pylint` to version 0.28.0; From 11ec7b403416d8ce2e3010bdd181355c73ccfccf Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 15 May 2013 15:19:07 +0800 Subject: [PATCH 197/513] Update docs --- README.rst | 9 ++++ doc/pymode.txt | 135 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) diff --git a/README.rst b/README.rst index 2231f121..4b5f89ff 100644 --- a/README.rst +++ b/README.rst @@ -329,6 +329,15 @@ Default values: :: " Highlight exceptions let g:pymode_syntax_highlight_exceptions = g:pymode_syntax_all + " Highlight equal operator + let g:pymode_syntax_highlight_equal_operator = g:pymode_syntax_all + + " Highlight stars operator + let g:pymode_syntax_highlight_stars_operator = g:pymode_syntax_all + + " Highlight `self` + let g:pymode_syntax_highlight_self = g:pymode_syntax_all + " For fast machines let g:pymode_syntax_slow_sync = 0 diff --git a/doc/pymode.txt b/doc/pymode.txt index 59cb3f94..124fbba0 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -100,6 +100,36 @@ PythonMode. These options should be set in your vimrc. |'pymode_syntax'| Turns off the custom syntax highlighting +|'pymode_syntax_all'| Enable all hightlight groups + +|'pymode_syntax_print_as_function'| Hightlight `print` as function + +|'pymode_syntax_highlight_equal_operator'| Hightlight `=` + +|'pymode_syntax_highlight_stars_operator'| Hightlight `*` + +|'pymode_syntax_highlight_self'| Hightlight `self` + +|'pymode_syntax_indent_errors'| Hightlight indentation errors + +|'pymode_syntax_space_errors'| Hightlight trailing spaces as errors + +|'pymode_syntax_string_formating'| Hightlight string formating + +|'pymode_syntax_string_format'| Hightlight Str.format syntax + +|'pymode_syntax_string_templates'| Hightlight string templates + +|'pymode_syntax_doc_tests'| Hightlight doctests + +|'pymode_syntax_builtin_objs'| Hightlight builtin objects + +|'pymode_syntax_builtin_types'| Hightlight builtin types + +|'pymode_syntax_builtin_functions'| Hightlight builtin functions + +|'pymode_syntax_highlight_exceptions'| Hightlight builtin exceptions + |'pymode_indent'| Enable/Disable pymode PEP8 indentation |'pymode_options'| Set default pymode options for @@ -344,6 +374,111 @@ Default: 1. If this option is set to 0 then the custom syntax highlighting will not be used. +------------------------------------------------------------------------------ + *'pymode_syntax_all'* +Values: 0 or 1. +Default: 1. + +Enabling all hightlight groups. + +------------------------------------------------------------------------------ + *'pymode_syntax_print_as_function'* +Values: 0 or 1. +Default: 0. + +Hightlight `print` as function + +------------------------------------------------------------------------------ + *'pymode_syntax_highlight_equal_operator'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight `=` + +------------------------------------------------------------------------------ + *'pymode_syntax_highlight_stars_operator'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight `*` + +------------------------------------------------------------------------------ + *'pymode_syntax_highlight_self'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight `self` + +------------------------------------------------------------------------------ + *'pymode_syntax_indent_errors'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight indentation errors + +------------------------------------------------------------------------------ + *'pymode_syntax_space_errors'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight trailing spaces as errors + +------------------------------------------------------------------------------ + *'pymode_syntax_string_formating'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight string formating + +------------------------------------------------------------------------------ + *'pymode_syntax_string_format'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight Str.format syntax + +------------------------------------------------------------------------------ + *'pymode_syntax_string_templates'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight string templates + +------------------------------------------------------------------------------ + *'pymode_syntax_string_doctests'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight doctests + +------------------------------------------------------------------------------ + *'pymode_syntax_builtin_objs'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight builtin objects + +------------------------------------------------------------------------------ + *'pymode_syntax_builtin_types'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight builtin types + +------------------------------------------------------------------------------ + *'pymode_syntax_builtin_functions'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight builtin functions + +------------------------------------------------------------------------------ + *'pymode_syntax_highlight_exceptions'* +Values: 0 or 1. +Default: |'pymode_syntax_all'|. + +Hightlight builtin exceptions + ------------------------------------------------------------------------------ *'pymode_indent'* Values: 0 or 1. From 3f75e294a0b2254f0ed8732cf09116011fe9e096 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 15 May 2013 15:20:36 +0800 Subject: [PATCH 198/513] Update changelog --- AUTHORS | 1 + Changelog.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index d072603a..e0bc5bbb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,3 +28,4 @@ Contributors: * Fredrik Henrysson (fhenrysson); * Lowe Thiderman (thiderman); * Naoya Inada (naoina); +* Anler Hp (ikame); diff --git a/Changelog.rst b/Changelog.rst index 8e1bc6ea..e3d57589 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -5,6 +5,7 @@ Changelog -------------------- * Fixed autopep8 (`PyLintAuto`) command; * Fix error on non-ascii characters in docstrings; +* Update python syntax; ## 2013-05-03 0.6.17 -------------------- From cb5d970d4d51ff68546614c119c2f5ab88099848 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 15 May 2013 15:21:16 +0800 Subject: [PATCH 199/513] Update version --- doc/pymode.txt | 2 +- ftplugin/python/init-pymode.vim | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pymode.txt b/doc/pymode.txt index 124fbba0..b2778909 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -6,7 +6,7 @@ (__) (__) (__) (_) (_)(_____)(_)\_) (_/\/\_)(_____)(____/(____) ~ - Version: 0.6.17 + Version: 0.6.18 ============================================================================== CONTENTS *Python-mode-contents* diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index c948f191..dc8d6f56 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -2,7 +2,7 @@ if pymode#Default('g:pymode_init', 1) finish endif -let g:pymode_version = "0.6.17" +let g:pymode_version = "0.6.18" com! PymodeVersion echomsg "Current python-mode version: " . g:pymode_version From c33b13534478944041098cb1f663c2d96cab9897 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 24 May 2013 11:06:48 +0800 Subject: [PATCH 200/513] Add g:pymode_rope_autocomplete_map option. --- README.rst | 3 +++ doc/pymode.txt | 8 ++++---- ftplugin/python/init-pymode.vim | 4 ++++ ftplugin/python/pymode.vim | 4 ++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 4b5f89ff..8d7a58c9 100644 --- a/README.rst +++ b/README.rst @@ -203,6 +203,9 @@ Default values: :: " Load rope plugin let g:pymode_rope = 1 + " Map keys for autocompletion + let g:pymode_rope_autocomplete_map = '' + " Auto create and open ropeproject let g:pymode_rope_auto_project = 1 diff --git a/doc/pymode.txt b/doc/pymode.txt index b2778909..af8917b8 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -539,10 +539,10 @@ To redefine keys, see: |PythonModeOptions| ================ ============================ Key Command ================ ============================ -K Show python docs for current word under cursor -C-Space Rope code assist -r Run current buffer -b Set breakpoints +K Show python docs for current word under cursor (`g:pymode_doc_key`) +C-Space Rope code assist (`g:pymode_rope_autocomplete_map`) +r Run current buffer (`g:pymode_run_key`) +b Set breakpoints (`g:pymode_breakpoint_key`) [[ Jump to previous class or function (normal, visual, operator modes) ]] Jump to next class or function (normal, visual, operator modes) [M Jump to previous class or method (normal, visual, operator modes) diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index dc8d6f56..be4524b5 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -189,6 +189,10 @@ endif if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope + " OPTION: g:pymode_rope_autocomplete_key -- str. Key for the rope + " autocompletion. + call pymode#Default("g:pymode_rope_autocomplete_map", "") + " OPTION: g:pymode_rope_auto_project -- bool. Auto create ropeproject call pymode#Default("g:pymode_rope_auto_project", 1) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 2ca6f33e..cdf77b53 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -96,8 +96,8 @@ if pymode#Option('rope') if g:pymode_rope_map_space let s:prascm = g:pymode_rope_always_show_complete_menu ? "" : "" - exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm - exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm + " exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm + exe "inoremap " . g:pymode_rope_autocomplete_map . " =RopeCodeAssistInsertMode()" . s:prascm endif endif From 7f7317d540c1f30ab289d18aa54889bca1b3ad00 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 29 May 2013 12:00:50 +0800 Subject: [PATCH 201/513] Update pylama --- ftplugin/python/init-pymode.vim | 5 +- ftplugin/python/pymode.vim | 9 +- pylibs/pylama/__init__.py | 2 +- pylibs/pylama/hook.py | 15 +- pylibs/pylama/inirama.py | 269 ++++++++++++++++++++++++++++++++ pylibs/pylama/main.py | 212 +++++++++++++++---------- pylibs/pylama/mccabe.py | 4 +- pylibs/pylama/utils.py | 4 + pylibs/pymode/auto.py | 2 + pylibs/pymode/interface.py | 2 + pylibs/pymode/lint.py | 6 +- pylibs/pymode/queue.py | 2 + 12 files changed, 427 insertions(+), 105 deletions(-) create mode 100644 pylibs/pylama/inirama.py diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index be4524b5..59e287ea 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -191,7 +191,7 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope " OPTION: g:pymode_rope_autocomplete_key -- str. Key for the rope " autocompletion. - call pymode#Default("g:pymode_rope_autocomplete_map", "") + call pymode#Default("g:pymode_rope_autocomplete_map", "") " OPTION: g:pymode_rope_auto_project -- bool. Auto create ropeproject call pymode#Default("g:pymode_rope_auto_project", 1) @@ -237,9 +237,6 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope " OPTION: g:pymode_rope_short_prefix -- string. call pymode#Default("g:pymode_rope_short_prefix", "") - " OPTION: g:pymode_rope_map_space -- string. - call pymode#Default("g:pymode_rope_map_space", 1) - " OPTION: g:pymode_rope_vim_completion -- bool. call pymode#Default("g:pymode_rope_vim_completion", 1) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index cdf77b53..9b85421e 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -94,10 +94,11 @@ if pymode#Option('rope') exe "noremap " . g:pymode_rope_short_prefix . "m :emenu Rope . " inoremap =RopeLuckyAssistInsertMode() - if g:pymode_rope_map_space - let s:prascm = g:pymode_rope_always_show_complete_menu ? "" : "" - " exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm - exe "inoremap " . g:pymode_rope_autocomplete_map . " =RopeCodeAssistInsertMode()" . s:prascm + let s:prascm = g:pymode_rope_always_show_complete_menu ? "" : "" + + exe "inoremap " . g:pymode_rope_autocomplete_map . " =RopeCodeAssistInsertMode()" . s:prascm + if tolower(g:pymode_rope_autocomplete_map) == '' + exe "inoremap =RopeCodeAssistInsertMode()" . s:prascm endif endif diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index 71308acd..c0426772 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -1,6 +1,6 @@ " pylama -- Python code audit. " -version_info = (0, 3, 2) +version_info = 0, 3, 7 __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/hook.py b/pylibs/pylama/hook.py index c6884d5c..93152041 100644 --- a/pylibs/pylama/hook.py +++ b/pylibs/pylama/hook.py @@ -1,7 +1,10 @@ +from __future__ import unicode_literals, print_function, absolute_import + import sys from os import path as op, chmod from subprocess import Popen, PIPE -from .main import logger + +from .main import LOGGER try: @@ -20,7 +23,7 @@ def run(command): def git_hook(): from .main import check_files _, files_modified, _ = run("git diff-index --cached --name-only HEAD") - logger.setLevel('WARN') + LOGGER.setLevel('WARN') check_files([f for f in map(str, files_modified) if f.endswith('.py')]) @@ -36,7 +39,7 @@ def hg_hook(ui, repo, **kwargs): seen.add(file_) if file_.endswith('.py'): paths.append(file_) - logger.setLevel('WARN') + LOGGER.setLevel('WARN') check_files(paths) @@ -78,11 +81,11 @@ def install_hook(path): git = op.join(path, '.git', 'hooks') hg = op.join(path, '.hg') if op.exists(git): - install_git(git) and logger.warn('Git hook has been installed.') # nolint + install_git(git) and LOGGER.warn('Git hook has been installed.') # nolint elif op.exists(hg): - install_hg(git) and logger.warn('Mercurial hook has been installed.') # nolint + install_hg(git) and LOGGER.warn('Mercurial hook has been installed.') # nolint else: - logger.error('VCS has not found. Check your path.') + LOGGER.error('VCS has not found. Check your path.') sys.exit(1) diff --git a/pylibs/pylama/inirama.py b/pylibs/pylama/inirama.py new file mode 100644 index 00000000..7887dafa --- /dev/null +++ b/pylibs/pylama/inirama.py @@ -0,0 +1,269 @@ +""" + Parse INI files. + +""" +from __future__ import unicode_literals, print_function, absolute_import + +import io +import re +import logging +from collections import MutableMapping +try: + from collections import OrderedDict +except ImportError as e: + from ordereddict import OrderedDict + + +__version__ = '0.2.9' +__project__ = 'Inirama' +__author__ = "Kirill Klenov " +__license__ = "BSD" + + +NS_LOGGER = logging.getLogger('inirama') + + +class Scanner(object): + + def __init__(self, source, ignore=None, patterns=None): + """ Init Scanner instance. + + :param patterns: List of token patterns [(token, regexp)] + :param ignore: List of ignored tokens + """ + self.reset(source) + if patterns: + self.patterns = [] + for k, r in patterns: + self.patterns.append((k, re.compile(r))) + + if ignore: + self.ignore = ignore + + def reset(self, source): + """ Reset scanner. + + :param source: Source for parsing + """ + self.tokens = [] + self.source = source + self.pos = 0 + + def scan(self): + """ Scan source and grab tokens. + """ + self.pre_scan() + + token = None + end = len(self.source) + + while self.pos < end: + + best_pat = None + best_pat_len = 0 + + # Check patterns + for p, regexp in self.patterns: + m = regexp.match(self.source, self.pos) + if m: + best_pat = p + best_pat_len = len(m.group(0)) + break + + if best_pat is None: + raise SyntaxError( + "SyntaxError[@char {0}: {1}]".format( + self.pos, "Bad token.")) + + # Ignore patterns + if best_pat in self.ignore: + self.pos += best_pat_len + continue + + # Create token + token = ( + best_pat, + self.source[self.pos:self.pos + best_pat_len], + self.pos, + self.pos + best_pat_len, + ) + + self.pos = token[-1] + self.tokens.append(token) + + def pre_scan(self): + """ Prepare source. + """ + pass + + def __repr__(self): + """ Print the last 5 tokens that have been scanned in + """ + return '" + + +class INIScanner(Scanner): + patterns = [ + ('SECTION', re.compile(r'\[[^]]+\]')), + ('IGNORE', re.compile(r'[ \r\t\n]+')), + ('COMMENT', re.compile(r'[;#].*')), + ('KEY', re.compile(r'[\w_]+\s*[:=].*'))] + + ignore = ['IGNORE'] + + def pre_scan(self): + escape_re = re.compile(r'\\\n[\t ]+') + self.source = escape_re.sub('', self.source) + + +undefined = object() + + +class Section(MutableMapping): + + def __init__(self, namespace, *args, **kwargs): + super(Section, self).__init__(*args, **kwargs) + self.namespace = namespace + self.__storage__ = dict() + + def __setitem__(self, name, value): + self.__storage__[name] = str(value) + + def __getitem__(self, name): + return self.__storage__[name] + + def __delitem__(self, name): + del self.__storage__[name] + + def __len__(self): + return len(self.__storage__) + + def __iter__(self): + return iter(self.__storage__) + + def __repr__(self): + return "<{0} {1}>".format(self.__class__.__name__, str(dict(self))) + + def iteritems(self): + for key in self.__storage__.keys(): + yield key, self[key] + + items = lambda s: list(s.iteritems()) + + +class InterpolationSection(Section): + + var_re = re.compile('{([^}]+)}') + + def get(self, name, default=None): + if name in self: + return self[name] + return default + + def __interpolate__(self, math): + try: + key = math.group(1).strip() + return self.namespace.default.get(key) or self[key] + except KeyError: + return '' + + def __getitem__(self, name): + value = super(InterpolationSection, self).__getitem__(name) + sample = undefined + while sample != value: + try: + sample, value = value, self.var_re.sub( + self.__interpolate__, value) + except RuntimeError: + message = "Interpolation failed: {0}".format(name) + NS_LOGGER.error(message) + raise ValueError(message) + return value + + +class Namespace(object): + + default_section = 'DEFAULT' + silent_read = True + section_type = Section + + def __init__(self, **default_items): + self.sections = OrderedDict() + for k, v in default_items.items(): + self[self.default_section][k] = v + + @property + def default(self): + """ Return default section or empty dict. + """ + return self.sections.get(self.default_section, dict()) + + def read(self, *files, **params): + """ Read and parse INI files. + """ + for f in files: + try: + with io.open(f, encoding='utf-8') as ff: + NS_LOGGER.info('Read from `{0}`'.format(ff.name)) + self.parse(ff.read(), **params) + except (IOError, TypeError, SyntaxError, io.UnsupportedOperation): + if not self.silent_read: + NS_LOGGER.error('Reading error `{0}`'.format(ff.name)) + raise + + def write(self, f): + """ + Write self as INI file. + + :param f: File object or path to file. + """ + if isinstance(f, str): + f = io.open(f, 'w', encoding='utf-8') + + if not hasattr(f, 'read'): + raise AttributeError("Wrong type of file: {0}".format(type(f))) + + NS_LOGGER.info('Write to `{0}`'.format(f.name)) + for section in self.sections.keys(): + f.write('[{0}]\n'.format(section)) + for k, v in self[section].items(): + f.write('{0:15}= {1}\n'.format(k, v)) + f.write('\n') + f.close() + + def parse(self, source, update=True, **params): + """ Parse INI source. + """ + scanner = INIScanner(source) + scanner.scan() + + section = self.default_section + + for token in scanner.tokens: + if token[0] == 'KEY': + name, value = re.split('[=:]', token[1], 1) + name, value = name.strip(), value.strip() + if not update and name in self[section]: + continue + self[section][name] = value + + elif token[0] == 'SECTION': + section = token[1].strip('[]') + + def __getitem__(self, name): + """ Look name in self sections. + """ + if not name in self.sections: + self.sections[name] = self.section_type(self) + return self.sections[name] + + def __repr__(self): + return "".format(self.sections) + + +class InterpolationNamespace(Namespace): + + section_type = InterpolationSection + +# lint_ignore=W0201,R0924,F0401 diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index aebad2d4..20f68218 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -1,50 +1,55 @@ +from __future__ import ( + unicode_literals, print_function, absolute_import, with_statement +) + import fnmatch +import logging import re import sys -from os import getcwd, walk, path as op - -import logging from argparse import ArgumentParser +from os import getcwd, walk, path as op -from . import utils +from . import utils, version +from .inirama import Namespace -default_linters = 'pep8', 'pyflakes', 'mccabe' -default_complexity = 10 -logger = logging.Logger('pylama') -stream = logging.StreamHandler() -logger.addHandler(stream) +DEFAULT_LINTERS = 'pep8', 'pyflakes', 'mccabe' +DEFAULT_COMPLEXITY = 10 +LOGGER = logging.Logger('pylama') +STREAM = logging.StreamHandler() +LOGGER.addHandler(STREAM) SKIP_PATTERN = '# nolint' -def run(path, ignore=None, select=None, linters=default_linters, **meta): # nolint +def run(path, ignore=None, select=None, linters=DEFAULT_LINTERS, config=None, + **meta): errors = [] ignore = ignore and list(ignore) or [] select = select and list(select) or [] - for lint in linters: - try: - linter = getattr(utils, lint) - except AttributeError: - logging.warning("Linter `{0}` not found.".format(lint)) - continue + try: + with open(path, 'rU') as f: + code = f.read() + '\n\n' + params = config or parse_modeline(code) + params['skip'] = [False] + for line in code.split('\n'): + params['skip'].append(line.endswith(SKIP_PATTERN)) - try: - with open(path, "rU") as f: - code = f.read() + '\n\n' - params = parse_modeline(code) - params['skip'] = [False] - for line in code.split('\n'): - params['skip'].append(line.endswith(SKIP_PATTERN)) + if params.get('lint_ignore'): + ignore += params.get('lint_ignore').split(',') - if params.get('lint_ignore'): - ignore += params.get('lint_ignore').split(',') + if params.get('lint_select'): + select += params.get('lint_select').split(',') - if params.get('lint_select'): - select += params.get('lint_select').split(',') + if params.get('lint'): + for lint in linters: + try: + linter = getattr(utils, lint) + except AttributeError: + logging.warning("Linter `{0}` not found.".format(lint)) + continue - if params.get('lint'): result = linter(path, code=code, **meta) for e in result: e['col'] = e.get('col') or 0 @@ -57,27 +62,27 @@ def run(path, ignore=None, select=None, linters=default_linters, **meta): # nol if not params['skip'][e['lnum']]: errors.append(e) - except IOError as e: - errors.append(dict( - lnum=0, - type='E', - col=0, - text=str(e) - )) - except SyntaxError as e: - errors.append(dict( - lnum=e.lineno or 0, - type='E', - col=e.offset or 0, - text=e.args[0] - )) - break - - except Exception as e: - import traceback - logging.error(traceback.format_exc()) - - errors = [e for e in errors if _ignore_error(e, select, ignore)] + except IOError as e: + errors.append(dict( + lnum=0, + type='E', + col=0, + text=str(e) + )) + + except SyntaxError as e: + errors.append(dict( + lnum=e.lineno or 0, + type='E', + col=e.offset or 0, + text=e.args[0] + )) + + except Exception: + import traceback + logging.error(traceback.format_exc()) + + errors = [er for er in errors if _ignore_error(er, select, ignore)] return sorted(errors, key=lambda x: x['lnum']) @@ -92,11 +97,14 @@ def _ignore_error(e, select, ignore): def shell(): + curdir = getcwd() parser = ArgumentParser(description="Code audit tool for python.") - parser.add_argument("path", nargs='?', default=getcwd(), + parser.add_argument("path", nargs='?', default=curdir, help="Path on file or directory.") parser.add_argument( "--verbose", "-v", action='store_true', help="Verbose mode.") + parser.add_argument('--version', action='version', + version='%(prog)s ' + version) split_csp_list = lambda s: list(set(i for i in s.split(',') if i)) @@ -108,7 +116,7 @@ def shell(): type=split_csp_list, help="Select errors and warnings. (comma-separated)") parser.add_argument( - "--linters", "-l", default=','.join(default_linters), + "--linters", "-l", default=','.join(DEFAULT_LINTERS), type=split_csp_list, help="Select linters. (comma-separated)") parser.add_argument( @@ -120,61 +128,92 @@ def shell(): type=lambda s: [re.compile(fnmatch.translate(p)) for p in s.split(',')], help="Skip files by masks (comma-separated, Ex. */messages.py)") - parser.add_argument("--complexity", "-c", default=default_complexity, + parser.add_argument("--complexity", "-c", default=DEFAULT_COMPLEXITY, type=int, help="Set mccabe complexity.") parser.add_argument("--report", "-r", help="Filename for report.") parser.add_argument("--hook", action="store_true", help="Install Git (Mercurial) hook.") - args = parser.parse_args() - - # Setup logger - logger.setLevel(logging.INFO if args.verbose else logging.WARN) - if args.report: - logger.removeHandler(stream) - logger.addHandler(logging.FileHandler(args.report, mode='w')) - - if args.hook: + parser.add_argument( + "--options", "-o", default=op.join(curdir, 'pylama.ini'), + help="Select configuration file. By default is '/pylama.ini'") + options = parser.parse_args() + actions = dict((a.dest, a) for a in parser._actions) + + # Setup LOGGER + LOGGER.setLevel(logging.INFO if options.verbose else logging.WARN) + if options.report: + LOGGER.removeHandler(STREAM) + LOGGER.addHandler(logging.FileHandler(options.report, mode='w')) + + # Read options from configuration file + config = Namespace() + config.default_section = 'main' + LOGGER.info('Try to read configuration from: ' + options.options) + config.read(options.options) + for k, v in config.default.items(): + action = actions.get(k) + if action: + LOGGER.info('Find option %s (%s)' % (k, v)) + name, value = action.dest, action.type(v)\ + if callable(action.type) else v + setattr(options, name, value) + + # Install VSC hook + if options.hook: from .hook import install_hook - return install_hook(args.path) + return install_hook(options.path) - paths = [args.path] + paths = [options.path] - if op.isdir(args.path): + if op.isdir(options.path): paths = [] - for root, _, files in walk(args.path): + for root, _, files in walk(options.path): paths += [op.join(root, f) for f in files if f.endswith('.py')] check_files( paths, - rootpath=args.path, - skip=args.skip, - frmt=args.format, - ignore=args.ignore, - select=args.select, - linters=args.linters, - complexity=args.complexity, + rootpath=options.path, + skip=options.skip, + frmt=options.format, + ignore=options.ignore, + select=options.select, + linters=options.linters, + complexity=options.complexity, + config=config, ) def check_files(paths, rootpath=None, skip=None, frmt="pep8", - select=None, ignore=None, linters=default_linters, - complexity=default_complexity): + select=None, ignore=None, linters=DEFAULT_LINTERS, + complexity=DEFAULT_COMPLEXITY, config=None): rootpath = rootpath or getcwd() pattern = "%(rel)s:%(lnum)s:%(col)s: %(text)s" if frmt == 'pylint': pattern = "%(rel)s:%(lnum)s: [%(type)s] %(text)s" + params = dict() + if config: + for key, section in config.sections.items(): + if key != 'main': + params[op.abspath(key)] = prepare_params(section) + errors = [] - for path in skip_paths(skip, paths): - logger.info("Parse file: %s" % path) - errors = run(path, ignore=ignore, select=select, - linters=linters, complexity=complexity) + + for path in paths: + path = op.abspath(path) + if any(pattern.match(path) for pattern in skip): + LOGGER.info('Skip path: %s' % path) + continue + + LOGGER.info("Parse file: %s" % path) + errors = run(path, ignore=ignore, select=select, linters=linters, + complexity=complexity, config=params.get(path)) for error in errors: try: error['rel'] = op.relpath( error['filename'], op.dirname(rootpath)) error['col'] = error.get('col', 1) - logger.warning(pattern, error) + LOGGER.warning(pattern, error) except KeyError: continue @@ -185,13 +224,6 @@ def check_files(paths, rootpath=None, skip=None, frmt="pep8", r'^\s*#\s+(?:pymode\:)?((?:lint[\w_]*=[^:\n\s]+:?)+)', re.I | re.M) -def skip_paths(skip, paths): - for path in paths: - if skip and any(pattern.match(path) for pattern in skip): - continue - yield path - - def parse_modeline(code): seek = MODERE.search(code) params = dict(lint=1) @@ -201,5 +233,13 @@ def parse_modeline(code): return params +def prepare_params(section): + params = dict(section) + params['lint'] = int(params.get('lint', 1)) + return params + + if __name__ == '__main__': shell() + +# lint_ignore=R0914,C901,W0212 diff --git a/pylibs/pylama/mccabe.py b/pylibs/pylama/mccabe.py index 432f4c1e..c95f561d 100644 --- a/pylibs/pylama/mccabe.py +++ b/pylibs/pylama/mccabe.py @@ -3,7 +3,9 @@ http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html MIT License. """ -from __future__ import with_statement +from __future__ import ( + unicode_literals, print_function, absolute_import, with_statement +) import sys diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py index ec98e4e2..51ab1d4d 100644 --- a/pylibs/pylama/utils.py +++ b/pylibs/pylama/utils.py @@ -1,3 +1,7 @@ +from __future__ import ( + unicode_literals, print_function, absolute_import, with_statement +) + import _ast from os import path as op, environ diff --git a/pylibs/pymode/auto.py b/pylibs/pymode/auto.py index 16d7b811..f353b832 100644 --- a/pylibs/pymode/auto.py +++ b/pylibs/pymode/auto.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import + import vim from autopep8 import fix_file diff --git a/pylibs/pymode/interface.py b/pylibs/pymode/interface.py index 3134882d..8e70d331 100644 --- a/pylibs/pymode/interface.py +++ b/pylibs/pymode/interface.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import + import vim diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index 6746332f..dbc164e6 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import + import locale from pylama.main import run @@ -47,12 +49,10 @@ def run_checkers(checkers=None, ignore=None, buf=None, select=None, complexity=None, callback=None): filename = buf.name - result = [] - pylint_options = '--rcfile={0} -r n'.format(get_var('lint_config')).split() return run(filename, ignore=ignore, select=select, linters=checkers, - pylint=pylint_options, complexity=complexity) + pylint=pylint_options, complexity=complexity) def parse_result(result, buf=None, **kwargs): diff --git a/pylibs/pymode/queue.py b/pylibs/pymode/queue.py index d1d087ae..5aa5c26d 100644 --- a/pylibs/pymode/queue.py +++ b/pylibs/pymode/queue.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import + import threading from Queue import Queue, Empty From b845ed0a11ac49ed8cf7f8800548b87f97fd0575 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 29 May 2013 12:04:47 +0800 Subject: [PATCH 202/513] Update Changelog --- Changelog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog.rst b/Changelog.rst index e3d57589..f6fdb811 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,9 @@ Changelog ========= +* Added `g:pymode_rope_autocomplete_map` option; +* Removed `g:pymode_rope_map_space` option; + ## 2013-05-15 0.6.18 -------------------- * Fixed autopep8 (`PyLintAuto`) command; From 02f5ae248d75b17b4993be55fd0a9aaa027890c4 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 29 May 2013 12:06:26 +0800 Subject: [PATCH 203/513] Added a simple logo --- README.rst | 5 +++-- logo.png | Bin 0 -> 62060 bytes 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 logo.png diff --git a/README.rst b/README.rst index 8d7a58c9..deb602c5 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -Python-mode, Python in VIM -########################## +|logo| Python-mode, Python in VIM +################################# Python-mode is a vim plugin that allows you to use the pylint_, rope_, pydoc_, pyflakes_, pep8_, mccabe_ libraries in vim to provide features like python code looking for bugs, refactoring and some other useful things. @@ -524,3 +524,4 @@ My address is here: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Kle .. _pathogen: https://github.com/tpope/vim-pathogen .. _pep8: http://pypi.python.org/pypi/pep8 .. _mccabe: http://en.wikipedia.org/wiki/Cyclomatic_complexity +.. |logo| image:: https://raw.github.com/klen/python-mode/develop/logo.png diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..908cc2250133602cba3058cd8742c1f9e2366a7a GIT binary patch literal 62060 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¨G+hcBD571pOalIsGwZj*3SjuAXfd;0K-7R6W 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�OmmOZRZ%b9DVA3s*V>A0(`R`k=c#={WVs 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 literal 0 HcmV?d00001 From 8d5c556aa8fdb3e177abf1a61587d4cc1a1668fb Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Wed, 29 May 2013 14:46:41 +0800 Subject: [PATCH 204/513] Change logo size --- logo.png | Bin 62060 -> 16429 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/logo.png b/logo.png index 908cc2250133602cba3058cd8742c1f9e2366a7a..b873f5cbeecd98729f2c9759dc43f975640fdf70 100644 GIT binary patch literal 16429 zcmb`ubyOY8wmynO&|twOxVt;S-QC?~;jY2mU4px_a19dNU4t()xWkXV@44shckYva z-WsDa=qA4fKW8z@PU}WlGY{uYW=lCHF2FB;X^Kolu=3+$bVP|Xa z%;UjN^0x%f$NgVqMiSz`MOF0|1mKB@iTRvN&3TkX#s8)L z@x)JJ>EhzZ!^r6F?#|%O%HZH+!N|}l^} zP~`iFkw?zK)XLmb)X2q5fQ5;fm7a;6o`qYLnT>~uorjBwkMTcb{>SIv+9FP7MlKFc zstyje0*Y21X0~>JJrc7pu>60?d4j|BLpI`2OZ;DVsSv*t-5L&-oG5|04dUf{25iqm!Al z^Is0w|0VxV+JDObk9{?JE0=%Q=|9N-sipZpe1U&g>z~AbDg6HiwwZ_3{{z^6asD^h ze^Gho4U==c_Y6f9K2}2jb!AU1kblq>R z^-tbEZ+Twdoi)66>7~}eo@X_%x}UE<=Q&UEACBWp-TTrOcY60;cV2T8byg@jgu& z=euG%4*C%pl+iO5pUqJ)*T&RmEb3X#beZ|)vlwA!+D02;Tog)8D)P)a>CwOdB-PJ_*bT6Cwpb5 z#9Oa7Yu-mT}Om(Bg0fZH-MM1BJhu&RvrI7$gDBxR|ShQ2{Q5aXiCpA=|rHY7bxB-ke~!xMsg4YZ@|dD;q~<7Tz7;viNY6 zYt~leT&~8XJ{8)#9XV!t`XyFpwN8t_d#9iAgA1knOwB&eLXl_;>8Lh4W|8Rvu3+Q-C7aOWY^%)O_*xl+rV2L3nkBm;hdK>ZIcjeCoh&*eh{K1IJl1GIrg=jLPi}MWXNz;TcF(e0i0ywK6 z*x90zyAxL3et`Q-Jvojt332c?yr=6Vo5tQ+c&RMf@m_RZp90tH8=IQgmik;;i%Uz_ zeuz*_S5@L8!O!=OYap+=Jun@=_XPvpFZ9>ioJ;N2fD;@Jo3?nI_D!xQiz_8^S+>u2 zXXgSeOP@E;B=96WgRiJ>&&@o?r)9PP*E2cnteb6? z+CDWX|$VAM8G)zEIu@w0+axaU($Q9j#ent3$$Nt4mT4SgC0{z-^h&w<3;;98ICM z@V+~(A4#HIOQu$_eZ1NSnhYbu5(idmH(RUs`1#swv^TpR&yTD&S?%6MIDazz`pMF| zN7~7$!KU$84``bqm&rCF9*b|qYCi6`jW`uSN7?W~aL+|C_J-D?l^A%7-F2(z=Or8L zwWR01!hgTMD=MQDAd{cT?OekDbW~`cq0upF82q`Hq4UqTj+d)3f;7z1(Q-S8ETNJH z|98JER*P8Qn;8iX+hrz7X_K1Lm4n7)iEM2ljH|NF2{U5*9Em$XDPw)7^nut z_2c&1p0{@2hu>lH--og}6K}J?)5gD$$4b-HT^&>N^dJx46usm+CC1h&6^0@a@+||x zG-nEX21RxRS~k5ZWAG^TaA~3lDtR>>)K)qxxDduX%&kBN7kw~5oX`d*lvEm*vo4$+ zg;FGJre42mJWisK1ZPmICVboSM>TB}_%6T8TcHh&YW8XG-cP0~^Ol8pB8AHIkXy5&K0EQ?Pa8!bE{j}XUgqAnrGnR)> zVEq1NV6(~2ys$399>u)zDU0hk7)2mGDUmQcZ=!a!-uvF(`))tY)A2 zIrhm~sY5pM1qP(3)wqSkNLI|rsDw_Kl}CQNmM3YT1qV?e{L&H2twS79eHz$ z&4adlu4B$tn`z5RgvG#=7i5L~AIT#IBhAJ;y16eNk^vSYvX_0ZL|n+pTh{%%1H$Y! zotHy+8?s#MQ&${>B~d28xZ`7HBs!i3#C;sEKWRpc>ebpQmXIVRXogd>P=+rLmqrWb zIiIZ{U^S^ff6|?}RF7H{lk830?_$bUyQi2Wzm~q(gr9aKlxHws@H|DW}_0AS~d$6YCl>7jw8qe zo!?#_fqt(~hVJa+@DtlSVF<)9CbWErBqu49*`Jkge|$819(4tH_d{>$nuRuJi;kU0 zTH~oloY$Wru?z&xy#;fb(`LccSl$C-uB04Y%7wT!SdUpB!xKZ&b5Y#}Er6y)PPM26 zj3%%$zBQ7KX%10I;2TKB+TBPBJ>{gPW0FQAR=O9xtWv}1L}uP|7^4mw?BJZCs7N6L z-mLoKGu;^=2w6lRs1fm>6A;1A&U3kY|> zQz>Y7W=nJ2w3k`b{H$65O{v3(bw@8MM@7GrC-<#Yn;JK}7I?NCQ>&l75{gTqznrJi z>uBfjxbDEDwl1tT+vHHWK#~fL=n|uU<0G@zfUMErPA8D|Oe4fH;h<7I<9T0qo{Hgb zVSH8N+NTGmaxKPvGMte*bdD3DrcS|CYq=J9%xG@EA>%cLwS9dUoLRg0F#4n9&kQ^X z?bV!=EOqRt5`k(e3^rRa<)BDrxjL9?I*}-v34}&xFWS=aVzo{cm#x?>b^Fjv0U8RO zkSl#oVG+J-WlOmOmLJD-=H1yZ9kv1Xvn>m}61teX9AhAwnX87SY_fJ=HCt8`x<-uv zpyvv_uBlo`=$zTByua!f9f?HC<+>PUC!Np*T6Nrpv<=D1(z)?KpdRodMUccHt}& zq7+_#Li^#%&{%Pn-r)%NM!m`Ksc|~|d>gLmyivz9rm`wQ8X5{8D8GTBm+LjbVW}YBY94is56?$DdmVv3kpIj(;(NGUlE9e>Ny&v+GDo{fYZ#J); zRUQg7s5QH#Yox&{@r=Gm4uDfyRaAo~DzOs6Z*bvp*uqtm$Ew;#Qbo^1?38ceh<^e@ zJgL-avG3JUXPS+6W<`E+2H&cSCljpykZ!JA-PaxgkYL$Xw&|^e}8opXOk6SF#SSCZ> z^(9vQ`kKyeow|HfC?SEV=_2l6ua{jR6|$OAeUZhf6&2Oa8VgI0^YijRi`I6pGT2UM z%;lO!0x>zWqL%#BLyt)a7aLGTr(hH4tqSQ}ga;c|@7zNMr&(dHHssM_x5i<=Yw1yF z-J>5F^W$OJT~;hai{JVHhUc5Gl?atw1zV-rAxaxBjH;R4*#I=hOA3mu4sWjbEP~hu zHQ1Oz&}>b-p`;%R|Klai`Hs5HV%5mb5V+=LJ3yEAF~P4H1=K`A`lZMB`R>zPZBeNh zDRYp`+joI(_D&xFV;ubOPUy39p?|4r)yj__IoEFl>ZK-)aHTXhV?F6+2EI=rXBWCN zd>F^F!Ih9tm_OI&D+^LFrie=ImDj3Os`syR z?WnRuhwgE+inOMr7xxgG^nVR&a-(Ge0jigOWR9AC?$zLaq$TO zH5j!cah+>4<#VlHaiCIJoxYRf-a6Mf9J-nL^h4wv!zRJ}R4pHL1Xv19vLBT_o|7P; zGBReIr+Y{1`nhnsJwoLDr{NxM#wHj$P&D?>rv>)R~4HYaXuw0EX2 z@*d`Oa(bM@7_x_E9=t9d=Ce)y!b97Vh~~`j<^lqFIuPm|r>m_1wBlCUu0832 z?Gi%@+N`xtl-cpWIYupb8+oJOTqC5rpN=b!@@&wqO9O9F{mybPt8VkaHJE18>b#i_ zO`T}O6=b)i6Ck4S$`o`2OLS|rW+LMBjl15xsuK$Gkk@rs<$>g0a6YR9sQ~!XRO#;RjVFl|;diCDYkR zL)|SMUV>vJsjm}|qSk}(t~pIU0VMF4w6KrMEf;n2VnP$U=zg|`k-W4!=O$k;@7x^V zh{J3Rn*Tjve_eIWYuc3X!gTTtwN>6tN9>OZS#Lq##=N6+T!R{|W>1SfkU@}l82V63 zYOBj7DaPZ9Uq>tL{`gp|3go$wo%@8nuOU8jyih4|8`&v81-d{B_FBD#X(kOprksr- zoxJDIU&T$w`4F)1eY%kf$DThikVa#%o-d1DBQ5>TwhEbpyF4DJV*AR(KP2F*P?0;v zcOsqpbcMA3yZi;&tZH(CiyL6`3HA#Tf6_sON^^~7OGVD}@0`VhV--ke2+z5eNiDOV zdCzfnmVVQn$B#EZLFF2%LeY4o8nyajheH5|U9tc(vDLGUad(9BX0w* zDlMhtmUc0Naq+wSFrPVMy)P7&WT5)0U&`0&44~aOo1c#>YbQVJ0CYuas_wV^1$X7_ z&Idc{zIq5V$SNY;GiB>#PWe+xeKh_m%#SAyQB=(&wyw* zNlb<_b!(P$YstsW@|NGQFNJIzSyIjV!}srX-uCY~c5?cFl_D#aEN?zrkWVSKwgWKjfQ=~Iud)#$C zJxIaTSxyHx?Gq)#BPlr8VO{o%OaNmFcN~6Gs@U$;P1*TlRe_YXA$|6G9y9NTTLBpJ zX>hv*=Bm;(mDuhQw@UJRLsH)}gi(IRv-_}s$sL^xwT%8G2eg{=W%ko`sY zadU`_`@@vWkHP--DR@gk`!I_)S`M+zwcUil>;#W_IqdwkcQQ1XS$MGaDR7+fo}$}O zHx&S!_LSKO+RU$nm-LFD>RebtkKTjH9K4(C*3;&Rprw1~-6jZ)xhvq1gIQy4nRyro z?6x#TZ2k3`)Z*S<*#nW}U@a>!`zF<9fMw~e!pM8rwN~vTw~o;( zy?9gZ|GZ8yo?#3kaH=mk*;@~fZCq#tnEnjHSjvF|e0$#T4*p1WJ~6Svsj|>_w$O}b zn3-va3xS+N$N?IF#pm-5MpCn4nhtDqZ!Dkra z^t>9!B1(>WQ|Bm0g*@$|MrDeJSJUj#^9YQ$ zs&isMoG3Q!G{0h|)m}m#mPvM0tZLZJP&TmIax&MpZf?e6(0S}PWQxl9ib6hm0D7Qw z=(ImWR5#OCkt35h{sV|7_>=yGf|_`2v(QOX?IVpTGbB32Zic{0H=MQA&pJy6$5%*0 zV7lKlvxfO>uCcfbKVLh6yOTMKUavnfm-b*{YwAk&`@rp^t&h5mqNi!sme%$MM9}SR z{U(659Zw=!J?)aoX5Sba`;(zE*8e&g16fBx#u+Pw%2tq-x9**z}i79+mOp4p9 z$0NqI^H#R0oDElf2-$X7%Jyj(Sg@*&nD?PTl`=IvngVXpTicNPLEgxL!Qwt?q9 zruW<9VU8e^_G8=aM8~%&&Lu{=uzkKb&(TdcIlHHxQSCIn%IZ5D+3@m4EB~p{g&jAT zhg9GKkK+&2=+|b)^@tgcz4}B4L#6uhcL=q}M5miq4gV|3>{DA+v+bOYR6C#&qp#@+ z-YF{zKkZi)H>7nIb6anewzc>O;Xc6SWYss$$MHD_}ICtYi3B^C}8$@t~j}=^Da7X@-*0JU^bF^2G>a;;x?P*OUD< z)RVXuYa*(@$xGJ)CpAHWG$sV@wM{TsQX+R%Z-cG=@iL7yw`aZ9X*&LJ*heU>rZ1C8 zSI~4c$t&-6(t70kTqij?dT-x}IS6c?))(&f1qJG>=`EG9vvLoMuK{2tI4461*Xs7! zLCEThv{I<4c$Cow4blnr@!;y&l0yA2yD$oYo|><|6SA1Fz(5lW^paNZu2D>pnZ3M zU#hEYVRy|g`|?0`0_Wi;>L|OtH4!@v9kf>CR$4Zj$yH^fn#~>^sNu-?n@Z7mF7=Wk zYe&)dN3lT5qy|)Fy4HSQZaM|-eTD~bKCwX?e&3Tqopm%xkX0~}4sI(%del||BL2IdKEp{Z7 zs$t2}oxbvZ;%tz7fONt8UJaVm?>)u zi!R?vHG9RM)W?x>23!xkc=wp0Qw% z45`y%cLp&t=@j8wk7>b$vPBIOQVdqo%?>`N&wnW{l9iKl)q*m6Qp{Nil)eoYsbHXo zw$9+DkWDu<@Ow17Gk8>v|E)!J)P=)atUzNdWQO}AJjHJXzV~(<%|_6Xv4Q5(WKhbR z$kf4R?AV|D!oFO3pj$futVi;PA`za~85Y6eH$+^cg>-Ekf zi|q`Lw`$oXBC3G+__p!m6*3iZuR;M4vrg+2VTnd8WLc+{OveG0YA=;d4wY6z&roQW zFu73wH64nWfbmb6rA~lK7nS98}wq18$On8>ot(ci~Tv6YwD|U&hqQrXor$Y0{ zBw2g}p45D3wp1faAQ@Do5LzM_xnl*r_E3-t4p_>Qk`=`*&0>LAlr9e$NI}WpR8y%n zIYPt%>JkU@0!mb0EmTyuJ=~@IPlG3eRq#?>mEH0;;=ZJ@>g%qxDj$>8NUZt|1fLv4 zNBYNL$fcBdwIqQj>eu?ORh&-=6mP^C)OH46G*0exZbAiL$rK5jgEMA08IfAF*1q%) zfvJg#f)qYc;^6x)TfH^vMge7@kO+9#Xx!kv`2(d%6i~CB_CkwZcO=ogwP`xZp)&76 zjAe(z^3h7smr!BVa&}}h2lt~2M{S8;%bi`XF%pP(t9EtlobafYNq}>)$gLS0j5Kf!qFnntLJht&%H2dE~2(_y~&Z7O#TIxjXXwdEM*no6Z73 z7x8m>vu>v?m;MB{2y+@bx%Hq5cKz82Ds8sekx?@Sq8~78C2Onzmc%c;Z|Pi?ZLb@uT6M##tYW(=Gvd}iaZ>HI1g2Z=PWpwp ze4=BcMP#iSR_&KGqvM3Ec`+xieqoaogzARBdx8vphwr188(^wq&h{!_3D~(~etkUm zRcxeu9rJM}DQn*jKafPqcv=C#`NN;O$AmppW&tueeq zDg?ut3TbPC5Lu3tl$~o;K#@(dDpSbrX9j2ndn1Q63Xa{FhQ&(>n<}T1?Y7#5u$F*a z(^i-QLi{?8Y!)=x9Qs(bq#$sjZw~3KZ+KZ<$GporOsqWPv@%vw1J5sI)CAv#vDin( z^Hl{aADT+u)gOoT$FC}3D2pXUhot*c(6jbnHd;lv4>HMbgX`kh9T{jo zv#0aW#TJ(Wc(&W)7leUR?JL6%-Qg@ZUMxNHF)hKxj2jn^c|S~YmbkFt&KThf1YgsO zteti(4^q!=I>DCG%86CZ@7j;}jAl__!>jI-%yf3??8RT&^YXpj?*yBFd?`q(Ao$dR z9palNPAr2*61E5YGeDokY99h^?v{d}+6c@NG~rG{#?_9U6oLlU{yxRi#k;-MalG;H zXopPYSZ4PrlPqj}P11g!r&1wE8H0I-+D~Md`0QHDNGx+(VVN!j(Je`)m3dEN#F5 zJ&<UV&_Pbd*}OchA{dSfSO-f9a* zo7_xey$*NRVS@^^-m=G9M5hJy=@Fe#M{g28V)P?S1$>)6TAfV zRNk_Fd0z5UDVD9Z?y#f<;`Zp|H7TepgtJgW1U){FSNXX5BKH{d+Mg3jP*c*S%56zY zW9nxHE?cZNM+qP2h}MUxL=b%3&gOe*tejc2RHv6>@1)O@_*TUo3Q(4Ox7g@<@)8qpDs$qUy z8MaGQTYgcpi3rmP%bh?z(-B*vVEWSMJVhicK2zIJc?D+(B1RSfYdtqrQyhf>%GsZE)uJ-TFHYLCnKaQQ9R#gIf(g&uc%20t|p4fe5M3z z%~@rouS;TmKLDqic!N;^%8n#^8e0X_9@ z)b*$Wd;kQ>%z2ReZ4fr;+v7*{XSW96<`E@!P}rQiMNxtzxQ&~ z*K!=`M4q#A;Fw&GDY?)C7T_#WB-Qk;csJjpo^2oB*biO{_`!PNDsd`>)HKn;-9N7| zC&T9RndpXHD!Q_kyGI_oVfqhVh#7vvr_~rm*W}5H+%yCrnPu4>>|EFm3;Qs|@7KxK zQgQ;#lO9bt6F0xpSeXPyI2#v=6*rB=sKje&xGg1PC*o1!@`V*|B%#M%$K&So;|i-Fgxf$hKbE~Ta$n*i(Ic&^YJXJ z%^d9ZeVRy(;RTT{)Ce}jPF0EF>OPL3k?!Iy2z4RlX~tds#V}qcodX2TiU4>)vrCgv z+_i|Xcfo)Y*e0&8Id8Y}{d67Pc+y=iJa?EFx;tRZE2d;Hx8H|kDo9nDs770ycSWCR zV`;(Y6p;5qM>vk75(3o(vW^E_JMNF{)kf71ehb@C!PO0fbZgq^@whn=hJOtXTmCrx z@zZ2;TvlM%Lzo;BM(Vdea@p}$=8AgyA79B|!z?MgMsrX4z+$F|_VF|;Vk3LZ0pMoc zNrOaAIiPV-D2CN-*AR4%4j(NL!A>#UL6}Q6Vo-wW!tB?`(U@HU)U++P{ROuOc#t)} z^;*#ROnFp*(#Z$ga29r3fXx7trd^Uizh>`I2Q z#m^$g4CVcx1^D(^sDpvLRn+CQQJVW7!IUvY{b}j-e@fa4?xMTlt>6j3~mkgb}}0dk4Zgl7YiC^!qeuaPqelC%r?2;2kGTa_ zeq&Ja$$_ykS$pEt-T$Sl>#cUlZa#hH;MM_T9tqe87Gn2gEeFFM!eAi7LULxrgHGjW z6TsS1RxP8j4=MNtE~BwiFfMThKkDCP=Sc65O+aCLLT%sA)<(Z|j@wSMVWw*YlR zJ{wfG?6p0g`saC!FIMTBObI+juW*M8c4;bUqLy{1Mwy?&^maig&Uo0ThN*60t;$;E_~7E;#hfH%<|-!^lK*1^djrTM?nHn>-SjKW;t7 zMpp=YjT#~oYL2T0u?9ZV=HHksoowx36FAdRIg5F;?a^4xxB+Z zG3JTI0zj2hudp>o?B1gr9teM{gFm|LH zO8naq6$`r9=+Jcm4EdRoM&bDm)D0yz#;KpBvFqk>aqmc?z>_zC$h5D#;U~>?J{#Fp zH(j(|Z$4Ew=$x?XUi?zhrLDT{B(KpAXmyd}F-bX8ZW{8vabRoJ!0OGqA@j-Xj6yEe z0r5Om1(Yj(s%8&3hGsiLp5t&Nw+r7M$4`z#et*95u0t;=39hq;)N4pp*)Neg5K27bYhI%E#(6mNJ@<|&^NuB^=id6JAL+Mv$K}qT}nk(95 zBt(BRNzM_^FfDQr`Z$9onT|)Pa_+`(Mx7;_-6*^CE8C9@IV-^hS>xA{?!XVi%a zshhnRe7oho^0NY(_+pDB1l{_AqG9+}Y3=WK4Dj}!e&*PTLN(_EQh8v6+RLuTjU);hqbi)} zPgTq?fRtuSzlftgao*inRI!xo@^A{PlJzRDS^hEof~9=0{80x-i+5hpTrMn>=%vDf z1G@o53)wC+bID{pi>tj6>)=eZBT5E$?>a5h9<;EHljYIdO1s7TMkkCgc;wTCQcf7@ zfI;pz)l_ugssoYda+lWY!28?EsqALBI<~B^9Kx)@;qEJTbGjz?G?`7ixsM50ig^I| zYeM%UH~DAG-q)6|9A-Y27>$ZrzX@aq^qE!a#t$t3b?uyfyJ8Olb9R3W5 zQVJ?Qo%&`dBfcaxk{4`F#ftcg-|%Dc_eoIuIR+R86Q=l{Wup@vo%aL%Ez0$Le#e~_ zL@_#*gy=QT{R%>qpe?f!&W;>OXe=o$=&|}%56Ltl-aHv7Eu@TnNo2gHf^j0Yxv~De zF=qPVhrAZP++7FbwyN=aWvjwUXa4-Ws*IFWgbn~@gR8!6O)hk%03-Mu`C+E|$S%*G z{p^H!_peizD?u_SaRLF;Wk#e|=!}g+DR`(6PZHjjk65-(r#OLD!naU*=!?16iHE8? zQ(@1%HeOLKn`CCCRYwgaV-IjgK zk!~ZP?2q($95KVWWw(C@p%6`FP`Dj&$8m5~Z8E3m&XgQLSDdK&y=tfI?PfX87W@Z) zZTAa&cl40({5QY06rqq^Jee+ss07z?HlgDo?bIFBqqF$rYg%krxvI#J*v=D@b~?^x z6lPc21&1H&7AhG?L-9AXu@NgO4-2yP*?-VGp%>KPy*Vq=7^wLJZ<5& z(C(B;9a~k~-dO^GGn`)sQNSnNo>DHEvROfj(U`($#*ZBw7FNgN9TGj6H%W{OfI&>N z-MyW&mTkkN3#PyS7gU79Z0Fcls!46w^Zt<4a-Gq)al|K%E7Z2ND3Km`2XUFe zN?)YSOznMTA#={TVieuht7T@Bk9=5R!rn6Beu=!_=GWcebK2Xs88Lz)C~Y&*GNe-9uX zFHS97P&3>;)}eBJ|CA=coL2(mytD83~-2K}SCGmZz)b+TUCwqP0@LK(7VRg`VZ< zq!t*78208UwBSKRjl4XR@dHcS4uGLL)gS@x6?o0wfq*sFZLGFwa$auG%HSQl&ehf> zg-@8ZrtB5%LTi=`AcJmaX5_(W@f%d#GU!Bj?F#Y^Bq@azKYO(wZAsPtL}a3Wmdx&Z zF5it%j~Kl5bqYto{dyo8RuM;vn-E5ER!;^Bleg}3aCGY}(uMw$nwvoLpNmJmNXR1@ zbfqz05uxJFqd~KtNV}uc>ctY*bFJKkN$R8$!^UqYRo}9GfGU&#LIWVdfUqcs^7GLcWBn@g&;UbKOcIi2mGDiC;?n^W=? zrJ)wRmg*^f)LNPet_qh`3rXAT#}4%Gpz||B-$@QKh0qz&VYV0N8M^%v?ctX1f2M9v zU9Dr$(Ev+r&IhYNeh(sT6RRnj8fcnR)i)}9ADho-)vdxS)gG0T`$dWNA-D>tW#S#x zXm=`Ay%1o3d=6Iu8`HH0g7>P2v~!LO1XwiZGmN~FK2(Pjw_x3y%>#e{Sq$}nU?yw!9tCSFR~s?i>j2+$cu z&2y#pGf{bja=g#maCnsPH86*jhjx#kgzA4s#?aWul1~Qvxqe&9o`d{um$GAjRsk!2 zQkTQRO?BYQw0OhMMAr3(*hz}JAV0H@Fa$MCQ?Yzx5QdINRr)#jH~TGfp=dge3pxo8 zkLFBm?w=hB04_P3mS3;@&++)2W<9xCKVMzLOXYO=;Y^%utTHzfxp&J92Pw_DEK$Pf zaVhR0#gHmcROIs=yjPbooM)PB>C|K^pj1C$#kjn&DRoDN%VvV&5wAEkClIY|!-laF%)jujN z@NuYUTYYH`Qz3K%FhZWznnqC&G2JhS;Y1zPQNVJtm?BJ+$fXq+qexQU+FN`=45L%h z`t0k^6gt%oB|Ms0QwEimOgb83U^e9%0B`rcoKFJz z#Skq=_K{QX@=M5LkCwD_B>$gYNbbpW7g=E`e3^RPNKoXgqPfabW+zu#$r%g3WI2}m zw4t=irQp{huVi!Uf6Rj4gg!cCnsWVE2p@qD*5KhBm!>a;~ZqK35| zS5%(v#t9-PdpyqH&=XFy-^d&7A)w|e8)y{&Vj&8W>kW@J(KG>14Vc#`$({hY(LCZb zg#P-@F4dz<(Y$Ff|nbi6AtR z@xmrH1Aj~Pv1%rzy!dg>$mm|~@aXjH*H8E^O2qru+gg9SIKelM(AdIgdK%4IF=Ros z1RCjKq+Y)6#?ywE5l>xgG}!Ex4QsXP3>)2y0CxXP6t`CVbgkyNc^WX;P=#i(-#?`R zT^r5d$*UUy$G81#rDWOYoOP`cL&=*f8_{$pXbMaE-qkt2cPsK%jPQyfOX?etFo;aP zr_a?bn@$PHT7tj!VpE)I3$jZ|eAjT2#C|NN%B$$1b74(~jV$U<8=@&E0;1=YEPt_EklxN^li8pEeo# zcb|mF`@~IB05xq-;?K7ote#&FHS)rEWlI~FeIu|{wxi~)6Y3qWH681&PICm%d%0a+ zt6T4v?)q~pwGZC zbAjk z5*-ruHX5gITz?kQ%U!VZZQuyJD8!s>Z*yDKh298dO)}$GbzLWqK*{_>cD;H~HMp`E zkezxF0Y&%?3Q+tEz7PdnagWXS&_k$+i{3k!JhhpxE6Ys!nB~ra3;`~;of(Je6LILR z!G3s#wWn!B_vhj3>qt>tM*VD~o8}xjvXt&o7077;>f0Y=q1#xFw>3~JSSk#v!Mvn1FZ2eM zOmzMjiHnKviA(a-^S?VQci0kBOVA$2ciC{~XTp^g)fEn@MDkOIbzCXEaMuK2-m!u` zBb~grSx_NqPeFfYfo;Y1xydWd@aJ+>i9(LB+y|CZa8nT z+>2S%!i$k5Fl9#|AvjE4$JQNi;`W76ag??(-`xx;$gd7d27*TPp^`uB(aSb5NA+6L zQHoI|@}!T66vf-T5soT_jf2#`U(_0{^qT;ozsov=j0jRIgT*DIG<&7+!+45gP06wv zJ1f~-zF_2dC&BUMnN2rhK9)2z56Dy+@XA~dT5$I|r!Cr%pL@utgv3YM)j);UAz2&V z*lC4H6~}YDHTjXD4VeaaH+y{0{MA%*yPxcL9E@&^_2cHF>1LXIGok-)N+063zD=@9 z$kIe`;Q|E%Pq-ucFYe$KLUt(kgcf7F9}U5ww^(D=RMM`zC8kS;smLwiE2d*#{>x=R zB~8GIZtw*Jy~K?Xe%Q}8oGmi0g;Avl7d=)kv7(N0Wi8iaZ^FwNj8^0!4$1I!VOW_-xVP3H=-PQ6VN!2d?!8)8DK(Slcp@-ANdA|Txj(v5_O@3WuZ_x;1` za%DNsIdf+2x#ymFjC!l8fQd$i1_uX+300KUfP(`m0e=Wlkbr;T{4?DN2PX*!m6g)+ zfSZtDuN2`HwY_2baAw_qA}5bcbwOD)}C(~*A-ULb=WDCMDvxP zE**mCx)oKJX%hwhxiO*^cezeY_>TGV{(5l@yy*Vyw(7~mySa8vsj}#vNP~g`3X!Ct z$+ubI*G(|sHV(fpH4lEvR1r!;gCay8jPpV8y*?N4rQi^_UfDLFtK+>tKcn&PeI5=SIjyNKlSuK4*B#Hv-KUk&R2}$yA{#&muHt0>aUijNr zIAAcmHaPKcn(oY-q~_GxmvnYBRMVxjL^;u)i~*&_KyTNd1HBqt#xqPwW{yw^58~ zq+{QtRs%nl#9{svRISd681W2y{(Jf$NL0(qY&`$_WJu+KWNZxgKiMZ*Dc}TP_&^#O z*)i&Jr7ytoOlX22;Y9DhB_!P@KjU_olos#go+eB%r~xbD0Q(%{huuJc6x`T%-pngE zl9>6H?)2Hi9i4C$3$d@I445-l$VnyrgA7xnp8x#{I~}k%T~M+i@aszSL17FSyIQZG zB#(Hh0&K``GhdTwd_A@&Y5q$_)A0%U&vn`HZUKX?*^#5A{xKn5?7+ArCKB#zrhC1`@XFrJ13{iN1FR`pvBjdEuJ(*!EpK>UU zu>exVfDc=WhyZgk%!BNsfSWMzsNz$?xugau3Y_<@N%WS#&YeFE|H8c$V?e{MBpfw@ z1i_lgpEbkm_da)zvyra;cFk8XqBIHxp6Cn)Vu0f3j~PX&UO^O^F)uVXAH>zQc_sFybJ>BbCCn5UE-}xhLM{Nywn~dYntjClXV8`Fm1^fB_a%GR5ahB@cK%uEe z8G4{kxB8WJ3KeAVN7dgBDk{qoRSNTrW1RfmE#&RS&r15*j?MQp>Tb zO)SlpFF5fn30-{VjnMp>OuzJ&w8m=Bjr2UE^sMlhUQa)obz@ymk7OrvNx;(Ojn26l z43Js?2S|44&>*8jd7y%a;DsCDOz16hDAuod=qGIoY%W&%#c!j^v2MtG&%1FBL)m(_ zByZ`-Ms=O63GCfO69~ZNCi4=#kRSk1BuV0}c&IHE8rAs=O_vIN^ZVc|&!AQFuQSA( z(9XmE=!bSJ=7|_-LIFK6-BjG0s~8%U92=BG8{u#RzD42Bt* zc7$@e1WL0AQx)S?J7tAeYZh>d*1E<3+|_3a`F*+LQ#?p8U z+G#|$4;2nfP1VTsZyiK?SkxRk?b)syCdne=|GoWbjT631Pk-+o3->|SVkLEye07Zl2v~HvGVpxEdSnMQJ*OJ zb%!I@M9(3^U;6`_X(z>nrr)UfzjpnP)+ym6K9Fcy^NCn6P`oc0;F574{cf^YheO>2$10VQWXh_7Y zOjAc~_u6qI_Ey+dK)D!N>z04eto8PRLdN9jP*RDMh{cul6p#JjkYc({heK=*ET^o( zV86wP4HF-{2IL+Vw5foQE4+K1lBvWMk8c&bG-qTh1Rpr;y`2#K0%ws$WIr8W>fYeg z=!cD9+%ZJJi?3kLbAhez(~t^&9*k)?hGTs*t{!lqIuni~?7|zHp-3Q1OM%6NC@dyw zl}n^TfQu1t1xezzEJ!*KdpI$dQaDku5?*Dct(+_}b_-uOM zOb3+gKk&gybU}~`s+Sie^ONKeF@L7!GguPQHzMoADid5b8&rSE=j&zVnlE`xn zqUy_)z{YX58eG>SvX+f59OW;&wPp(SzYH08g-u*4Z?BbM0tkjXTTTxAa1tXRN>5 z{n<+B$uS4f*OEu1hj3kqgKJfD|YC zE{hR`0Jx?K{D873MoZeUFd}!;!!A+U4)Mn!e(LK6HdCm#G;1YQSZps50H7&wrfZW<^EMWs zRqzk~BEvVn7cpubkJan+gh*3em~{B5%}hoI9|h>q4=}vtB%XIwgI=2X94urqJgl&e zN8I2UojxU+f5<=kMFzpDmrwt_yK=39|8dUQY~D&hKS8=dHl~{amg_NLd|^N}jS&?z z1Q@OZ?VH*FiLu|r>W88jg8{j`4X~;9>kHYNbhU*ZjLc8c$s>UZ&1c__>5Zz+I8gs| z1&|@T8E+!h(WC2km%+2R7@$V{+M>H)b7vm4+L!)v6i%y0M+y&Pf+oB$*Q=H(5e|{$ z0VyV*4_XWUi<-?KF%BSUHNJG;;b$O^JJfZtJ0kR;}`S_<4U9 z+|b}&8YxZIU94Y9kuV)~9BaReSk$toVw+TYIrUE{4rMcaIBGz0eG9-12o*45@Eo~7 z1-f|ts)^>?D|u-KI>|`~WVi9JtF5Sz1nUv#&dl$*FXnYeaST2ISn*9yFGL23nZHW`^nng0$XdRrW;tOw=Jk=0IxN%dAZg2S|BPC@xKRM7o@dAuiTu z#Vk^j9=EQ)a9>fQI<%7+wnLzvdIQEg919ojU3U0(5kc3yWmC5;yri6 z?HWQc#z$Cg4intfDI zFtBkNE$L&}iomvUoKg6O$=1VNh=vCqK)0yFRougfhAJh}AwkrzDo4SY!49}KfMSJ6 zvbUHM3@30?$u`^~4zqFV1f6!M;G|lCIbeCa<)nD$|Ez`vl8xsac@UPTQ@JobHDcjU zl1297IOT zV{Y}J_a=UXsI%AOwXvyOD7tcz@~PEy%t!y#Th76fii#+*3JT=)IyL&7?1t2IX6~0; zQlWfjo8PE^pRIlv?Do9}4N3gNc<9S=wqMpJxGJ&14Co2EGKZ+h=0i|GQV~HoCHW_> zD(YacMG8;%j)jFq{^R^4?}y3WTBA;rUz54=Yb_4s&o6&2E=Q;XNi>6MXS8Hb z#r~6@xy1|U`4o0!!LXnZO$1&-8J#01e+Avbgg>7%XtesVH7R-#c06AtBP|V@&ML{w zByk!V^#(4DIao3vphqgd#c>^tT);jkj$E+6R3$s8^by2tVPX`?-5ndolFv#cNdnad zVF11czd-9zj+MdOZtEs13x+T~>4=&}fU981)Kb{Nja6ggm_PNWK3Pl?BX8*43 zD$EFYksQkO$Mr0JyRF6df&AivP8%4qm8`-RW{qx#D{pRsVz9+4y z!6TI4z4#=XlKrW>T^or+$Otf>E650`jwh5$UF*Z6qQc_KT`W~0m+{C~&fpv#O%?SO z=0p)94c07EB@MGVnkkV?B1(8Cy%c3B9&2%wqQq0%ez75U;oS^D^bf`VZt zm7AHYwxuOq`r(n0yzJ`|cQ@7SjS{)-7$T04$D3aRfZf#@%Es_RF9q zgxK3?Kf=hjKYiMYiy`!-B_&cf2S4IijOlbeuw_Mv*>&L>KTL(c4|pso%rXc zzO<}OdRFk0nXKe=t(|vz&aq52HD1^|1$v|ei5H>U!=FVDiGD~lpkl(?6}zvb^c3FS-tgKo zSzqi{TR058wm(gO3k=MUL-fEpS^OHo)$`C#Q`Iy)Hnpuh5lDwuDigq|&_E0}ge}j& z?mCSI>XQeS7qOZk@p{;f2qp?uMxg+Y){LNYb(zG{Z4bP1W<4liZ2G)4qs;ZmbReAc z6%o~&&?ka+>jIm0Y>8(aMUUQuZSDgV3#fd?GfIQS85s}+wJu3TL^voqg|aG+n?2n; zwo)-(3|iu#7-Ft)1uAdwD_*Dv(*vl+h3vH#cPypo!tZPpYs}fX3e7mVtpG^V#jduq z=s_N#-@DK`Azi@h397}2d+)HU3T5kN%$z#5Q$|a|#$~ojGK@n6H;ehZf1@9?h1KcVhgVUf{_5Werib`VKlx z|6T;XZ(xyDS14V z7@B73o8ScTr%`HE-%aVgNbac0R)4MnC8y6gB}x~d3P~t9(L!$LLZ9Tsq&lXaYWRtS z+{`|Vr{b_t+AlShTyRo%E6!4>91cvxX^7Ig8Q_bUNV3 zE@X^l>P1H&f$n@9VJ1G@c*wK+CcyM{lyo|iE6)y7Zq-&|Vxmc62^OV@mh)JeCPkEX zvOH1E`|F<{DLAfs$ZqJ8lz_~t215ya5{48>1vp_%w1ulp5|#Q~5%*(~=;>FLdw;%^ zQmXG&XO?cfi70;?w9v*ZLTRqVCway^9f|W9V_4>o5MC*LoBRXL`O-=TW*zbFmsz;7 zB&|mtS$X;Xy|ENbrpW+y#{7ntyT$jI-PO6Go==6wb5eq4)T(!KR>=_n1%uYYE6@go z*-!{PxX0Q%wE<8-he7&kP~A&g)*Qem_fV2dRyJIPxbU4*+h51|pJus}14E_qv1nXY z!W~5ga^aR@uQ1{jABAve1JB!kVbm*8f1s>e#x*Vd$*^K`94a{w+-er6awTP){AmD| zm@d(Kqkbqf@S&`VA<%#S>)YzMjzO!$WOndXAWz7p|1!a9{JpM|RvO8Cois{v=kF6f zu~tOlyszR&b&LaL#ZjYpNu%QaPi<@)=_46j@3FAOZgD7tT?cLdM6#~~=|)0_H~Dr_ zzW3i(lgZ(C`qV)vTXAkwIwwSq^8l?tgcLIMd7?}o2D2p3@!~e|K+}|xQ4q5>X7vYy z1vp8!KDWS`lNID6`qs@bM%6T3wrN=M>su4&Ngj+(J@X9z1I0qgNZgR5u!xMH>36A z)=If3(|jbJ)M5A_NMkC+=h4V>-SQMvtv`=<1X*uZOJvuhDy-C!EWT8NfnSLf z7vehskdSl}>51g5^Y}JEm$X_4W$ppvtsztUZEf_fe>N7{PY>%{0cde3X_F&A+{;hA ze5L?>6v)iOoTi%ID6Ln@?~BdN%TDW`Q2rkglfB|lbYn>ILE0yPsEtb-k7E*mOI=u{ zo$do)9+{Y;mO3O&mK!X4H_)h}TC2AEn1;)>Q4!BNEP|qhC>35IF(L?OFc5R8VgPI@ z6aZm@!&&L_c$*J&dUSy)fF@-L00xT;L%4vP34BWo|KApGUQAhNT}bb*yueAd4dTUr zgL>J+JH*GG1p1wYM{O(2HYTrLKOGiHTC7I}%coisGyrDO$x?fBq6twxAgxN9VQ_d0 zq(K(uP8HTOHn1~VWJ|rn-d?Nky<>d)Qo0lkJ$>jmW^oK+rn|33jAdJYDhhBIUk6Pc zL)`2fU-9x{O4#%*x^B)12?!8vc>kh2T<>g6!x(G+DN$oVW0MQmf#JhlYtsTh$iR@= z!~f&OMt7~+s5%4KY9bT9y}jJ$2da_!+nJq0YgLz*ruos5aWNH@i)eako6W#~-4azK z)7~k&Cf89PlGy(uDx`5@9fm9IgOFepbCyseNlA9gQ+1h-j;`-DXm2r&@@d|T33U%l zR-@g~7`1IX^hPYt?;1KfCUwt>g^7!ci=+q8+YCLhb0l6VtzM;*00Q(E1z=R+vacm2 z=eQZ>nFWlQ> z!B(TG%*ht@tc_mwCU^a~%Ci0JL>|EFG2mX*hiOb>dGqgCVJ&lLWMsg=sT*;=>V}71 z`WZ^0)h}lbIL{(Xmye5~a3=y$3jJWHmqqmhTjM^bb5y(Ez_pcZ=uQoAakgp1%2T^s z`5lDc%N+OSjX>1!nr2bCXJT1ev2;-y{dZ$S5@`}+#1z6r>2T8pGRR)1^&=C~Qa%WR z*(&Tur*XhBeeb_EO(Fm)1=aAZlc~jDYFP#32y{$rm9MFq_7VL}yw!4sC3DTM(B{i) zC?5weixR{`06wlQpN3F7kwmZ1Px#Kzw8i*e#C;f<6~3~_AG%&2$t7YWo6jjYz1g&% z#+DBL@yoY&~4yW0Cfyzn25piFtWb z$_oqLob63nhZ|s{W@m&r*0Gg#={Y)j`t9WpdHkKmM~qzxnG57wzq6N+kztibQC@Mn zxFnoY5KkuNnHseNL+7CEAIEpCAu(l5d*BShQOl^%(sd{qDlV4Z5+ zzJ`bcmwdS$G6rNMnaBnJ@ob)MkFaFF`=#IS0tFo;(HSY;d&tOdz-``bDl_)^_u;z*l^8?=%A#)sA!9g6@5}eM(6hDrkn(M(9 z0#)uU`AF$)*VNG1*p~`MXtWC%d(FXt$@nJIq;!1!a)YP$jWpC;0tngVuWBW7azOTr zZ&FV;xm`BYo3GR^(BbeEw!XVZ9AH%9r|Z?7JH3cT^m!p(uv&C}d44?Q^H6jC&e1A) zw&KT*oAGUllH4Yb47i3lXS^yJK;xf5slIL{@&3ARyz)6Yd1pvkHA9w0P>@*XtFh5_ z;{62{wF)beqRfZh={lt=wUVh4<&2OAI(AIL?zOeRe}7A`EB_ux+$T}lmYP|9fNiqf zlX69%t9?C4g~^oERb^{dQsxnF4p+C+gClS~7N_I}&U5FHi#}Oa0|QTqp4)zj+j^tR z`zX}*+qn)eKAR{^(w*!+>20Bf`ru@%e3Aa!U|FfxXoV#^@0YSX>(~^~Zd3gTI!kXr zcwdnn*S^l}_LHiMD>Dtf4P#c|V^Rr)D-Lizin(PEN7juA=B`)=zm>c*g2F+WjGDYu z3QYZ`hZ#2cW!NKb&zTH-Ce_~IIC5{5beq;|FP^{Ok|+kY2O_@z zqE|548H}=eOOkL;VtQef*a#&9WY;6bG64i9lj0UJ^xgSNk}%n@KDoSgjP3eE&JbKO z0eh7BN@2szh+yKVH$(&k+#2~TXevs~p_0ntjNhKGl|R4PW1@wX+1xy=ZwwANiNOCO z5&qMy6i~cJJiRcNd|Pw6ok9~pE-tvTJlmcgr>q>q=oD~RsXNVeocIEY9$dveyG?#e z7W~w9Q^V>smWxv|gOmQu@3_{METR2P?JYln=pR_;Bf$SSl86?n)E~|{u%y@Ar)@jZ zcX@FU>f@g+E#)Xq@nVxc(4rnRK}^=Z{onZyE|mFZ;VWZ9tM8z zd{xT(=*C~)BE{k)N>*RCjsn&jJ$unGiDyolYF+R2?3|j~{!rVtgT{*zE0Cf%1D1e6 z**9#;Sr7y|P+!m9eC9)3JWzLW9?IIR#1$#ll&p}d-)w8S&|Yf(RAwQwEE~0!K6l+N zZav0|cz^HHrWz&8)(#kM%dwH#PK}(m=}<3>a(l}=nB@AMM&#Hf3psKprkk^Yz96!tb1#7wUG z&znN^{I&k?x{*y_w4q<~xR|Atfe!sGnoNeoG3z&alCe+B?Xp*EeeeiKnWPL<9-Ih| zJ0!4JDXj|qTA-N7+!e_m7_RvTE07Yc+qjdg31>0mKA7Q~TLZ8CC*}&7VV&JlmJ+M3 zq4DG9`XtNsYx<%u`b>7f7zF|jF)^~3Po4h;*068#HG1|hve9J|souL3h^{TyT@we$ zi?`5-k=jJdQz>e-+_&2W^P|r*UP7C5lm<*&1Ihuao79wFxF%oK=vo=Hzb8EBPO0~h zqHrsuVo?au^mwA zV=VZ>n_h_yJ%0rqZX|&^0^mmFOf<@RdBx%H%)+valIHtDk>%)m(annI9Ow+K>Ea-$ zW`G$GU4AXsA1fx?nsh2U_$J!^>36CBJ?+FOHBPwk{&tc=43U)lzRJpB2~Y*2EH5l} z)7aicui^z7!MX5_R#LgTxzW^5M$pBeFo2Xh3eVE-bcyHgnbLN- ziWu5hFOA*xZmcl{QO}uN`2#YrTdZU7s7A2AMCh~R> zMHLn8F@1X+_@32dU0_YGeQR8=&iI`gdZNNT$~8cP^$}!skP^k#PZfuJJB4)(MDHCF zn=qkx4q|%I^*y#}b~M5r7LTDg85)}WQKp_-e`?tOOy;H;jn!W_7xA=nEO&ZxeYK%O z?;WTjbNcCPWyQ!9h^#+LHdx0^Rg0o2h%CLo#E7o$YQKmqKad1T;v56TqE_-(E`DXf z_;+qx92PKATsE8vZ4p16Z1jo)f@i^HlbuLaW<5zX)5P@oR#JwjKQ2SaE4 zm$vS?Srfbkzm*6YhT*{#Bz3hOo0)k2(0})=ebflR!*r94CWAp4dD3d~eAT-f6C6D_ zDq{=E;Z93n_n~bn*#frVziRZi#bOspf6b1!(#S}Ph>VJ`FIB||5~EF-i+BZ08VmKPJEij_|r`R3yh!ELk=Pe1Vdgh*BQi zUg|xi$9@_VbiLX|Nyg?-ZU|2NIDIEyq`1hs7&h1Pz8h`SGdnG@-bNy4W$5N&q1)G! zO-_WB!+X!reTLc)=q-`}LeVQI8Y=du81eR>ub^~mE&jLo{&mt*U&Qo&s|r}*mq0!m;Bj2o zoZ2zwcz&Opn)*PzJqQMb;>7e}DjM4z+3K7ntBt^1eB5LK19^b;RXA`B3d!;}US=wq z|MVCijjx2Rtxy_#!(I>YH{Qb6_wNtNGxA=Y9U#&O zmU;6!Ilf97sKw!yN?n>5*KT)Zzr;;^*U@`XlwV7m4ABUMq|X>O)2j2En;xz~6PJR+ zI%+;iWH^Kh%P`7Fsfs&zY6eV;6^YyI5kZ_5YEW7m2A26{VhP%G-aNn5CZ}F)lyV-F@c~-uHG8Cl)nJ_4DKAE`J#|lJNCD9(;AC z6I*_-_S+iIlEqQE3*lsP=i`@7Ud{ycZBNvg@1apqQ8;*b{YFmjuz5vKU&#ntlY=c} z#{`V@9x-KTBm`o+XZEO^BA4g#mfIGDD8=GXQerY{ z`H(U!jimDV02yw`F7Zt#;9qt>DAef$Ujx*-T)ha1Lgb^O7(5&_Pq9uIU?%n(eJ_qw zu8@v!>&m#&wK>pr<(Cu%neVHADwcigM6#0*-6DDlsr9?@P2SL}oERM-I`Jkj-yP4R zTfkQ%_bTVS+Ha8siZB}M9X2;v76F~PDPiW3krB6vv2-!;5QpR`8#PcarEMZh86ZHQ z+V$6Yro}_6KtH^N>%ru8ar|+y#l;x2T_7WD^hWIFJ`}FC*jB%DMo`#4P(TuCYBB9i zr?+(1ZM)_7^fd9UGvYW=ffVFc!OXSX#YSg;JWZePv20MBb)~hzm#UBADCs3xvj*F zpUjlRl$q@tqJo#6piu202Po?E(tBR-;ykNC8&+tD2#9gvV|&|Gc?MH@A3zvOwhqgJ zDj7C{$W1;eDmHww9ReOSnj@NTrO@)^&`yW-uL&56@F8`b%$dJanBlLX!nAKkX-dq- zO-@M(E(8EJaW&cfWWigI-G+A7k>Tf7;FN{1J-=e|8~PiQTK)MS-GlxD(y43a=GnZ! zWzD7$&f-9z%x^3bi^D4!h8Y)`wN@Q94EUoAI=j&WLcQsY7t#BrX8$dKMw$&z1`qd+ zw-YoSZ(Q7^OV7sXCp2JbuP^KavRk%>AD&5`P_ilW7pg}2(buC=h4L{QV z5@7T!4^P!Iwy}^3aw64Phwg3UEl?Gyb?|Hi+Kd8XT$o*%nKDjg)2Fq%lP zGcGQe;3qG&VtsE9)oqrL4EpT#;9L!o>9{u2pM*mN3n8*m2`#9F1VdbM8enn|)fc|L z1%isN61bAdeBhgZ6h+rdA9FWsxV?tYaMa`ert8$QkiY+jm9F-r8xdaK;{RTAnoj2X za;ivgj>o|7sMTZ*IG>k3MkSRYho}#-gp*X_RVE)d2E%4<9|y7iTA5X9d?~uv9rDyRbGkB&cn?Iu>0~@_u zyi&!>Xn7R&uCt!#e2V7^__@`upC6}TEQ(bJ@7c+-o}M^e4CaS@g!7ezXi#Dws3@;61REsih?E6#&pm^1etMg&zN;z2q%H-?4cHS4Lk62f&- zIJY1!Hrc+%80YrBGMnGL(0fOb@{I=9I7V_j@LL%;JNx#fTnu*bi2hN(9yG|4B}}dm z3~1fkyCj-0M}3apey6OV^$I{5)^-F?jTZ#kbp50mJD_zU53tb^`rMymGKR0UH#dCh2Fi<9XI9-8*q5crRqwuH@$fW! zPekuREgZDcSrdu!--orLuK&=O*a8#Ndk;2$zFmty)>&(-(I8_LxDv}FcT~>w;cXCycAqSrq*z1sI@&nY6_(=mUw9o=+r)FSvW}m^DDEO9 z9XZwhQt+nWG9*N<>=yr5D(>shKr0hIPYf(+@HdtZk^m0^^NTW6FuK@z+7F0ly8l#m z{{3J>NhO&9fik+UWj4TR(E_GwF&NJ^!Dnza*nwVD8lt-PGJy746LG_sd3sq*ZOvq% zks7Gar%&3E$wt2EKRF@LJ-;|b4f=Tt9wLlW{)(0`IZu3d`&vwGmfE4z zrkS1n*%FVzRWCW!BKNS}I{gd_XeglpEN03sBT8M$7#^$MO0~PBq(m4@vMHb$ne24c zr0gi2dODNBxEc(J>gt{BE3a)?ucX3|YCb#kxnfin%d@{fy}LXkbUH@lDIj}22WqlC zaL>SKWmp|_81w$<|0ixOecH@X2XJKeHbc^r54bl)T_xPosfM&if0sVUdKSt%LqX;nm)Zy|B-j_9bk(w9Wu&0Oe8AmGXhAwqe{+< zHKW@K2hd*?@oeeQoWdMz5uI32!)s;eX-E2mJ*z;yfN)o{a#fVS1V%&h5I<0hynW^wC)U|PDX$|1uptP0@`3=i?h z*+d%*rSyI;ZFKtr2}bWN>+!uKmAdWE{wJ)tXj}dKG`CZd^S1p@1FzlcvE*T;hxu2% za^B=(CZ5OlLV8X9&NLtWk6mF1fcS`5w>NC_v~d5Q4dt=9Ies*#{_a z*KWz5*8C<6O8|WWRoxY_aD@k-UI`HO@-c1MXqBYa{g2ZW0HP5DZ8JwsjzO<>=6|fhuI6$10N(6)+VO0&IEa36+0}0jf%t?7y*D2kvG@kTUm-(MHXzk zieJztd16PYN-Y#Goy;YDun-D|ugPD-p*@0a%69H$9!4eRBdMEd_`;f4W6+AFC=*4P z^?ikl$oM?g)1qB;_gd(YgWy9OXTg<^Gf<2eI~bDq1>G;9P{d^&?Bq~1pn|T8>!1o9 zwn#4sv(H4R&=yzsjrsa=vY*@}^DTDnsvhoH?xzneGU3X}v&ac2&QAC$%6C%(nwt@; zWeNLr$BYed$IgJTWbF{Z+w+I?j!byeRWyOQ*>)c7^52FO++%G;r{{%03aOY(8QnpY zV!APRN^mjIumBt-fckSK?aOYdxilSgoUPOx7z;1BIS+fd6)7EO|9m*g)oJ>>H-qNa z8>~ZYV9HS}>+g-G$+<_7{>QVGd9}3q_bv8?bBb%}52vhv!!+B>4u6mn<)BZYR|4bn z%>IfoenTzUKbXztPBtyIWDNMvlFdeC?7un)79X;Cq2TJy4JyY8DP)Y;cJ&XDL%4TF zSM4u_!>pIZgM94-X3k-Wyooy~Fd>zGggW#kpG7ds#~e|8*Q#%SQdi>3cPbW##rX6I zMefj`G2D`5x%CJM+nvAnNP9|cnD0W2a4v(#^NjNA6htrcKGL*U@Br*vt5bp1u;Df! zuMM*NGQ@S*>d^3om^=i6YLxu&8A9r)9Pq0qFUdym&Mue81yCI$*@HxSzp%I7)Y^?# z0-D+v*Hkwu9H89Dh-e%yt5x{^^-A1+-96Yf$?VVxu;?TCHrfRBKjE*#J65Jz9?%(U zUAs1~7b9^OXoJl-o-X$>M|Zw4AtsfL4v+Efm;94-m;+*>s$*hhUZe|#Czk5tnNe@Lp0q8OO9H~xa$iGxi)2DxCu*ZBf^dx!M zuffq7dYW6xD(eZhY<s?K{tPQzyt-1Aav`k@r--!;;uQqp zR)L2fuHiGI*Una31r2g`XC0R6`lv|~d&beaBw|dmC8le7Ut98cB8Hru6kbv%hC7s+ zbOon)-V?ud_;db}1L1T)cfB$F>3(s%D)Cn@G{!bqprJrnG?0>SEj^JBj()T$TSOPo zr^fXcm?{uX$F8vr^9I*M8R$#Cz}@*d?heO&!JNn|$=-R@_3nKvSK6C-otsZeN}I6J zrB*K;UB`z6g%?`!VIk?}w;HSGe>QZjUkyJkJgikWUY?aW9sVIIWIjv7lstKF&`j3T zxayqqm-`54fnJQadaM4Yi|ur>g|v&aFft;|%tCQxLrB4GvMjH0Gvuk%IinU>IR<@y zD?*gr(lnz?duf1r8tA(o6ZQoPCAYp10rCZ1^O+Q{(>*Fw#BGmY;`Gn!Q-dzVkp?om zH0flN+D$LpwY?VEHBlVrjZk4gIem3RsV5gxBUCtuLMiG2)(>RrqX1C zAfHWnfb_Di~->S|@t<>rf*&O*4YlfBc#)$8OShbd!;Sn$=X;9)B z?1OkoUCp9DHIP}I#ae0#LW6D~Uj3-9a`<4sk`S(UQE@UY9^^@)NH6><0 zKAK5J#|_?-nm7E#zWdb;ZBDd#t?bPPRsE$H=vCpK@0-8L^e^V_{jnLQZ*(|l3gnDP z>UX5_5{pA6>_?J{Kiu-_E$2&wa6(y=Af#u4+bzWgt&Ww`!W;43iJb*)99PS7134P; z(b1smD@MTl63>?iY^L*!aHi!U8WdUL#2}UTLjO7++A^k)FQ0wA-e>foaY-Wf+W-^Z z)9A*>LI2($Oaqdboez(gXNSjqs&w<&6Y8({&wm?y&yg~{x6yr!g*qVK1fL*wvm)0@ zSpRW?nfzeItn%9|fO<-eB?wGp1Wo3O%6PRk_+?SEQRV2lMQJos?XQ0R*ossmKKGWa z*dS2fkS_=lTVYpkxsWdJ(zNi)XYywE8Ops`nY>C!AjZ}t^VyJ42^STD6j(<4< zbf{CDEsf+I39P)pur-!?P`eQ-&5ZqdcM=nGy0R8wyIP(?fM@0ZVewd@CydZD05mOb z9NPJl0aXkGfq}bheb52*>+XNL%q84!i|*fTWsUoj^VNFzSpp9F=WP^kWlXNj^nd14 z6{_2bHq`zyaBb!!`)FJ$MW7bg%CHck)sIyy8|{Bo?ecHj>@C0A?m@>!RQ`Ih?diF*jiDa$XQ%aI9(;0g1HERn=OPJ#W4WiwgC-BzBJZ-ua!2WDm=p*<#?`Gi**b!vGyR*rnh;C(G?h$n!s| zkU=xjU!NcO>ZUo9@a+m%yVl$hC+6%fbNlux*V!G21lf_>th`NrMCq-=Eo1y>+-*@` zC1Pydn%v-#IRtnpKwGY3R_tt*N~@&75`ILr?}ZyFM;QO{jJCq7m9*9+SZApS&@E{9xavFs2{-94#4S zZ+>k@jv(UptY95NSe#TmTuB(`C~3RuDJ|`GQY6mwB7^~xrSt-=j-9b99m-g$aLRQS zTRpPX8m@~39f76_(lRiSxjl%Q^Rf;8?6I4_zmdMjsk+njivITE!t@UzJvCF-WVRso z;ZP91?fWq%=+I+CSi$6sZyR~75BzCB{X_Yp8f&6fmtA4d{rfKcnBYyEL|le%QYT{O z&&Y?7I^VfjQ;NMH3dJpF=*b@~eo#F$tZMo!{KU_f2(w*h4{f=5%w07 z3y-3(ot9z}al5>#g!)-@bu$?c&c3p|0H5>D3E@Cu(lUtz){Ut~)J6J-YA@oV zyR!+-V@O07tbs2oA5h;L^TimM*u1(#U$VwA4zu};ghIW6>~~-*qSdXB&eYh{{4@iq z^Rhl`gWfkAS)@AaeWtYz+@Oo+7QT*3i_XWnqa*laEEm%}<&I zx*}Iw<+$*o|FE}OSJ8Si8TU&#EptC&$8-Ak>ReGJeD;f)ilb>$&#MJ~Gjau?o4WV00FOM|w^sD~-q^GtYvkyZU`=o3Mu1CzY|j|Q_D-Y9@{*(@b8 zS!wWKqIPMFI*hF}+j$eFz+(5j+9tH$W`i#=ma!dS_RkeCp1stmt`}o^2Nyp7soQBs zqoL{}^Ju+5hn6QsrPq{6l^6Di?T?XP5qE<74QDBv{~PAtjD<|t|Ns9BAa5|{*<<34 zK6c!8Ri*X>DYU1Du)mU($;II6$3H!!8-<<{V!p_kEmI0;bP0cGF!je3fgc|YW&pXQ z*GV^MF7ei)%}yZS+c0QOe6;F{K5)fhI$ug^<8Zt}yc=iYv&=<#Y|u$ez61YsBzv3< zIHDBc`IT%$R|@cc<&KHgm9gSUI*B{{Vbp^1r7#`q2K!PqDbRlor)J|fUi;Sz{%WQ* z+aE%*{1=);w$Ue-^%1(RW(I_0vSN z$QPeU_41@nl?Cv{dh5#PXv;a|C0%L+11Xij_%nzGwCSUgF+UiU7j*^=S= zEhZ{}S&twbJ66$eu&7mex$)XirX!DhFSgmU`Zv4>GR+hHYxH_Kz-eE4qL1`!}sSwbyrRz$xfuH_B;-pi9QsFSmtg-qB zxe}0#-X&&Z#|(EBIc0XCY!w2Eo*jnb>e>qODQane2JmV?5ML_9OTlEb$G^^Qk@F&) zfx}{@210i5jZ3UwRTP+z@gRf3ar^$Zry&-r4l=9MPq|ixHh+BW;UtnHOo3LF#Gboi z62~sb&rAW>ELXczw#hhlisS?vO{ad67wFTDh>xIW61gPG&+JzYk=U-bQB@q>b^RlG z_xQ^y_fz|T@h1uK7u2nBr2IIH$xlCkw5@&dbWY;C5*MBRX)Rq;ax)FF?iU|h9xy{9 zStK8)YcohP^z5SLKTbe4_W|kF^N7K@#@?m)NJxJ?{>bO=m zW5njgmq^^M*27x|>j?}bG4bGfKSrJ$1TRPAlEwdDnCYr93H*rE^d zmVFZeQv*GpvfVQiW6i+Lq!kDgb78B=KqU{>r3bZ&{3k?fyz^i&I9DfCX}~D3McP|D z0|N3bKUWj_NYcIIp3e{eY1^H!zP~?cBskFm$npoa^%h2x3wHhzR(id^=y^l@!CGN5 z@anT$MvR;5C^9Uiz(-m(_M!`5CUA@_)t(A_ek(c|At!@2cm6+`zA`ATt!Wnt?oI{` z4DP{QLy+LE!4B>o+}+)RJHcVl-~@MfcL?rwciyjVO%;Ecn!R?f?#H^MnO#Z9$I=eo zYwp#8r_Qz-J#m_&GV;LJbuWrxB(6oM3}AG(AriIKgLH=gtbtb&pY9E>LrRcZE&{2jf9`M z-;ut%c*_X49MHa9=gVUA3(P{U065P)0q8^9;`(gDLGnUP+8%_PjCKSx(lB6fSP*&> z%itrEjzI$Q$6O)gYPD{XJ~u8nb2T)sm<7WH+4(_iyqq^NM2;UR4;Tyr5fi!Z2B`T= zW54dyB5EEGCI)z z8Z%hWSXrQ%h0ytfYFa}ww5qEcYyPPHj59X?} zMER3Gln!L-&-Yw9c*xSzlg#;5-XGW8ijrb6Yaj^3dF24%3}5l=NY7fJ6XgdEl#(h0 z_6Yy+QvgI(+!g5v&Q6Q01wflrIw%mjEmALj{<3Q{AvZoCTpL|MWM^>~oj_xub(Rij zwRXia!Eq}bK)h*HbP1(>vvO@52lM&9zI6yfaprGD#X%-VIoF4Nm*I>pK&Z% zV}qPNT{U8%QfHu|Hj4W4!tp=B_uPsC(%2#Mgs<+p*`tGlP%2i5_FqdvSU#u+xWcUm zN^<$Zp2Pm?caeQu`d98T(fINs+z9RV8@VC=s_m84M>Q}ZXw%3_b&KKdSVAFM?J-Ip zWr0S@VA&lJX(zg*wVeKlk_mKORaGKsbz#sty)CZs_+gt(5F@mz>2jVXd?vzS7=02711KKx?$uDSA(V?(5U z^vr_^Prx;*@J>wg>NhJBx(=PZd1dgqU>rm-F@l>pH^BfOdM~tX3Fb5dokc)-#Rqof z9z&j}?d4fHr_@FzUHH|wIIkIvWSg_O0?B+=y+_`VVSU%}2RFe#0St7_!oTn5V!x&} zzIgsOYGo(&yAN;QzzAxDj-PH;t{ifXmssG-=p+Yz1ILN%rwG}(*6o;`mFdj~g~O}D zCUmpA>%V6GkGQ^nI2Lk8kRcInpNnbm&gJ_b2Wc>%GX=GNL~~n36A+i#_(nM7Tap$+ z*6!C%^459-)e@TH>Fzqn-Qv{>rPSR8%gkN2Xu2J!2kH{+kfSvcSu9M5EYI6 zu|OIY9ufKeEH2DoMYt(IUWru=aoEwmpSSxRAv03z9!5!#OVAV2bQn$azSip^YvPYs`Mu zV8{~e_mH38tLL_sC37FmnSUw;BeTybS>f0~vYM`{rW5`%CWdUw+ z(;FZ3H{Hun^nd6wL3H6V59`-NL?i$kT!77_jbFhou8Q)oIkhP}*v6eq^<0WR1@sRM zRtqsd$IFneuCJs2hmMB&H;PvHt@UWT?9u_pEZ0=+;lD6+1mPyh6si8UJykT&Wmf7W zU-Zz!w`T$(now#$=P`NdE;_Xq@xOYnP>q&a<&Ih2)bN(#Xf%K&F7?@X1Ul=)Hs8W# z2hWmZm-<^iE)fyR7%($jNost%1sI9`O|PmXI9ddI@WHCW97`hgkZdQty2<@^d(2N-s!V$-6?dKg^8Zc^I_|6015xqJHYu9^3JU^Uw<=Q2POP_ z4_pxdczyvQHX;lF7NKLv25gSA$Y*Sc8^)K8)1~6}-+ugUI!m+heeAHe0E6$5=g`9N~V^STR_Ritbp0<63IXh0{)X4oo zLFa*(R2XU7{o*K)&Tc7`=47!zeqA-_1LxiIA zX8DV1L{Bi%km)f1AiZ=a`{bkkMMFQ$1qr_u_u9GP`so`)v(B}30LX?Eo_aL%my;h4?Y(C| zuyy~5Yw_saQCUFCPYT#jV7Oe|>8c}`K(lr9t*K$@)%~{X6V87_w1@sLb*TjG%7Y_F zjR=2tmTSSW+u1Vbq&8N~tzor-r6yj3W>$7wmzYS&iTf>^I_NX?l&W?PxSJn_Bl;Wo z2yl+BM~`SrZ0($9Nd6bAerVUbH>9Q+8hO&ApTwl^+lUUw3%nI$dBObKY4q#fHCu(- zHsfxx!TIW!!2q}9&#TYq+uLhrp!%m|PLH4mjx2ZCwfXkb>exARs9kkh{{IG(PtLYP z2Q{I%D#AeRXl@Q$Bc>?Y2IQ%&yeIoLr4DV-e_jn=zX^3Aw5<}k^H3H@lXlTL9XBrM z2LkHH6yEX&oV6&`J{a5NW%FJKCWZt^U=HkyV=elRl3(#f4}VLyqqSw@Z`EXk&BS%; zrTd8vK2Rk|h@>MXeKT1&clVE5zO!2F7~r)zec44kyxgYEIs91Z{Z~iRaR=d$o|w)_(?`fq9f_Tc=VAgK&i*ZL9euj9xj%O)wC(T4`hxpE$Y z#~Z({)fOlJu!J(d?XpRfeh*k$b8mZ^YdN~O%ir+Adb#4`SiqfC{3SlZja{A6fq9Z# zmPD(}+LlqGtTZs5JieoEfY46m%n8JG_B?(&`G%E!oLcc>;qw~mDIBPVA&+pV-y8RP zL29MfqT61lM`z~KN#ZOEHqaaC1(p@6(1_we|Ky@z6wJ5u8E59>1O06~?{Mnei}d_3 zn10=hA$+>tHS^K2-^s=R`PPyI+dke6l(k9-Q{j0W_j?7T6=7K_N~3(aU-?bsk7=p< z{r!i6#BtsWY26Obc<_|WTnk|}qwoA}JOF3veDe0f_}6&D{doL`<{7-oqCmsvW)Ccy zkYn!;0^$sSo^7uPl1m!23CBU=$C!7Te?pqQ_~ACtvXRBowC=$$Tdo$F*cT5$KqvNF zv28;e&la?*detb|dTr`P7(uG(q|BM?Ui|W}%H{}whSfigJu#WQBG|RH`@~i z4xZ_$W_}RK2L5xxzk8x=zJV)>yzxxEz4Ajv0S94lbPZm5a#Yz102%jS?gp<;F@!g-ZE@(nf$`?tkJ;#XU?18`dXTX$JTV%vj zPrL*Ye;W(r-80HXMmLePbN)2x!@qbcuA!$IoBb5wt&u8)BN|sSI$#P^cJ4sENSB~v zW|sfQQCUvpfPbfx`8;gBKquRM&L|APCWI+_U#c?EnJ+aR%eYAGd?`9LeAI)1ZgnN| zIQ*p9V6GHiFj`#z3cxQ6_^mz1>c^i6v*~2Cqed-~O>u zxc9&e%#e&0Me;SNCXuDrCo@zD0J#Y#+ktO5_4GGKT=*}?3*^5XzRIJ*_*tH1_|*8= zj2b*Q6gHS4n4ln@zu2ba&g-hHDOm$OsTlApJ=h;^e0McygG~pkyxms& zb_2G`zvF$+m(Hv0coHWG0-S@2m!z4f_DVL3m%dO$b|R@XkHBnWiOv9&l}9aZQs>8pH1^|3Q)Ie$^u$_m`HWJlWF*u6p55FvGhi?pqhdY-c3(EW@D z3k#ExdfzBL0KcPEM|b0e_rrdYSdQhs6H;>$i0=|^mE`xkpdY8kVVv(Dp!%IF6>djw zZ$=sXy6itFzXE-iT!%KZ^aS1zY0H&r@+$@V$@80P+1!DVf`2^5W#hTQ7U%oaZIU?} zsT2Ye=pc(KNzxA(zf@H6e7sL0k7YG_vOCRXZ-K-P_vpl1Y1_-s>)R#P2oKTRYPwm5 zCMMu-*#cT7OcBs@*V1h(fPI=YO~~6Lojoc??b7?urigR`amROJPGm2g|>g=cc zX3jd?86Z9j8LhmSofTe6)XMQke!EpxvM1EXl?+-#gz1{GuY~7{{aGR0&+Xn!&hE~o zme{eb!tYM_j5#-K$e3Vrdkq}c{S3ik`*rD^GcO(dimkd8Ig%>13iV5X8}~_&^+QXz z;Yuh(YaSUhd05$agFiZIr%4#f4`9{;l3wC2_5pXYRma~O)q;5NvcBDp!J+6{27G!2 z=R<8Qy4gEWr^`9S>KeWUHZwC$qLPSt&Fc@!;Fo z89pqK-$*3Po3sj4J6>Fku!I5*$MQ73@qNI#$ZFX0!9W2gRU*Q+cIQH0_IMs0QQJya zC7OS`4pK0ZNXnPBZW_%9+`+(IN7@H7abfL0vC5!T7BIM&0vt)ybR@<N*s1jydU956Rbg_piYm5ib2{|osU!G2?_Y@psbOZ0TLoKE)ESia)*G=uI*+(xh z+_p7<<^WeCj1ZVM?EMfjem@fCI1P6wYqe<9)t-f6yQ&)ZB?W=`%Z+pz<%xCcc6~}$ z+QR>lIJ`6yU0I?mz2hvGmQ(|g1lQ_=En|!0G->}_L~*c6Nb6YSO;k6SMsVq_3~;HR zpf3cJX|xyk8=*JngcH?@HLlPFR_kn7p+0A+5@DBNPs*;47KmR61`JSK+l zQh{_*DrS*W*l}AORQ57zO{!aT_6SrbpbGE$=h_VqcNC@#NAZ5&O;p$nA+q%Y40TC@ z{mxj^QoZ+ZeY+1LVy$}J4 zzgyW5xunVe0@N`!{vW zdA>}OAtQr=hNb->u&asa9x{x-h>CjGn}BaN%)ZA0YH&vw!j5J728<)yP9K+aE)aB1 zRR+eJaoe^5jDKhO_L)1-|GWH6c-(I7?X$tWU+PW0)>81wJCQot8$&_9c2^Rdl#~wStGevVM>pS^y<8%JXOkbdYnpq+TjfAx(sqDaG~Wp`kdKW-mY2@p@*RDUl zTL0CO^Cb)h2ZPI;SAGx`H@SEmhOH!AU;KE2IP6H~{*?H&d11l*^8@((HS@m&i8SH2 zhaFaUjR`XJX0`d}n>fiQ zxLqZzj04X#^wUzFbaqA50))8Ne{2EY+R6eOt(QK#>==!hHq0#i@V|+_SwcWwpd?ZB z{m(TN_sq8KN@nG6itB=P(}0EZ^}nE&c}hUIFsd=u#7n`jQma*i*Z&TUB}E$ zsz_+(lcbd6$SfY9^TTi!Y$=CNRFtZV_^h#`y0i`%wcv&kFJ_x%L4x=w!vcnwP%C0$ z{2QARd*f?cSrmw@*xevyY!~N(uF6$hK_?lXGa{+ig=p@YO$zMV;{bKwd}YYqudD=r zR{-1D>Dk<*@g%=bLq99t83e+p)6m!|;~2M6%D%(r+0Ll%>4t%^v_JurI{2LANh3Gu zhLh2>pC$_zE`+0o!Qp8x=XAzik_q#I!ekveta1xFeHQi#DxaiHS-uuCLNSF^s+7bm zO%o@_tftg(0{dop8u@zv0(zYDI!sy{k`ECQmFfa-2K1=}F7)0Z~-eL*% zBYdj~^x&aBwW3>b=rbZvZ*4!6uwJS7xRMp6tXe{ZYCC^Dz4(mX#@71~+%HvV17cN5 z8AMikE9fgj!rTZFfMQYAxhmp#GGm$~J}*Zofd#Ib80_MOJbCN{RzNxz1y}F8nJMsp zk|TgC17HW+&Cg)EeKHAYZQWmEszGjvR6(Jkz1I`M7(iBsv)Oj(4#33!FE*y*ai3_T zf#|EmCfChc3geT7DfAql8Uo2{RCZb&n)>~8-b_<-O;g_EfrbQtj&yU6;kL_{HAhbD$1T8)RmAxcxC94fQv-{Cw>6f>oBC0MfHWexNR*zN$3B> z=XhZN552&(--3k--R(F5Dd(2y2+ZV)2mSBRMeaEJ!nY+-zfNnY2mJ7aSScXi<2R5< zB$g1t{IfrjfT%!9h*Fw|3KfGJ;gj_IJ2$7JYxEWL(zCds8Zc+Q{%J!_P9qtAVBcZjK$=}*E~5+KcvGpw5E313p5yQWsbvp?!c@e zovi`=mPtc&k;bb1uPLZ_=NQlwmd7=ob&e!QrsDZYjrJx-4tuJ}CWIFfNdDH*i<1wP zSA5USvl$^9kmf(MF9vK`V*+SSq3!u(PYxyUJBgf?6^s-Oh!%#9`(l4%NchE)=_!Ir z177`M5s1$r62cz%n`U51VPd$8Ze}ix=9xb*8Mc%*EHnt(2^V{*-+S061YpzC(>nqK zk{~SAER^>h;y1eT+CX9tFvG4hbnavnu}|XDbYmlA(#Sq!h1{faAbzd88Q9Qdp?vRa zXt=mRZ;wG9S7Yt!_`sng2IculpPh}Kfg9{<{y}_y6Snl?iVPuhV#5&m_9HniA^$#)(CZ%GJ>_v zdz@8QI`Alrk`tTHVs|>pg0u)aN^^uQ&U7fA(=s<^JOeb1*R3B9zmbOD5% zbpx?rkD2fSYR$~+ZFVwP+U8*$F5oOPmTP2eEWy94SER@1v~rQj2+ygSm|YO#yBx+giQ9LsE>dDT zid$<(bx+CHC7EF&1 z+n+b^Wj@+VAc=Yqp4|FonKm|8vfN||fDY;^U6_+w?6yoFO#4ned^qG=w~4FN1p`#@ zYHtIe-e92s_pe;rmdh5E8Lu2*Ny5J4i-$^covF9yI3$c|B^cd%c&=HS5bp2Qrc}%ZZhYw+`FEZgH?>Zmb-9 z7gjNe0%<@!0!nT_5opq({!<>oFajg=gG&v%nbM6uo3jW%b}@0#M8sKy#fvRIEAZmx zk^aEi(aTbIdl_Ioe7HBv0^8a&`QEWJ5?{wuEksXmGH2OFA`!Z;&hsA1$JLNIMF3v3 zSV_#BpJ5zEcl~202~p znea$Y7+q&lWCe<&Y@LrS+v(4XJyCkVAHud&`nzD^6bwP3D*g6TAJ zBkn~2eUKh+EJp*ljsu80)813{;Xp;C_VWueCMq#%mu#sVtnm2l!`aYW6zhns5R$q6 zK$^048ILY!j6z^La#2mwJetE^D^v!2iTx2LO&Jvl3wCCtT-q&Sn4zH|kt(UDyED(f zX)_(C>%WQC-FMK8|2g}-K!;1RpBZ)+=Zoubh59RC*k|D9par>5(S4gyd?dN4&dfGv zarp9BiGXUl$5Y3l8j2%Kcc6^GluFLs{9$PNez@$#MDqxED&%Eq*z1I)%YQJP%~#%i zxkYwHR)E-XAO^Y9v{xZ^?86~ubvIgbT-l2TX3a3VPNlCqU?)ArRrj zWu?~761PG;iI;uk!B`_(C~!xNo6{Z8lq&7EGd+CB#~HAgPsU$2%hQIfHSV~tRh;@#TklhiE3 z@5>b5FR)l~Cg1b$azw0%FrI$OZO#$_w9Qv&=PV3G&VB(c1Fw(Cx&@KANo+UJ-fk$fXu>}F`H(Dy#am02AYn2TZn)O!aT&;DkO~O^%b>1=5+O3Xi z4)FG#W|%AK20u#mKO^m_3USQ7+{JF?B`j{LyFU>{-3q29^Wmi?XqG~L22cv)i~lWS zXSM4idU`vDnA>x{UEkGam-N%~QnYNDS>-)dDLCp}?ZgU4P&94Nv6&2Fc2G1Kt-G?z z%E<+@8UN`D!=m&nUdh4-$S*@6kmdpiYVED=d^_&kpP`JIr8pGr|$<;-x_#*33%qRkAZGymb`kck9 zDYW}G1nd+Nfe08OBP^Z7gH$m%y=5vToAnby|AL?}@6J{Zz9TBf`{^g*IvW+g!BO!z zEnrikz1u%lzr~=m*ZaHn&b7{hv}7JD;IW!bo|^#A25@_xg&D~0Vl8~q5)+J%K*Ezr zs&VM8)#BjS`qKF0mRY3ntlFyp;_bMg)JruKE;zu^)uO?)mM(r)zW6*XU&6Eru)wKt zDZ{dW`L^5PFG4)!a*O1La3M-ci=n`47`bZix5opKli0etx)&4?wWOqG;yqlC`8dv; zw=bOKndWb-`-wFksQ_{!K^F6iE&u9tG`>GK5d%#jsP(J@TX;$C^SN7&o zK_la?dg!;X=^wuUIV~~}juPtg`UD*<6B4S^>6DPuz79S*S5y7LwBUxb<@~(S{FiLV2kxihUd$ikBnd0)@ zJ_{1YWQpBT54$;FJglf%txqy&rWJ-UJD-e#rYydkCy{-+6tOD0y#8)>&cC@!8&hrd zSNFKx3SaF%<^N6z0jXOf6D2#I_G5zdbSqX zZFI~6B91G$&&TWoqz=fLZtFinspT?~n$_ZH$QfbO!6oUn_2dQPvaNFSV~r1gJn}#C zm*LUo^9#b`pVMkYk)>6eGi$cT+TdAO!D+Kca+9)N;uxRAOzb5tC79HJ{LN1CdJp@&v#e{ zpNs=Un1KK|wiUL1Z6H)ClF#+@Fa^l=PzJ!+Qm2FQk0kQHS{y$DU@y{Rscp?$~8O#3=)fe`mykI11;DavbCS8q9Zw4-k*4yy6 z4A(JPnZ_ZP!v&JTp=bu?sQ$Q_I*fklZ)BPj87qMpLQ(}^WAV?7iBiq|U|qSb7dHc_ z-`ar4hx3vBvCN~z+Ms@s2zjJV4NmLDNh(5{^>$uPtNAGHCflSq zS&5K`x__Ej(aCtxq@6P(J2qZa-dF=KdjjFGciptpX+Ml1JQwu5<8K`R#<<+#RALou zut1_ylD5_6#p}3>SSwpnQ!`0Lu{*IB42|B_#e-k^OSMvaPOnz8Koaeq1woBn0ZS+N zc(zO&F!UDy%3_>0%@z*J*+Dqz)mXgW@IbumaqC>M{7;!2@4JtHT}p0)HMQjJ44H@>E()jNEK`V&`(?fyqTMv-!dcL{R*7ijU7dj?WkHn_A@EJ>MUdv zJn@Zuor#3;J%2jJT2C1#%mLYBE%OC<3_R+b;mSGC$};Yyz`sA!f_f}SZu%V_-vYJ9xpRp*xhdkKVoz7=mOYMA z)RgoY1kGZ%kf==F6O&GYE*N-WufDu1p9pJiHn#*p0b8R)V6z~FisA4^7SIyMb4_iCpH#J=(i^Tn~I}xT)jb;_Zm=z{k2SxXIK>0;-v!U_`TV-FkCCe0pHaD9L zYwqlXjIoxxbR=<8TWmN{%`MW0z}KkeL*rW5Mxy!GpXJ8e9=(1~1=kpgQ5IE1S*ro# z+3w=s79p``{9-tgLS>LK-JRwtF4q<y4{+B-JC4U^&5=G&5T?(4anL>}v5Ho}%50HJ_2d%Z~;E1h|OwYfDe3$>_Qxkke zNYU0s7{Pw{MxaY?blE|;Y>a=70+nLIM!q*oil^AqT`l&VnB%`yg%uQ!=86@gZpd%D zJ=Z6L!=2-c@mF-Vc#~)Cy<$^gY8_Zh7LgbHr9i_jKj%o)4RrzX#GXN<~`+wr0Eb>u`s@HY@d-DeV@PQTC< z_ep#;l!N8>!QP$jF(CWeveVKt7i5`gf~s&NaYBKhvk@A7o&bgbwTBU6wNqu;k7WUV zr}|-Wn|rS$r;+~+XEouHmeL2bofb7p)Vry)@rN(Jvps!*M%ef7f3{|eymu^(*J@`Y zOb_$NhQfv|_^MQ;snnFbCc4!LV0;Wzm;>*ARS!|?)JsO{2A($=H=&Hr5{?{4#8y-p?Gq*n8eAw0U zjXl3gAy6AEvz$$7)g|RyoKIOHv9lI6Psy4Mm+)JH%Tu7M`SqGsgOKb)^Kk=>uh?1@ znRUBtVTjfGV+!^6t6$1;<-E?Yix4a0$MWPZi>_BgMqY9dLA7f=`OvrjvJhCbw-~Q< zfx8HN9Koc7nl?;8ow#d5oy5q1TH35yp+3g+O>8Lmo`vBX>rn6!eDlR43&VebM&rew z>tx?X{#ISNpt`4#Cx)}NBzZGGk2tM%`o7=imZB10-diw5U%)K!V-$6ab*IeJ@I53t zou3dig4Os#xn5D0pyRG~3m4+9KEyh7Di<&*XM6c4(Hzk7u`V>Hnws;H>=@#oe=PR= z2utNWIue&=^K3mcUSDo96Q|J)5iCwUV#ZowdR#!wxExu)bt%`wZjS!;M;)fzAMI-x z`mio`3Vzp%uFoS?b58;oRXuHuKaaORfnyvrS zO8I2W!a_?`{ofs|&8UHv?`6w_dFwe!NRjurJNn0Ydtif%Naz&SfZ_hZ+Yt0iZQ!oC zuV^1hz*nlW_M87>IrYJpgF$k90VE%>=YJdWA9O_!uP#ecz_)q2Q67oQsX2{hS z|Jge$3$_&si?al(Wh_jnu?zImBfg?Hg^0Q4E^_UZ{dqhn7@0b6Das1w%6`*&zo3pdnftKOJX z(`U@!HCxF9H)Rw)vu!)tCM2IxC7JF#uMCbSGRv6~S(4U5JYH+Lx&#oY26#js*+y7{ zBn!~X;{^*M%=)gEc8Npzrmub8<*>}DR$$GSd4cW&+fn$-V<&D_nXJ^ z#-3s$wQA3HWw4+&)q2>nWS5`@Gy`4`WbzMf-X*X3;JVO`c~@~DUx3ZCN4Yll6WPs_ z9>P&UeYPV1fsOsDJ9z)KCgC$k@qKBN8J3fzMo!15yey%xj5r5(zki^ea^D{JUd<4T7wm#X2Yft$Zy|Q=1`x&y?1L{NP;#< zv+g^`IuIvOg2o+dzAXguy`9s*2R}~g2$&KnKiW;I)hDL=3uLT@`Rf_t$ zxYr78u+ouKS65)RvRZEviINaQ%5yr&^mEW6Ix{ReY1%KRf$HDejJxsxcG82(r&39? zpNC?u!Ny`)GP6>Wzqua~8J$4yu_TuS8BnOAU+mA>NB;R+V7zjPeyL+jn@{p({RsUw z9lfdu3plpLHu)8&fx{F2p=te|B;5A?*!1ok7zL|;l8dKzV(QB#yoEp8kk+n2(kqS% zuoOyrPL004LfCn;LI-7zEkv4^qiM{q+tPDPQIbYok|$HLKtV{PncE#|@KF~Frf5=! zS~Uo=4rn|NBxN~`G#)mBl@Dw3epFvb$e2Fm-SRgs)4(#avF&M5;Gd84tP}W2j&A>E zMIa+&WQ*-e{sRkLb^P8Gc^LFP$M9brC!%J~kHu=%%R{b9pyno;mWqbpSG5OA---AU0%_+NdyOgHXc}csQ~X|BV1wK4hBs zEUo_;3@>e$uWQg;uyWemQEBmf%Bk7fDOr~ZIole0Hougu>WitLZXA_&as)bu?i=vqrW8e+^MAcY_jURIdn&f9L4$E z0Y;MA?!7=e7g}ZK{;cSM{2vc;icZ>446l{NK)#Eg`y1B!{a3!Aj~DiQ^L6A$o<)U+ z2$>LY6^%S<5@>TvA02$TsmsD22*YfRg%w8MW!H#m&|-kHc`QYDxZSxYE3~{{5X0Mu zd;!bC_^K1siQ3Flz$2v0_Wq^2QzrPFoAnqIHnVQxCAFzy8wK*9+W&L*HF9EZ#KuW) zsom=GWorC2Q#?j4Sheif2DZZA#Jua{a@uSZiL<$b5Q%mm)}|RFen-=8!KN!1vly`% zp8SY&od$udcyphZFoz~Ltyba+EynI!>Zc_m!5mBsy_(W7!U-!+T)E|a+Ev}3kvhp< zhVKf3Xb)iue7CbIDt6h@glq>InS!2>)4_oO*M8ao3`ITWnwmaQT_6B&R!kHJ{RpO= zhj_--g42K#^BWqikH3cr-IRK6*0}q};}ck`-GW%Um(MgAJE9GwfzMNYb-(RVdc39S z#(#T)hAr2*64rPgP}-zzj5qhbXPGu1#f**QYisp$*dQ4GFsQ8{f7e?UOe})_qzFtj zqYATb8{RYuI@rd!CPLc2OdnAlyaHfRl!CXbI4>7U)V7Pow5E7;!x z@uC?Zcj6n+v9yKlpN-^yO~r39eY59Zd;(gY-!&TSnA`-iCYwhl^CQ>EdA95yR%B&%vF4pzmClw1JQ^^Jjn(Dmo<3Y_^)-rTk>ibK z9@Z<&fSP>G78u<$s`ZsfIoMq{n(WA!v>T+jmT;ZghN#VSbkH=>3Nztnm&GM~bqh-g zv&-4HeC_`F(OX&3tubj9PTaklD7n0*}1PdQFM%l=S+15iS&B0Hn zrl_2o+OLp7-D)nOp_dO~JBjXE0tZ<2M!#WR|!N1}MP~f?1=!6s) za&Lc%K$3~=Fanqg)u#(&l%!$jNO_;=3GVzf3`4H0!1JEgK3zJcFm`OaesL?d0~s#C zh6YWh2*js+o`f|{db9@~7JS-ttCta$?j-8?ahFv!;{-ixo&P>_H|BAPcNC%AJ1 z@gKo+H_!?DJwY4@o2F&UVy<~X*&; zf@h=G)IAA??v@48%|lhwG`s+CG585J1za4$K|uZ3=EoNG$SmA|+iAKf;#Bi&L3`qJ zSe&5H#!6>;xz6mF8*+EtAl5T%b!?`DR+3h1O>;Y;meS9!DjmL#-@4}R4m>*98(Nhs z_biCX{4cG#TAilMZ{Hz}bAEFoVjP=)7&l;rKL!>iLJK|x@+%=Hpu>XMRv@TghZFXf z0I+%tA?>9+4>|d#+pk)KE0R5o>(*M!P;R=o+Z_8zG-!3D=*ronE16$nnVJ~al@8gD z^@2r)ika9J+^GwJzacjNiho==NHNq9{3y5xY1-p=Mm89b7Rp7aw^4wSN~k>2M)`>b zW%|emT6-k48QQYn9DCrv+(X%cPO5chIZ*9*>o3CqdoXoPI~ZK*|6q;$3;d_I#v@+^ zVGG$`D`UDFMomOIHT)$CUI{J5|EsqCxt1blAr8BDaKmU*gdYZ{h7e8{k>dL3q)n_> zvSOlD5^4--=&|}~gGrHK7bDY(3`^fJhJWO3ST!v@-;wFkz zU-0f@Y)^u4SJ7*nbsO#57N0aNMF#(jS;%_aoqim_Dzy2Y1(A4}9Ep1< z3_&BB23Vj>$iez~pXo%loQD37qK&nJ<#S+hN%7k&FNm^nFWd8Pp2JRGWWKWJv;*k3 zb_8C9LY3kN1k9hod+%sLqALPboKRAZ6hrql2MUCl_5`(YrT*9$Ft7n~QGZ}q9qO7+ zTE?Y>pd-0i(SK@Te#(3Q|5*Sd_l^!U1D{+B*BGDdJM-;g_sKQB#Xs2`SeGRGLYZ>7 zu7pVu=9tW+qq zbyEiRoiLi({$~S!Fd-(N`1HQO4LmbrzIH@jjo}a|t5ipA+$!@^WpcEtQ2o ze&@CsQvYbsWXpX$L+;x4pW<=?K+H`8tfIbXIsi}?0fGP10C{4*+2ya=NUuIv+`Q>? zEYw^gmnmt@wB0v|ni4<@>=&S51{T=xraZij8l>#tOb9(&^8vaHST?whn18E!o`97l z7QnIze?k>LR57`A$(uXLFD`7EhEUQiri!^etLZj6lHj4X_m7?FEA%%w1<`3RRHQiY zPZ}?*ke!`KT2p9ol*=wbm>Oq8LCk_jMSAqU?JTWKTm%mVfz8Pcby`kH^v;cq;#JYB zFa&e~KveLKnK>|QQzjum_aTdt3eYEsz(zrZ!4;Sv-;obt z4fG-;vxEkJaf6UtKH3R(;@>E9v9f@?L)J77U5)DnRA4pBbYX!5w#OGSyA7;xyjgXc zm;%UL-I{KUV^u8Iir0q$;bLHK6f2WRg8aEPN!!h|>=Cdxj~qTYJOSWjrh$bsXZi%y z?NGe z9YeG-t19v)zS{ksg;S2B7l7Mm&Ejc>Q|89?bUym`Xv*(-v(`dZz$08Ocrl|7LB61Y zs&;w@*t>C*7fPBB>}F)k!!YUGIl$BS=lHGE?4Ck7Vqxig`TTm7+6jhdy-eYC+MgVZ zr#uR$$I%NjL7R8ECLBCLKV2VYw`|LkLELm+BFYx0hcWa{t?FOoPeQ zmlb({MP#~7AM-pJx$|i=H;u+~XJvw0sk}%O7eUm$Qxdit9=>ej9;S zm`CFGlh{a|_c>Hg?7fAWRXMhRg@{3t(IBd28Wkd7G21Q2f5W)fM^4KL&w*I?hC=4` zDVBJeUI&K3=WOj3M#+pM-6ON2NxvU#*o7mvyj>0_^KJmew6ePT>N?sOHEuCpARGi- z`(;S#cx!77v%>QlGg6HpCmp|tx(D6SVPe<4(eh!GEmQa5DQwDn!dt&){n~-KnYh0< z8YKsZ`Pp9oZu6Ohb_LoG^-w$ngY0iMQNa%sBDOZcf%@9uDJhU>$>FABY$5&A0< zjsRCPOYFPYGaKC$UC5W@!)BDCy)B6uT>A6jvIBUhN_isr=IQf+y1>tS*ApvNR7g#3 zYxH8OL_i$33~czE5=vi8vpX%xa*P;dVy*eao7y#T}elvOJ|n6q%{^H0doDI)`~I31SOkQx6Kb0 z(F<_Gm2}s71KvR5eI4JqHrF$Xm|!C-WeLML-qKG>EbUp`w-aXsmxLA%MZ*Jjz)g8d zXJ1N?T{iISUS(9>&1iNkzglqOR29mg31KHA}U5N`n2s{ciGP6i%8QVQfKHa!hkJ5vsaD`_{j>pgORVGoY9}@N z=exnp9n7<(sH}`Btp&kX>O3)ZZ~FJ@G=!sw%K5#9e9a z-LX)QOE+Ec;iE8+4>^vQsj1cc%C7>0OQQZevaS0m!tt*(nH18Cng-944E{yyq+}=K zI(sG)x3+y`BIdlC;i0BV;4p#!L%|~5dpi-%xMi@!${|KrvS@>W@2YR5^W2yW>nrdh zOx~WfQK=m|#<|cNIrlTHgMgXql16x6Ng-S)P(dwQWLZlxx+zM{fYD?ydWr!9#5NJP z%nhQ%vv}*+?gQDUDjUHjL7zQG-gy!9C_qH^UsrgJ#=lONc|Kvag(}zLPwKpyPVOdsqJ6hLTh>_mM3q; zf2L9RF@)m81-c0H{2tryb20vohW}g<{?o|&sJ1&VPtOK*cHyKc)`jzjD`lI7R31Vt z;ql}xX;L1&32i^=8`q!qi|Q(K_@=aC z1Olpl?(%@3`}egS7ugne5rnd42`L6prQ*U(%MhqQ>2rBtvEq+f2Y7K)k8{IW5z*MQ zI6M*~+RUF8`#F7h%a$VOG3_@SQ|Nw_Fsmh0k}?V>YJ*6c&H!$5JF05i>Lw0$K|%xj ziRBf{2_bo-s^&3gPsWa{U)_Fl;m{O- zqpvbiGABf%#I-)pa+K!Qo@Wn8J*(h!%4<<}()Lfs%goR8tY%gLWvyCX;?ecIKI$>! zma-{)>tMRY@rtadL9P&xTN>YKRguE9IWoq7R^t_xJobeh7AtG@Jlv<=;+0#3zu^4h1Y7)6Is|U6zwlYWa^fNb2A3hSDb`f zJU4*8N22fF;x-PcWR|^5V!~mrEPL^{?`#*7*oAHh@kj2{x+IOa#KstJGe%_o^pA>B zN^q-Py~ZUyDJMX-WynRHGGhN*Ya>s4z?{m}<4}h!r@x_JzDrFSa!E`(^fCHYSqADX zKt}C}JepfW%HA~FKa$aWpwF28`^z#^f$xAoYcrv=fBmpMk_)HkxP>8AVIaY_?f*+%+!YCX&X?GvYJyh4e7fVd=IdDTg*fYO zvVeT}ZXzn_n_A~Gz|DK*X(Y+0CC2RQGDXz z=@AaTPIFkL@!4@;+SJqf4IQF?^D?$~jKdxF2_*df<2$ibm$41@mQF_2 zIWQlelVc6nJ8d>Y<5arZh+=-7O;C6)@|ZUHfD%Mbfhf)kHfalSu;_?0#sJAyh6#H%+lu zm)*&v%yydW9Ap}0vM|2A+&qY=MNQ*JN>VPepRL~%U(7ofsP?qWQ+meb7Eq9)e>rCs zqpHL9OIZ)*A{YFjY2gN)e9gP%Bf9JiE?>hWr@7(%w!c=#B~orI^6&*xoU@igSl zP*{%88ww>vhCl?lA0*7yED!0CRmpYHTaXonxvCn1GhWs3N%D?3?OltG8Xbjd53BJw zwn&4WRpuLo62laF&Y@wBrB@vOYC^-Fhdp8ulWKeueO{j4b^NZ4v-iEE5qNK26uLzv zkuKKS?H-X+^4n^Y2Qygyn)&I}=QSEvK&F^3#O%X&l&$12D#l-zpnHNuS3q@$|vCR2DV$ zJ||)l0ZULQMmroCf!XbNN!VN?Kg|wz|8O!V@wZf{4A=|xd4jU^eOu;n{pOe07J0!Y zx(!cPmwOMm0Jx@a>%IL&+mZqASj3h6R+;L8xJm9U?D|4lYFW)%jJ*Lba0+e)6OU$>uqiajr3qKjG>f z1^+qcuz;{eL9NNdQycsex2C`Ka(rdUAy`ic(cGET|9IpM2Pcn+$^PDO>%?Hv_(%@K z$vuEFXZ{(bc65Hk*?{Gd#>&N{KHw0?ajjP33#&#^)-SYQogk6h2YOf$Vq@`m_au!S zi@o|Sh^jmIrsiw%zL=8&r`wwW`QCTP$!@yMR{d-C*8h|^sQ2w4iF#Tr(xK;+TQw}M zTzO-mWK~cg5L`<}Ejv_%FV--;8Ig5#Oeef1#FDnA`c_;q*w2(@V6wQ3-jFHFZvj`>o3unFdn45oxiV_lYB)b*1r~V)HkV=kRXVeT0mph!HT7~ zTC|k-S)tYyZaoy?@JoU&f`XPt+0AzUtm}T}_ zme?G`*{}GmWQD$S>dlzk1{$Wt1L3qCpoF(NF5(4i*lp3wyVPS7!pWH zIW+u4IP54Cc0*fVEmdo6H4i{jQy$}0lN!!k4Fg6iF`pKLrQ1$@lKB7ttrubM@fodB zTHV&{mT%9do@F}0Z6-q(>Kspsxk~21?yLK@f7ky81lS&=vRk_?>C5ks?HF zR>Twb4|cU+6_}{rbMw@Q1*CiEB-b{M;Pk`&ZP@L=OqjLUFZXI2d2v4GMb|G~hrEO% zp>kD$gfUhC@#J(NdXROZf640`(4S4sqq(rax=7|WYj)u(U2pb@V^lo%8zuFpf8+i= zZ^~@wWeoCckyAzHm(?FdG`K^Fc?N)gg&*xhWO(3Efe!|nwx=?upTCJ33>ccITp%l? zoiKlVSEs~e!M<|)t!Z7Tkb1lSZ@`L=Vf3Qdf$vtktOtOc8y`cv2zNqWepl2dRN~#a zeBI(ej*Aamg$-@HCS9RkyX}t0zppD#aC> zCM#TjmnoPgE379t>UBz4$|yOhDrl8dGH!2ejdA&%VIBQSe*bGm-!Z={j2{Ll+Bum#wqY9tN>}gw?G;K-4aaIurstB293jBV z0=v+c%nM&}p5Dvjkmo)No)gLsBxECI>f}FK-+b4LxnuQD=tpRpf4AooLy29X=6M4w z(}9mbNQaP~hz9oPC*vv3IvOANETt>IPKfr=Vx0_7)K^Oh-MI68daYkoD%WgU;|?{7 zB&KCK@N=P>D*u6%qR_v?QMV-i*umlBak}=l@d&P zUZ}=`Vg{;#^Hx(VZ0qS99Ib0~rlaqh-?M=4>CiMBQp&h3ewqW+R9d$;HBW|8*1Lf< zCE=;zQ*UUplRq}ARB=goPIzPv>&Rsq?&}bz=>(}{wWtV(7^S;0Wd|}%07vg)G-@cMB zzgS;uzu(jqUc7Dop;b!G{d8j6Jne-EVl-Hgri1ke{i+C>8^ayH)OhhlX&bT}SJ1b1W-D@|~5cP>hPyE}MMe4XoU z=Hi*M%>W*}H8uARZ0k8mD0VX|$>vqkr^QyO6DtlvrE>@mgn?M4n$_Am)y=a0@D?lx zk0?Jjt(^p{UPMF4E13APpkUVG;o)obHB2Sj61v%?MmM%)-|OWCUuz9sXT9Le9yv)I zW+lAebtIAaB;s`VHNZMr@rj3O&l-+RvySdB(SX@TrK?i%!Mfiz(_JirBqHw8>aGE^ z=PqwJnCMDWwaRm|F%Rp&QWZ}How(TkX0XaM;rmddcpb&+ocHBH&q1A91giZ4OpZ7{ zyF7!GI@26+0ACh<%ONP3RKxY`w^}&kT7K<A<4T3qRyEXAO z$Z|dfA4ZINslMp*TnCnloi3J)S9(?p&kNV#3cWeK3f2g|PzL@lXI|s;4Cmnnc`lsJO1_-x1 z{o$)op)4n$sGr?!g~T+5!`}7ochibA@^hY9HIPt|&Pe&H_zI92tuM!S;v2-|CK7+{ z5!E=hv+!oJ%h+)#WYtRK^_jgsY(KKKivFc|#3}uot;tUV%-4R`ISDk#ChE0mbs%vX%t; zBWt1_Krg&__1CkvH6G*R>>c5x-Gb29_a@RvD#PswWN0DSl4VxEI9OZ88NgN5eBBGf;g4!np#7EjZJ!64ANF#tRpi^A;}2mx(UmC}3P>p=a5LB7CRxA7~RA~bB8F+3<7waCw98Idzy zc~z}GH>=7LP>S%z&DNAHeJ#m{ASO$fKBbj(;SQPv_#BpmPM+U2Q$^M1k%uc+ZHxzq zV195x6M_j)x$Su}mr&sonwm*fH4)SIlUi2i1kS_fPh(YK++q=}8VbAmi|IDcu1ABnIiAs(JQeX@Fd}Ms_bU&_VC(Yq z7W=Dtxt|@VpPu>OozZ;j+^Or(yDt#x!E9POWzyhm{bNebXRTt*f3#2=<FlggZR;6_;j9i*NVx6vAG@493L>YMJ(EQP)&YNSJ?^W5io)m1qb6 z2{4ZbC&9-N)GPZe#Ok zcncdHNsR36nCn3aCQ~|B9Ghw;!LTK*PP1r2L%#T}hwVDf8hM3hU6Gms8YUS#$@k%L z-4gK_f@wHDv(p0M7u71YhR5(Z;1*v)N}k<~(3|CVV>1!jjW&bG@IXtQE*E+7WU~2! zsoC`8>$~SUPT{OuR*Ri1`eG5VCGu)V3tcs|e>Vf7cq@|*uQhTt$)$Hu*1Jo`%X(rx6;{1gF; z@_AJCejtO?F&@nlH<89kui`pXlNJ~V4fH|T7xoOGq#evJ1r{$)Gp7T41SMfdeuq=V zL?B+WnrqBqi>1R%pWMsawU|i^Kh~%sf4AaZua@f74xp%Ha@B9-CT>}upV%x~s^q6e zSs(T#a;KnPeTGWxYRs@1 zNcbMHp46x_>$mEVuQ7;I#$cp5^OpLaq_pLd%;?+7qT~gt4z+MuhB|Cbm4>iYTB!2KnP6tUf*8OO zq@V;f9Gz^9@8||ksn#}M5TBH{)kN=(qBbjX&rSsHt^7(Sa0?GjDm(WiVP7Gt`-qzm zi%H|Jmi5LLywNTqv&qD#tgI=+A{&A_gSP?7pzJr)SrBTc-~Q^Fa=RM9FH2Xq>`nou zM~?g+9!lZeZ`jTJJ=H{_T}Sion}_e_P?08AIm3;0NiJ$Kmp;t-Kh)&if=*wVW6YNs z!nIMJ0L)@y#pygB0(~Ne1DONPL)CgWTIAuMscSq6sZ8npG|(6s=Mo?4BBtyPUr>%q zJzvPTUNrPA)NLRvCOUMF%FBpcjaYU7uTctx(YU1hen-)U$M?on#JPKEj`A?a*|;Xr zhY|z{ zq`kGaO^*vDjj!FZkm7_10kl4o$n(8UD~lvAm?8 zmBasC=W8Ps+u}|~{sJlJT!X-b5;bWb=HT-$+U*zL%it!+LOE0>(xC<~4pn{02keJu zZ&;gfBEz`H?zb5pSZ!g-qjyV3?edA~Gn+k5!tzYvHtqqTu|I*GKgD zk`%(4|T>O{Y@#;mEiaHq*41p!9Q=~f6E7KH&LuBQ{CvZIXgCSaXL=mC@(4zw&MS=d~(|ACOTo*Iy6WlmE zoVS046o?$%5ZVbrcKSS`a8gq=U?8s>k41Vi9hUqiTkP5Ak=os7zwr3)7$pYV!)WpC z6nV50=Z<;}^*uf{1ewiEY@hDuc(mDQ+x48zeQo@%SIdg?{MFo|xu2^B!wT*tKLdv2 z>^Y{Paxeh;gh?v%6(&gc`^x^9rJVDj4E;8^Q7{rUwDpMkXNT7w&jxd!d(vMcsU#K9 zo4}7`!xIuJq17}2^YGclNqk)-AvUc}8^{yXRDxh3NiyqUnJ#0 zSl=1Oo9F$9XJeqZL3ay{0us7LRyUa$Ho(8O`9ovHMx?eqq&sPqg!v!g@S)jhxdsV8T8~aZzO-;p4Hf$ecH&0I#u42}2dGGz$ie03eU0*+B+f?*FY^}qXywS4BPsvB zk$$UK+Uv_cB)0^LXbVxQS`f!G4%J?y6svB!F zB6!{EaBtJ8nG#|{j`@JGj>9GGMu<11kljFqU9@k=pDFbod&@i$_`aSL z?%9GATAaIQ?et}EV_2|EC-TLcV!PS0o9%&0O7+wWi^;^il?Yeo)3n25-#`9W^H$x> za{VB?PUtyrCXaMSXLy-#{BHKziexJr`w~|E7c4|sm|Q7^yoaf4BVApTPQtY~=HxH- z)Iu~zaB(EY+~uiaOf45P86;#>_TH;<_Wf3{^p{)YH4)&Zvg-S>>1rPmb!akV#~#^` z6{*|2Doz>LTj%(lF3g46FcNZ7HXT2>alcnZOF~yA>_)7vJN#VQWqjqwu*YaoB1oit zjteym{cX~G$AU_+Y(_0lH&d*&+m1w}db?=NWrIUT8r#|GJzKQsAU+Kya|^G8x< z43coWUnswL$a`LydJ8-7l}q<;M6VLv*E^72N<;Dx1KZWvI&O%OuFsw6|85&Q1nNA{ zyIA~J>Y0`;dBV4ZQf*fj&8oE9aL9NhPvDWkn*F|Hn_(Zr&8@7O{w{pies`0PzrF^= z-MEsW`?2$EvQs_7hU9>*`(sO^k}9X=h$PHQJht0@kryIvVZV51>d%pBXhM6)d&K^8 z_ppWN=k3q>*B?4sVr22&1>R4o0J}UxKt8ih^L+V9dNjo!n>)7P{ws@EHKj;ckN(~x zzlc35m^u&YkVZ^Bd5`%US);?gcoy~W%@Nq+B>SlnZLX7bGs?oPvMOJT(rcljztiP@ zQcRhixw|h55R642F{kR5>_w38UYo72pp*6DEtOyPS~w#ARGG$V=%d3tH7|(+=-1c- zPqFS%JS|^y&GiZ3j0d2|(O_~fcZXb~EZ|CZum$DV_u(W)uza&Pdmdyw>7t^uf)I6G zfBE#wUY~ju*FP>~xperaBv$?Z=f%Nv1zUBpyVsIN?Rcb`Zg8Sr#rI0>c&C?d%Ktxvq^pSB$#rhE zF@KtAOtq--4FM~jPma5^6Qv#>y3M|lPQ?krx={t|{Zn1S>eH^Y#;zRyLEYx%8ivJ| zGqkTej>`^TYt+wPRz8Y!#YOGrj&2fSf_-SAGF8it3$Bc>dL6GneV$q^xUqdwHT;_w zo?B6J1jIovPj5w8zS=5;kR z{{xz4o}=zjHN9M?L-6|sYpz9|5`+^&vBe>+&`AiVWb?;yNWAd+g69F=W~UX1>^!lA z3o$OQOp!UI2I45$aqme12WhC4{lzATS;B*T(TINZjRR7{V7&I<_fBhELG^~6T?0*U z4#I2%4}zYEm1##%KC^Hut5oGZ(&K=%0%rlgFwcEG^J3?t<7&~m4-~pkbLMjm>{*jP zbgN%oa`trcXCub)D0q9z^^tVTD zoeYw?2pIw+L?#B8Zq=uUo7uLH2g{N2day~zbs;wK^>d$>YB2lJK3j``Z|Z@uc>zuo znFP*osGo^w=>^IgBz~$a7X55ux|WU3E&i+B+-8#{hSiuA1h;lduFX;>D(gMI9ualM zf*&FAO!{B6a&AeV)H~FoXVeS!xiDL4*g_2XfwhDtVqmG42EiyYGEn-rCGy=_1| zh~-GMoP{45j%(WFA%Osk9ThKKOK|lMc~uvFgpPTBn}8zc$6q*)XB@$GIH>Ya`d7qf zS`u(MzgMz5PuA{xZDQ47=k$0i%2y;?W4^{`JM@X`pC%K*Dk?lc^`-N)T>i)TdL}## zAP*yJBx4Wx307BJ8oZ@B+NhmxaIrFdlK#%8%uhY-L;2IT{>f+Bz#jLyyI4*4`qwA?EvVrYNk0ZL7I6_wkWQ6M=sa;f+t}(%UiXQ@x5A9P-1UYZG0k`Q*LY zUI7*JYSFK1K=u|yB>w}31JkmJE%zdtEI>Xp`Tiajlzo;oa}USnGJI#wCH5U(dF#oH zHPOl!a{fr^&rjD=b}#lP_W@qHo(ir3E~0LR=~EM_v#Ufkep?LFH&g~{3YaXip%D7o z%QMfAx-XWw6>eh&nPQXjXG`l(+}NDP0%CZ{!c_B2lIpFgx@*d$xZCV}qv{&#zL}rx zB&cgc*xib5kfN@|702MQq-%dRV+v7mcqZdHTVCxBrxhxrk{8o43e^xQ;CvACfPmAG z-TmoPTjWr*@1X`Z>yGa}9Sr0H}wdH+(U*=i%qfYomz@#18Z9bHR-@AU6( zCrQH{=?cT|=bVg<*y0qkBDn}vKynH?qxKpt1l#=%-y8AC-*)Y*mVPSSiGwbb3$H_u z7FG_=uNEB(4V(PxvwYfI*i;e&cv&X98SZ_ZN@>67ThaD!{z2o2<$)hr;MYrLAs>3@9{uf~5MN|Ah_ zO|X}{N2&0<&fx1Gv&Ee!CsdpB=Rfvz;FXS#Ppt2@^Kv7|lBkQCc&;ADf+Fz`Z`8Fz zSN?-ZqnmVlKJRD$9*DeSaL?`VWhAMcfs@JAt}SEgAy801kzSSXJSwObsWR1VOda>3 zNiiz2R|kTnw;aF%UW{;wCc5=iMNk9m=@SD^S{G~nQlpmC=YD@o04e08isek+6_oKd zH&Z`4o>qq?8Lg;4F)wxHkS>(fHU><(44Fk&&vS z`Q0hT@4TN$HUm}P*LG$q53RWSz93n8O}_k=orx*z5o9Z;(iT9BSs?|;r$77M`pG=g z3q=gsK5#KzCRz0GS>dUwFR70rFS>)?5Rq{GQ^^2&vc)UXJy-lMqI8CC6jNTh+41eX z!B{@ea6QNeW_!ap%+8F0=kl4mg~>SUquMtzLZk}+>NK+A4`!K{vHzEgxwRdt^lg_M z(*zw0P_X_yV;1i8dZH|P?|Ra)8$2X1gsGPlf3Dd|;iR3D@1pT3D5zxBX`+mYXMCKCIN7 zp#!siFQyo(#O2?3K{#(UO_<%D(w|+^OniHXcLP8axthGgN7!zr_lWw5mcevrx*kNH zjQZe(t9xG%ZS0Mu`a1)3r1hxjdNaE3LGy57$25GgzjP4T^ z$oQ=BHH#75A5?(uH?`}joOTkeIy5}&MzrD_;ORBr=HHgKiF5tqe3~I^)LIgk33mjd z>TNvUhGf5Zi&C`{NkdGPHpz8>5pypMmcS3UsX#I zC}Hjp?TsSO?HdjTM0ujFFJ3rCCj2^D`87jXlBd~IUb`W!uW%$}kI#aEJxZ@HHBQFG17HggVfXszYcuG-X~Ce|iQmJ^9;=G_b%|w(9RHxa z$PVwvntc=({It^A;l4~@#0Rx`4%PD%rW;Rtp2aIZV!w6%B*F2iDrnjvn*)b$`dNK6 z!=?!82a^U|qBaS>zc$8a5BTgWjdjg3&!D0E$=Z78-RDdxZ1Vp{m!alc0L777yUj!@ zElmxn(PcBiNJlgI@Pq5X7(1PJsx7iX)BBvag4U; zPA~^o4$CV279)IXw)pT;1C&Ztf{T2jRQ;)$G1ZwratcnI&dfX^)n@JH$4}A@gL}La zUQsV0GE^Uq0F*hY{=LrmpuJ~$Jb$t1MS^+LYO-?wctg;@54kwFjalNsY`L=k&14qq zP>7&_!;%b`n_OPFQYzFYEX*OsnX;4H+5xNSOJeKJ7v{sJE>1Xg>gTF*`5>QE4gE(j z`e`-WQx%$e_^N1nO>n0zv zZtGXwD$)hIwmIR}`IiNiF5I@at_5Mq)R`WqSY?j$ewmeo{08{DpxVIZ;_G+KgN*3# z?S-NuX_l|%nJ=uHK101rwRZ_zJGp+Eonrs-_#|*uLT}TKm`~60X36AC!i1;tEwcir zB_n5)V7nSU=}Bz`BL+?b=+P_A(vR4T`;THp{gJPZXBm2B1nUDI%r(1DGb7Zu$?Nw{ zxA3BP2*)>NaJH@8`meT2`!kvz7Ha_iSBj!9@I%%O0vkd2Y7V85*?2mpZ2WYA7E$)j z^cCXugU6(JgpCjeRy?Ao+F7uPTIB;X+vl*p!H4(AI+%Tr0uO+Gy{Sz9d0}iO)i(X~ z8=UjgcIb3nwlwX)V21A(r}hLJ8uZP2WzCc#u0P!xDi4S03i+uE-}x%LHEOJ7${&;u zM(wjzBv6HE4-i0P{|{3cqJK&v&it~eO!UhvjZp`Ql$k+iy&$X$&x&IqGxR)q|BWj; zBb`z~$u|f-;$vUS$BCy;W9YibK`~F}-s#n7A|_GQ&s5v6Dy}pNwu2RWoUG8)O5(jb zg#Z)Vin?-NV3E6myrXaG6tlbhn}7tUJf68KmJTcwiCziQh4TK%~~84t)UQuiQ{C(BnQ?zql00 zcvZi;_68?Gtd}K$f?hw}q4})WnV*4b9{OkX-@~kWO#C}Z1r(KMXR0AOwseNxjw0gPmM^j4Tdf!i%E)U!mFBX4gz>wQT^YbeLDsHY;_2Z63_e3{|GOJUdbS(! zo?rUmtRYwjt@{OOkAu)il#q@&E}B;FN204sPEi~`wBLj?W*)c}spwad15825Igo4Q ziG6it!mS^nf@TToBXt)qEXC2l6$=)p;oCZn&1?X(H?pTZT_dPS2bIS?lC|4jyvz8A zPP5Jv^(fR!C#Bj}@<-ePk(n=5p@p@O#?(X4E8gdEx5&*sBQ#kdcig|0!ms75?=0@~$(%PH*c(Y1S zy92>~@{e_U9|>Vr;GEGgUjP-^WtlYB^pM7B|`NcHR4B8w(FvhD>0x-rcdN4Je-D!dTu0wQId~@R!&e*P?e?V-H~Bdx*v|; zoKl>!t%!I~rfxnn8j%RmnaprVj41f@*q{9Zy|0#Sa(h3ghn~vy%REDIp7^uQEj*vX zyu-eN%DWG&&LMTG!G=8W2t|EZW#PkZ#LMUsBL4ETd-wUU?D|z9Bb$r8af_~-2QD-> z-jld@2Xslx`K7O>i;Z$tRzFR*QxJFP9~96FE7eyG18TqRC&;F}#bs>@RBb47EIue} zXhP4rM4yGo4w}-=99fo0|1a_^tC?w<;13ON4!;-i~?`N_LP>+Xd z5v8j#ILS8HJIhk3mY31p;P2)Z48o68Uem~&_rY7J$j#wL9ux24QeF~a#jD*dZHE0c zw;0+e^?DtzJc7vJlJWndY=N3Qjv@q4G({`_yJpD*7<#F2c{p31-49fwF<_j(#dKSB z#CB9rDqL7~EVw~iqL6*vkemEwRW?2>c?2{R>#C!}aB1|?gkhjn^k*QLy`!w?_H5xS z$r{9azIwtB5<0;)FTmYS{+kH¨G+hcBD571pOalIsGwZj*3SjuAXfd;0K-7R6W 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�OmmOZRZ%b9DVA3s*V>A0(`R`k=c#={WVs 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 From bc5dc3db8ba2ff53fbcc3a6db89f621fec3b7a3c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 31 May 2013 00:41:42 +0800 Subject: [PATCH 205/513] Fix checking --- autoload/pymode/lint.vim | 1 - autoload/pymode/queue.vim | 8 +++++--- pylibs/pymode/lint.py | 4 ++-- pylibs/pymode/queue.py | 8 +++++++- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 082f80b8..68782e18 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -15,7 +15,6 @@ fun! pymode#lint#Check() "{{{ let g:pymode_lint_buffer = bufnr('%') py from pymode import lint - py queue.stop_queue(False) py lint.check_file() endfunction " }}} diff --git a/autoload/pymode/queue.vim b/autoload/pymode/queue.vim index 8de52cdb..b3160ee0 100644 --- a/autoload/pymode/queue.vim +++ b/autoload/pymode/queue.vim @@ -5,9 +5,11 @@ fun! pymode#queue#Poll() "{{{ " Update interval if mode() == 'i' - let p = getpos('.') - silent exe 'call feedkeys("\\", "n")' - call setpos('.', p) + if col('.') == 1 + call feedkeys("\\", "n") + else + call feedkeys("\\", "n") + endif else call feedkeys("f\e", "n") endif diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index dbc164e6..4c28c25c 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import locale +import json from pylama.main import run @@ -56,8 +57,7 @@ def run_checkers(checkers=None, ignore=None, buf=None, select=None, def parse_result(result, buf=None, **kwargs): - command(('let g:qf_list = {0}'.format(repr(result)).replace( - '\': u', '\': '))) + command('let g:qf_list = ' + json.dumps(result)) command('call pymode#lint#Parse({0})'.format(buf.number)) # pymode:lint_ignore=W0622 diff --git a/pylibs/pymode/queue.py b/pylibs/pymode/queue.py index 5aa5c26d..e43de9c8 100644 --- a/pylibs/pymode/queue.py +++ b/pylibs/pymode/queue.py @@ -9,13 +9,14 @@ MAX_LIFE = 60 CHECK_INTERVAL = .2 RESULTS = Queue() +TEST = 1 class Task(threading.Thread): def __init__(self, *args, **kwargs): - self.stop = threading.Event() threading.Thread.__init__(self, *args, **kwargs) + self.stop = threading.Event() def run(self): """ Run the task. @@ -34,6 +35,11 @@ def run(self): def add_task(target, title=None, *args, **kwargs): " Add all tasks. " + # Only one task at time + for thread in threading.enumerate(): + if isinstance(thread, Task): + return True + task = Task(target=target, args=args, kwargs=kwargs) task.daemon = True task.start() From f98662e64525e5e729f931d88adf856b14133fee Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 31 May 2013 10:39:25 +0800 Subject: [PATCH 206/513] Fix troubleshooting function --- autoload/pymode/troubleshooting.vim | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/autoload/pymode/troubleshooting.vim b/autoload/pymode/troubleshooting.vim index 02e00a26..dca1ea7d 100644 --- a/autoload/pymode/troubleshooting.vim +++ b/autoload/pymode/troubleshooting.vim @@ -1,5 +1,7 @@ " DESC: Get debug information about pymode problem fun! pymode#troubleshooting#Test() "{{{ + runtime ftplugin/python/init-pymode.vim + new setlocal buftype=nofile bufhidden=delete noswapfile nowrap @@ -33,7 +35,9 @@ fun! pymode#troubleshooting#Test() "{{{ call append('$', 'let pymode = ' . string(g:pymode)) if g:pymode call append('$', 'let pymode_path = ' . string(g:pymode_path)) - call append('$', 'let pymode_paths = ' . string(g:pymode_paths)) + if g:pymode_path + call append('$', 'let pymode_paths = ' . string(g:pymode_paths)) + end call append('$', 'let pymode_doc = ' . string(g:pymode_doc)) if g:pymode_doc @@ -63,6 +67,12 @@ fun! pymode#troubleshooting#Test() "{{{ endif call append('$', 'let pymode_rope = ' . string(g:pymode_rope)) + if g:pymode_rope + call append('$', 'let pymode_rope_autocomplete_map = ' . string(g:pymode_rope_autocomplete_map)) + call append('$', 'let pymode_rope_auto_project = ' . string(g:pymode_rope_auto_project)) + call append('$', 'let pymode_rope_auto_project_open = ' . string(g:pymode_rope_auto_project_open)) + call append('$', 'let pymode_rope_auto_session_manage = ' . string(g:pymode_rope_auto_session_manage)) + end call append('$', 'let pymode_folding = ' . string(g:pymode_folding)) call append('$', 'let pymode_breakpoint = ' . string(g:pymode_breakpoint)) call append('$', 'let pymode_syntax = ' . string(g:pymode_syntax)) @@ -75,6 +85,7 @@ fun! pymode#troubleshooting#Test() "{{{ endif if python + call append('$', '') call append('$', 'VIM python paths:') call append('$', '-----------------') python << EOF From 7bff0c2c9515b07aab29432bae3cbd664bd29918 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 31 May 2013 18:09:24 +0800 Subject: [PATCH 207/513] Update pylama --- pylibs/pylama/__init__.py | 16 ++++++++++++++-- pylibs/pylama/hook.py | 2 +- pylibs/pylama/inirama.py | 2 +- pylibs/pylama/main.py | 2 +- pylibs/pylama/mccabe.py | 2 +- pylibs/pylama/utils.py | 2 +- 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index c0426772..bcdf2ebc 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -1,6 +1,18 @@ -" pylama -- Python code audit. " +""" + Code audit tool for python. Pylama wraps these tools: -version_info = 0, 3, 7 + * PEP8_ (c) 2012-2013, Florent Xicluna; + * PyFlakes_ (c) 2005-2013, Kevin Watters; + * Pylint_ (c) 2013, Logilab; + * Mccabe_ (c) Ned Batchelder; + + | `Pylint doesnt supported in python3.` + + :copyright: 2013 by Kirill Klenov. + :license: BSD, see LICENSE for more details. +""" + +version_info = 1, 0, 1 __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/hook.py b/pylibs/pylama/hook.py index 93152041..c2a5d80b 100644 --- a/pylibs/pylama/hook.py +++ b/pylibs/pylama/hook.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals, print_function, absolute_import +from __future__ import print_function, absolute_import import sys from os import path as op, chmod diff --git a/pylibs/pylama/inirama.py b/pylibs/pylama/inirama.py index 7887dafa..da770227 100644 --- a/pylibs/pylama/inirama.py +++ b/pylibs/pylama/inirama.py @@ -2,7 +2,7 @@ Parse INI files. """ -from __future__ import unicode_literals, print_function, absolute_import +from __future__ import print_function, absolute_import import io import re diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index 20f68218..409099cb 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -1,5 +1,5 @@ from __future__ import ( - unicode_literals, print_function, absolute_import, with_statement + print_function, absolute_import, with_statement ) import fnmatch diff --git a/pylibs/pylama/mccabe.py b/pylibs/pylama/mccabe.py index c95f561d..19931dbe 100644 --- a/pylibs/pylama/mccabe.py +++ b/pylibs/pylama/mccabe.py @@ -4,7 +4,7 @@ MIT License. """ from __future__ import ( - unicode_literals, print_function, absolute_import, with_statement + print_function, absolute_import, with_statement ) import sys diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py index 51ab1d4d..d8f6febc 100644 --- a/pylibs/pylama/utils.py +++ b/pylibs/pylama/utils.py @@ -1,5 +1,5 @@ from __future__ import ( - unicode_literals, print_function, absolute_import, with_statement + print_function, absolute_import, with_statement ) import _ast From 4648395d7ba694473d14d95e954fa9d8dfcf893f Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 31 May 2013 18:27:50 +0800 Subject: [PATCH 208/513] update pylama --- pylibs/pylama/__init__.py | 2 +- pylibs/pylama/hook.py | 2 +- pylibs/pylama/inirama.py | 2 +- pylibs/pylama/main.py | 6 ++---- pylibs/pylama/mccabe.py | 4 +--- pylibs/pylama/utils.py | 6 ++---- 6 files changed, 8 insertions(+), 14 deletions(-) diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index bcdf2ebc..2be0a99b 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -12,7 +12,7 @@ :license: BSD, see LICENSE for more details. """ -version_info = 1, 0, 1 +version_info = 1, 0, 2 __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/hook.py b/pylibs/pylama/hook.py index c2a5d80b..36fa211c 100644 --- a/pylibs/pylama/hook.py +++ b/pylibs/pylama/hook.py @@ -1,4 +1,4 @@ -from __future__ import print_function, absolute_import +from __future__ import absolute_import import sys from os import path as op, chmod diff --git a/pylibs/pylama/inirama.py b/pylibs/pylama/inirama.py index da770227..42cf2389 100644 --- a/pylibs/pylama/inirama.py +++ b/pylibs/pylama/inirama.py @@ -2,7 +2,7 @@ Parse INI files. """ -from __future__ import print_function, absolute_import +from __future__ import absolute_import import io import re diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index 409099cb..92c3e455 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -1,6 +1,4 @@ -from __future__ import ( - print_function, absolute_import, with_statement -) +from __future__ import absolute_import, with_statement import fnmatch import logging @@ -75,7 +73,7 @@ def run(path, ignore=None, select=None, linters=DEFAULT_LINTERS, config=None, lnum=e.lineno or 0, type='E', col=e.offset or 0, - text=e.args[0] + text=e.args[0] + ' [%s]' % lint )) except Exception: diff --git a/pylibs/pylama/mccabe.py b/pylibs/pylama/mccabe.py index 19931dbe..71f024a9 100644 --- a/pylibs/pylama/mccabe.py +++ b/pylibs/pylama/mccabe.py @@ -3,9 +3,7 @@ http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html MIT License. """ -from __future__ import ( - print_function, absolute_import, with_statement -) +from __future__ import absolute_import, with_statement import sys diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py index d8f6febc..78460ce4 100644 --- a/pylibs/pylama/utils.py +++ b/pylibs/pylama/utils.py @@ -1,6 +1,4 @@ -from __future__ import ( - print_function, absolute_import, with_statement -) +from __future__ import absolute_import, with_statement import _ast from os import path as op, environ @@ -52,7 +50,7 @@ def pep8(path, **meta): def mccabe(path, code=None, complexity=8, **meta): " MCCabe code checking. " - return get_code_complexity(code, complexity, filename=path) + return get_code_complexity(code, complexity, filename=path) or [] def pyflakes(path, code=None, **meta): From 77d60d53b9425fc82edfcb294e1948c7c22c7c2c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 6 Jun 2013 16:52:59 +0800 Subject: [PATCH 209/513] Lint respects pylama.ini and pymode.ini files. --- pylibs/pylama/inirama.py | 3 ++ pylibs/pymode/interface.py | 11 +++++-- pylibs/pymode/lint.py | 62 ++++++++++++++++++++++++-------------- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/pylibs/pylama/inirama.py b/pylibs/pylama/inirama.py index 42cf2389..8226c139 100644 --- a/pylibs/pylama/inirama.py +++ b/pylibs/pylama/inirama.py @@ -258,6 +258,9 @@ def __getitem__(self, name): self.sections[name] = self.section_type(self) return self.sections[name] + def __contains__(self, name): + return name in self.sections + def __repr__(self): return "".format(self.sections) diff --git a/pylibs/pymode/interface.py b/pylibs/pymode/interface.py index 8e70d331..fe99d540 100644 --- a/pylibs/pymode/interface.py +++ b/pylibs/pymode/interface.py @@ -12,7 +12,8 @@ def get_var(name): def get_bvar(name): - return (int(vim.eval("exists('b:pymode_%s')" % name)) and vim.eval("b:pymode_%s" % name)) or None + return (int(vim.eval("exists('b:pymode_%s')" % name)) + and vim.eval("b:pymode_%s" % name)) or None def get_current_buffer(): @@ -24,4 +25,10 @@ def show_message(message): def command(cmd): - vim.command(cmd) + return vim.command(cmd) + + +def eval_code(code): + return vim.eval(code) + +# lint_ignore=F0401 diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index 4c28c25c..51930bce 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -3,9 +3,11 @@ import locale import json -from pylama.main import run +from pylama.main import run, prepare_params +from pylama.inirama import Namespace +from os import path as op -from .interface import get_option, get_var, get_current_buffer, command +from . import interface from .queue import add_task @@ -16,48 +18,62 @@ def check_file(): - checkers = get_option('lint_checker').split(',') + checkers = interface.get_option('lint_checker').split(',') + buf = interface.get_current_buffer() + + # Check configuration from `pymode.ini` + curdir = interface.eval_code('getcwd()') + config = Namespace() + config.default_section = 'main' + config.read(op.join(curdir, 'pylama.ini'), op.join(curdir, 'pymode.ini')) ignore = set([ i for i in ( - get_option('lint_ignore').split(',') + - get_var('lint_ignore').split(',')) - if i + interface.get_option('lint_ignore').split(',') + + interface.get_var('lint_ignore').split(',') + + config.default.get('ignore', '').split(',') + ) if i ]) + select = set([ s for s in ( - get_option('lint_select').split(',') + - get_var('lint_select').split(',')) - if s + interface.get_option('lint_select').split(',') + + interface.get_var('lint_select').split(',') + + config.default.get('select', '').split(',') + ) if s ]) - buf = get_current_buffer() - complexity = int(get_option('lint_mccabe_complexity') or 0) + complexity = int(interface.get_option('lint_mccabe_complexity') or 0) + + params = None + relpath = op.relpath(buf.name, curdir) + if relpath in config: + params = prepare_params(config[relpath]) add_task( run_checkers, callback=parse_result, title='Code checking', - checkers=checkers, - ignore=ignore, - buf=buf, - select=select, - complexity=complexity) + # params + checkers=checkers, ignore=ignore, buf=buf, select=select, + complexity=complexity, config=params, + ) def run_checkers(checkers=None, ignore=None, buf=None, select=None, - complexity=None, callback=None): + complexity=None, callback=None, config=None): - filename = buf.name - pylint_options = '--rcfile={0} -r n'.format(get_var('lint_config')).split() + pylint_options = '--rcfile={0} -r n'.format( + interface.get_var('lint_config')).split() - return run(filename, ignore=ignore, select=select, linters=checkers, - pylint=pylint_options, complexity=complexity) + return run( + buf.name, ignore=ignore, select=select, linters=checkers, + pylint=pylint_options, complexity=complexity, config=config) def parse_result(result, buf=None, **kwargs): - command('let g:qf_list = ' + json.dumps(result)) - command('call pymode#lint#Parse({0})'.format(buf.number)) + interface.command('let g:qf_list = ' + json.dumps(result)) + interface.command('call pymode#lint#Parse({0})'.format(buf.number)) # pymode:lint_ignore=W0622 From 2b7c208c9c6f9198c8fb16d510e5a2555422f286 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 7 Jun 2013 11:00:22 +0800 Subject: [PATCH 210/513] Update pylama --- pylibs/pylama/__init__.py | 11 +- pylibs/pylama/core.py | 112 +++++++ pylibs/pylama/hook.py | 18 +- pylibs/pylama/inirama.py | 68 +++- pylibs/pylama/main.py | 203 ++++-------- pylibs/pylama/pep257.py | 676 ++++++++++++++++++++++++++++++++++++++ pylibs/pylama/tasks.py | 105 ++++++ pylibs/pylama/utils.py | 71 +++- pylibs/pymode/lint.py | 3 +- 9 files changed, 1098 insertions(+), 169 deletions(-) create mode 100644 pylibs/pylama/core.py create mode 100644 pylibs/pylama/pep257.py create mode 100644 pylibs/pylama/tasks.py diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index 2be0a99b..6d528b30 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -1,18 +1,11 @@ """ - Code audit tool for python. Pylama wraps these tools: - - * PEP8_ (c) 2012-2013, Florent Xicluna; - * PyFlakes_ (c) 2005-2013, Kevin Watters; - * Pylint_ (c) 2013, Logilab; - * Mccabe_ (c) Ned Batchelder; - - | `Pylint doesnt supported in python3.` + Code audit tool for python. :copyright: 2013 by Kirill Klenov. :license: BSD, see LICENSE for more details. """ -version_info = 1, 0, 2 +version_info = 1, 0, 4 __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/core.py b/pylibs/pylama/core.py new file mode 100644 index 00000000..70915075 --- /dev/null +++ b/pylibs/pylama/core.py @@ -0,0 +1,112 @@ +""" Pylama core. +""" +import logging +import re + +from . import utils + + +DEFAULT_LINTERS = 'pep8', 'pyflakes', 'mccabe' +LOGGER = logging.getLogger('pylama') +MODERE = re.compile(r'^\s*#\s+(?:pymode\:)?((?:lint[\w_]*=[^:\n\s]+:?)+)', + re.I | re.M) +SKIP_PATTERN = '# nolint' +STREAM = logging.StreamHandler() + +LOGGER.addHandler(STREAM) + + +def run(path, ignore=None, select=None, linters=DEFAULT_LINTERS, config=None, + **meta): + """ Run code checking for path. + + :return errors: list of dictionaries with error's information + + """ + errors = [] + ignore = ignore and list(ignore) or [] + select = select and list(select) or [] + + try: + with open(path, 'rU') as f: + code = f.read() + '\n\n' + params = config or __parse_modeline(code) + params['skip'] = [False] + for line in code.split('\n'): + params['skip'].append(line.endswith(SKIP_PATTERN)) + + if params.get('lint_ignore'): + ignore += params.get('lint_ignore').split(',') + + if params.get('lint_select'): + select += params.get('lint_select').split(',') + + if params.get('lint'): + for lint in linters: + try: + linter = getattr(utils, lint) + except AttributeError: + LOGGER.warning("Linter `%s` not found.", lint) + continue + + result = linter(path, code=code, **meta) + for e in result: + e['col'] = e.get('col') or 0 + e['lnum'] = e.get('lnum') or 0 + e['type'] = e.get('type') or 'E' + e['text'] = "{0} [{1}]".format((e.get( + 'text') or '').strip() + .replace("'", "\"").split('\n')[0], lint) + e['filename'] = path or '' + try: + if not params['skip'][e['lnum']]: + errors.append(e) + except IndexError: + continue + + except IOError as e: + errors.append(dict( + lnum=0, + type='E', + col=0, + text=str(e) + )) + + except SyntaxError as e: + errors.append(dict( + lnum=e.lineno or 0, + type='E', + col=e.offset or 0, + text=e.args[0] + ' [%s]' % lint + )) + + except Exception: + import traceback + logging.error(traceback.format_exc()) + + errors = [er for er in errors if __ignore_error(er, select, ignore)] + return sorted(errors, key=lambda x: x['lnum']) + + +def __parse_modeline(code): + """ Parse modeline params from file. + + :return dict: Linter params. + + """ + seek = MODERE.search(code) + params = dict(lint=1) + if seek: + params = dict(v.split('=') for v in seek.group(1).split(':')) + params['lint'] = int(params.get('lint', 1)) + return params + + +def __ignore_error(e, select, ignore): + for s in select: + if e['text'].startswith(s): + return True + for i in ignore: + if e['text'].startswith(i): + return False + return True diff --git a/pylibs/pylama/hook.py b/pylibs/pylama/hook.py index 36fa211c..fa50899c 100644 --- a/pylibs/pylama/hook.py +++ b/pylibs/pylama/hook.py @@ -1,3 +1,5 @@ +""" SCM hooks. +""" from __future__ import absolute_import import sys @@ -14,6 +16,11 @@ def run(command): + """ Run a shell command. + + :return str: Stdout + + """ p = Popen(command.split(), stdout=PIPE, stderr=PIPE) (stdout, stderr) = p.communicate() return (p.returncode, [line.strip() for line in stdout.splitlines()], @@ -21,6 +28,8 @@ def run(command): def git_hook(): + """ Run pylama after git commit. """ + from .main import check_files _, files_modified, _ = run("git diff-index --cached --name-only HEAD") LOGGER.setLevel('WARN') @@ -28,6 +37,8 @@ def git_hook(): def hg_hook(ui, repo, **kwargs): + """ Run pylama after mercurial commit. """ + from .main import check_files seen = set() paths = [] @@ -44,6 +55,7 @@ def hg_hook(ui, repo, **kwargs): def install_git(path): + """ Install hook in Git repository. """ hook = op.join(path, 'pre-commit') with open(hook, 'w+') as fd: fd.write("""#!/usr/bin/env python @@ -54,10 +66,11 @@ def install_git(path): sys.exit(git_hook()) """) chmod(hook, 484) - return True def install_hg(path): + """ Install hook in Mercurial repository. """ + hook = op.join(path, 'hgrc') if not op.isfile(hook): open(hook, 'w+').close() @@ -74,10 +87,11 @@ def install_hg(path): c.set('hooks', 'qrefresh', 'python:pylama.hooks.hg_hook') c.write(open(path, 'w+')) - return True def install_hook(path): + """ Auto definition of SCM and hook installation. """ + git = op.join(path, '.git', 'hooks') hg = op.join(path, '.hg') if op.exists(git): diff --git a/pylibs/pylama/inirama.py b/pylibs/pylama/inirama.py index 8226c139..22998b2b 100644 --- a/pylibs/pylama/inirama.py +++ b/pylibs/pylama/inirama.py @@ -1,8 +1,14 @@ """ - Parse INI files. + Inirama is a python module that parses INI files. + .. include:: ../README.rst + :start-line: 5 + :end-line: 12 + + :copyright: 2013 by Kirill Klenov. + :license: BSD, see LICENSE for more details. """ -from __future__ import absolute_import +from __future__ import unicode_literals, print_function import io import re @@ -10,11 +16,11 @@ from collections import MutableMapping try: from collections import OrderedDict -except ImportError as e: +except ImportError: from ordereddict import OrderedDict -__version__ = '0.2.9' +__version__ = '0.4.0' __project__ = 'Inirama' __author__ = "Kirill Klenov " __license__ = "BSD" @@ -183,9 +189,33 @@ def __getitem__(self, name): class Namespace(object): + """ Default class for parsing INI. + + :param **default_items: Default items for default section. + + Usage + ----- + + :: + + from inirama import Namespace + ns = Namespace() + ns.read('config.ini') + + print ns['section']['key'] + + ns['other']['new'] = 'value' + ns.write('new_config.ini') + + """ + #: Name of default section (:attr:`~inirama.Namespace.default`) default_section = 'DEFAULT' + + #: Dont raise any exception on file reading erorrs silent_read = True + + #: Class for generating sections section_type = Section def __init__(self, **default_items): @@ -201,6 +231,11 @@ def default(self): def read(self, *files, **params): """ Read and parse INI files. + + :param *files: Files for reading + :param **params: Params for parsing + + Set `update=False` for prevent values redefinition. """ for f in files: try: @@ -214,7 +249,7 @@ def read(self, *files, **params): def write(self, f): """ - Write self as INI file. + Write namespace as INI file. :param f: File object or path to file. """ @@ -233,7 +268,10 @@ def write(self, f): f.close() def parse(self, source, update=True, **params): - """ Parse INI source. + """ Parse INI source as string. + + :param source: Source of INI + :param update: Replace alredy defined items """ scanner = INIScanner(source) scanner.scan() @@ -266,7 +304,23 @@ def __repr__(self): class InterpolationNamespace(Namespace): + """ That implements the interpolation feature. + + :: + + from inirama import InterpolationNamespace + + ns = InterpolationNamespace() + ns.parse(''' + [main] + test = value + foo = bar {test} + more_deep = wow {foo} + ''') + print ns['main']['more_deep'] # wow bar value + + """ section_type = InterpolationSection -# lint_ignore=W0201,R0924,F0401 +# lint_ignore=W0201,R0924 diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index 92c3e455..a9cf9473 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -1,3 +1,5 @@ +""" Pylama shell integration. +""" from __future__ import absolute_import, with_statement import fnmatch @@ -8,93 +10,14 @@ from os import getcwd, walk, path as op from . import utils, version -from .inirama import Namespace +from .core import DEFAULT_LINTERS, LOGGER, STREAM -DEFAULT_LINTERS = 'pep8', 'pyflakes', 'mccabe' DEFAULT_COMPLEXITY = 10 -LOGGER = logging.Logger('pylama') -STREAM = logging.StreamHandler() -LOGGER.addHandler(STREAM) - -SKIP_PATTERN = '# nolint' - - -def run(path, ignore=None, select=None, linters=DEFAULT_LINTERS, config=None, - **meta): - errors = [] - ignore = ignore and list(ignore) or [] - select = select and list(select) or [] - - try: - with open(path, 'rU') as f: - code = f.read() + '\n\n' - params = config or parse_modeline(code) - params['skip'] = [False] - for line in code.split('\n'): - params['skip'].append(line.endswith(SKIP_PATTERN)) - - if params.get('lint_ignore'): - ignore += params.get('lint_ignore').split(',') - - if params.get('lint_select'): - select += params.get('lint_select').split(',') - - if params.get('lint'): - for lint in linters: - try: - linter = getattr(utils, lint) - except AttributeError: - logging.warning("Linter `{0}` not found.".format(lint)) - continue - - result = linter(path, code=code, **meta) - for e in result: - e['col'] = e.get('col') or 0 - e['lnum'] = e.get('lnum') or 0 - e['type'] = e.get('type') or 'E' - e['text'] = "{0} [{1}]".format((e.get( - 'text') or '').strip() - .replace("'", "\"").split('\n')[0], lint) - e['filename'] = path or '' - if not params['skip'][e['lnum']]: - errors.append(e) - - except IOError as e: - errors.append(dict( - lnum=0, - type='E', - col=0, - text=str(e) - )) - - except SyntaxError as e: - errors.append(dict( - lnum=e.lineno or 0, - type='E', - col=e.offset or 0, - text=e.args[0] + ' [%s]' % lint - )) - - except Exception: - import traceback - logging.error(traceback.format_exc()) - - errors = [er for er in errors if _ignore_error(er, select, ignore)] - return sorted(errors, key=lambda x: x['lnum']) - - -def _ignore_error(e, select, ignore): - for s in select: - if e['text'].startswith(s): - return True - for i in ignore: - if e['text'].startswith(i): - return False - return True def shell(): + """ Endpoint for console. """ curdir = getcwd() parser = ArgumentParser(description="Code audit tool for python.") parser.add_argument("path", nargs='?', default=curdir, @@ -116,7 +39,10 @@ def shell(): parser.add_argument( "--linters", "-l", default=','.join(DEFAULT_LINTERS), type=split_csp_list, - help="Select linters. (comma-separated)") + help=( + "Select linters. (comma-separated). Choices are %s." + % ','.join(s for s in utils.__all__) + )) parser.add_argument( "--ignore", "-i", default='', type=split_csp_list, @@ -131,59 +57,73 @@ def shell(): parser.add_argument("--report", "-r", help="Filename for report.") parser.add_argument("--hook", action="store_true", help="Install Git (Mercurial) hook.") + parser.add_argument( + "--async", action="store_true", + help="Enable async mode. Usefull for checking a lot of files. " + "Dont supported with pylint.") parser.add_argument( "--options", "-o", default=op.join(curdir, 'pylama.ini'), help="Select configuration file. By default is '/pylama.ini'") options = parser.parse_args() actions = dict((a.dest, a) for a in parser._actions) - # Setup LOGGER - LOGGER.setLevel(logging.INFO if options.verbose else logging.WARN) - if options.report: - LOGGER.removeHandler(STREAM) - LOGGER.addHandler(logging.FileHandler(options.report, mode='w')) - # Read options from configuration file + from .inirama import Namespace + config = Namespace() config.default_section = 'main' - LOGGER.info('Try to read configuration from: ' + options.options) config.read(options.options) for k, v in config.default.items(): action = actions.get(k) if action: - LOGGER.info('Find option %s (%s)' % (k, v)) + LOGGER.info('Find option %s (%s)', k, v) name, value = action.dest, action.type(v)\ if callable(action.type) else v + if action.const: + value = bool(int(value)) setattr(options, name, value) + # Setup LOGGER + LOGGER.setLevel(logging.INFO if options.verbose else logging.WARN) + if options.report: + LOGGER.removeHandler(STREAM) + LOGGER.addHandler(logging.FileHandler(options.report, mode='w')) + LOGGER.info('Try to read configuration from: ' + options.options) + # Install VSC hook if options.hook: from .hook import install_hook - return install_hook(options.path) + install_hook(options.path) + + else: - paths = [options.path] + paths = [options.path] - if op.isdir(options.path): - paths = [] - for root, _, files in walk(options.path): - paths += [op.join(root, f) for f in files if f.endswith('.py')] + if op.isdir(options.path): + paths = [] + for root, _, files in walk(options.path): + paths += [op.join(root, f) for f in files if f.endswith('.py')] - check_files( - paths, - rootpath=options.path, - skip=options.skip, - frmt=options.format, - ignore=options.ignore, - select=options.select, - linters=options.linters, - complexity=options.complexity, - config=config, - ) + check_files( + paths, + async=options.async, + rootpath=options.path, + skip=options.skip, + frmt=options.format, + ignore=options.ignore, + select=options.select, + linters=options.linters, + complexity=options.complexity, + config=config, + ) -def check_files(paths, rootpath=None, skip=None, frmt="pep8", +def check_files(paths, rootpath=None, skip=None, frmt="pep8", async=False, select=None, ignore=None, linters=DEFAULT_LINTERS, complexity=DEFAULT_COMPLEXITY, config=None): + """ Check files. """ + from .tasks import async_check_files + rootpath = rootpath or getcwd() pattern = "%(rel)s:%(lnum)s:%(col)s: %(text)s" if frmt == 'pylint': @@ -195,49 +135,38 @@ def check_files(paths, rootpath=None, skip=None, frmt="pep8", if key != 'main': params[op.abspath(key)] = prepare_params(section) - errors = [] - + work_paths = [] for path in paths: path = op.abspath(path) - if any(pattern.match(path) for pattern in skip): - LOGGER.info('Skip path: %s' % path) + if skip and any(pattern.match(path) for pattern in skip): + LOGGER.info('Skip path: %s', path) continue + work_paths.append(path) - LOGGER.info("Parse file: %s" % path) - errors = run(path, ignore=ignore, select=select, linters=linters, - complexity=complexity, config=params.get(path)) - for error in errors: - try: - error['rel'] = op.relpath( - error['filename'], op.dirname(rootpath)) - error['col'] = error.get('col', 1) - LOGGER.warning(pattern, error) - except KeyError: - continue - - sys.exit(int(bool(errors))) + errors = async_check_files( + work_paths, async=async, rootpath=rootpath, ignore=ignore, + select=select, linters=linters, complexity=complexity, params=params) + for error in errors: + LOGGER.warning(pattern, error) -MODERE = re.compile( - r'^\s*#\s+(?:pymode\:)?((?:lint[\w_]*=[^:\n\s]+:?)+)', re.I | re.M) + sys.exit(int(bool(errors))) -def parse_modeline(code): - seek = MODERE.search(code) - params = dict(lint=1) - if seek: - params = dict(v.split('=') for v in seek.group(1).split(':')) - params['lint'] = int(params.get('lint', 1)) - return params +def prepare_params(section): + """ Parse modeline params from configuration. + :return dict: Linter params. -def prepare_params(section): + """ params = dict(section) params['lint'] = int(params.get('lint', 1)) return params +def __parse_options(args=None): + pass + + if __name__ == '__main__': shell() - -# lint_ignore=R0914,C901,W0212 diff --git a/pylibs/pylama/pep257.py b/pylibs/pylama/pep257.py new file mode 100644 index 00000000..aab6a91f --- /dev/null +++ b/pylibs/pylama/pep257.py @@ -0,0 +1,676 @@ +#! /usr/bin/env python +"""Static analysis tool for checking docstring conventions and style. + +About +----- + +Currently implemented checks cover most of PEP257: +http://www.python.org/dev/peps/pep-0257/ + +After PEP257 is covered and tested, other checks might be added, +e.g. NumPy docstring conventions is the first candidate: +https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt + +The main repository of this program is located at: +http://github.com/GreenSteam/pep257 + +Creating own checks +------------------- + +In order to add your own check, create a function in "Checks functions" +section below. The function should take 3 parameters: + +docstring : str + Docstring to check, as it is in file (with quotes). +context : str + Docstring's context (e.g. function's source code). +is_script : bool + Whether the docstring is script with #! or not. + +Depending on 1st parameter name, the function will be called with +different type of docstring: + + * module_docstring + * function_docstring + * class_docstring + * method_docstring + * def_docstring (i.e. function-docstrings + method-docstrings) + * docstring (i.e. all above docstring types) + +E.g. the following function will be fed only class-docstrings: + + def your_check(class_docstring, context, is_script): + pass + +If for a certain function, class, etc. a docstring does not exist, +then `None` will be passed, which should be taken into account. + +To signify that a check passed successfully simply `return` from the +check function. If a check failed, return `True`. If a check failed +and you can provide the precise position where it failed, return a +tuple (start_position, end_position), where start and end positions +are integers specifying where in `context` the failure occured. + +Also, see examples in "Check functions" section. + +""" + +from curses.ascii import isascii +import inspect +from optparse import OptionParser +from os import walk +from os.path import abspath, basename, expanduser, isdir, isfile +from os.path import join as path_join +import re +import sys +import tokenize as tk + + +try: + from StringIO import StringIO +except ImportError: + # Python 3.0 and later + from io import StringIO + + +try: + all + any +except NameError: + # Python 2.4 and earlier + def all(iterable): + for element in iterable: + if not element: + return False + return True + + def any(iterable): + for element in iterable: + if element: + return True + return False + + +try: + next +except NameError: + # Python 2.5 and earlier + def next(obj): + return obj.next() + + +# +# Helper functions +# + +def cached(f): + """A decorator that caches function results. + + No cache expiration is currently done. + + """ + cache = {} + + def cached_func(*args, **kwargs): + key = (args, tuple(kwargs.items())) + if key in cache: + return cache[key] + else: + res = f(*args, **kwargs) + cache[key] = res + return res + return cached_func + + +def yield_list(f): + """Convert generator into list-returning function (decorator).""" + return lambda *arg, **kw: list(f(*arg, **kw)) + + +def remove_comments(s): + return re.sub('#[^\n]', '', s) + + +def abs_pos(marker, source): + """Return absolute position in source given (line, character) marker.""" + line, char = marker + lines = StringIO(source).readlines() + return len(''.join(lines[:line - 1])) + char + + +def rel_pos(abs_pos, source): + """Given absolute position, return relative (line, character) in source.""" + lines = StringIO(source).readlines() + nchars = len(source) + assert nchars >= abs_pos + while nchars > abs_pos: + assert nchars >= abs_pos + nchars -= len(lines[-1]) + lines.pop() + return len(lines) + 1, abs_pos - len(''.join(lines)) + + +# +# Parsing +# + + +def parse_module_docstring(source): + for kind, value, _, _, _ in tk.generate_tokens(StringIO(source).readline): + if kind in [tk.COMMENT, tk.NEWLINE, tk.NL]: + continue + elif kind == tk.STRING: # first STRING should be docstring + return value + else: + return None + + +def parse_docstring(source, what=''): + """Parse docstring given `def` or `class` source.""" + if what.startswith('module'): + return parse_module_docstring(source) + token_gen = tk.generate_tokens(StringIO(source).readline) + try: + kind = None + while kind != tk.INDENT: + kind, _, _, _, _ = next(token_gen) + kind, value, _, _, _ = next(token_gen) + if kind == tk.STRING: # STRING after INDENT is a docstring + return value + except StopIteration: + pass + + +@yield_list +def parse_top_level(source, keyword): + """Parse top-level functions or classes.""" + token_gen = tk.generate_tokens(StringIO(source).readline) + kind, value, char = None, None, None + while True: + start, end = None, None + while not (kind == tk.NAME and value == keyword and char == 0): + kind, value, (line, char), _, _ = next(token_gen) + start = line, char + while not (kind == tk.DEDENT and value == '' and char == 0): + kind, value, (line, char), _, _ = next(token_gen) + end = line, char + yield source[abs_pos(start, source): abs_pos(end, source)] + + +@cached +def parse_functions(source): + return parse_top_level(source, 'def') + + +@cached +def parse_classes(source): + return parse_top_level(source, 'class') + + +def skip_indented_block(token_gen): + kind, value, start, end, raw = next(token_gen) + while kind != tk.INDENT: + kind, value, start, end, raw = next(token_gen) + indent = 1 + for kind, value, start, end, raw in token_gen: + if kind == tk.INDENT: + indent += 1 + elif kind == tk.DEDENT: + indent -= 1 + if indent == 0: + return kind, value, start, end, raw + + +@cached +@yield_list +def parse_methods(source): + source = ''.join(parse_classes(source)) + token_gen = tk.generate_tokens(StringIO(source).readline) + kind, value, char = None, None, None + while True: + start, end = None, None + while not (kind == tk.NAME and value == 'def'): + kind, value, (line, char), _, _ = next(token_gen) + start = line, char + kind, value, (line, char), _, _ = skip_indented_block(token_gen) + end = line, char + yield source[abs_pos(start, source): abs_pos(end, source)] + + +def parse_contexts(source, kind): + if kind == 'module_docstring': + return [source] + if kind == 'function_docstring': + return parse_functions(source) + if kind == 'class_docstring': + return parse_classes(source) + if kind == 'method_docstring': + return parse_methods(source) + if kind == 'def_docstring': + return parse_functions(source) + parse_methods(source) + if kind == 'docstring': + return ([source] + parse_functions(source) + + parse_classes(source) + parse_methods(source)) + + +# +# Framework +# + + +class Error(object): + + """Error in docstring style. + + * Stores relevant data about the error, + * provides format for printing an error, + * provides __lt__ method to sort errors. + + """ + + # options that define how errors are printed + explain = False + range = False + quote = False + + def __init__(self, filename, source, docstring, context, + explanation, start=None, end=None): + self.filename = filename + self.source = source + self.docstring = docstring + self.context = context + self.explanation = explanation.strip() + + if start is None: + self.start = source.find(context) + context.find(docstring) + else: + self.start = source.find(context) + start + self.line, self.char = rel_pos(self.start, self.source) + + if end is None: + self.end = self.start + len(docstring) + else: + self.end = source.find(context) + end + self.end_line, self.end_char = rel_pos(self.end, self.source) + + def __str__(self): + s = self.filename + ':%d:%d' % (self.line, self.char) + if self.range: + s += '..%d:%d' % (self.end_line, self.end_char) + if self.explain: + s += ': ' + self.explanation + '\n' + else: + s += ': ' + self.explanation.split('\n')[0].strip() + if self.quote: + quote = self.source[self.start:self.end].strip() + s += '\n> ' + '\n> '.join(quote.split('\n')) + '\n' + return s + + def __lt__(self, other): + return (self.filename, self.start) < (other.filename, other.start) + + +@yield_list +def find_checks(keyword): + for function in globals().values(): + if inspect.isfunction(function): + args = inspect.getargspec(function)[0] + if args and args[0] == keyword: + yield function + + +@yield_list +def check_source(source, filename): + keywords = ['module_docstring', 'function_docstring', + 'class_docstring', 'method_docstring', + 'def_docstring', 'docstring'] # TODO? 'nested_docstring'] + is_script = source.startswith('#!') or \ + basename(filename).startswith('test_') + for keyword in keywords: + for check in find_checks(keyword): + for context in parse_contexts(source, keyword): + docstring = parse_docstring(context, keyword) + result = check(docstring, context, is_script) + if result: + positions = [] if result is True else result + yield Error(filename, source, docstring, context, + check.__doc__, *positions) + + +def find_input_files(filenames): + """ Return a list of input files. + + `filenames` is a list of filenames, which may be either files + or directories. Files within subdirectories are added + recursively. + + """ + input_files = [] + + filenames = [abspath(expanduser(f)) for f in filenames] + for filename in filenames: + if isdir(filename): + for root, _dirs, files in walk(filename): + input_files += [path_join(root, f) for f in sorted(files) + if f.endswith(".py")] + elif isfile(filename): + input_files += [filename] + else: + print_error("%s is not a file or directory" % filename) + + return input_files + + +def check_files(filenames): + r"""Return list of docstring style errors found in files. + + Example + ------- + >>> import pep257 + >>> pep257.check_files(['one.py', 'two.py']) + ['one.py:23:1 PEP257 Use u\"\"\" for Unicode docstrings.'] + + """ + errors = [] + for filename in find_input_files(filenames): + errors.extend(check_source(open(filename).read(), filename)) + return [str(e) for e in errors] + + +def parse_options(): + parser = OptionParser() + parser.add_option('-e', '--explain', action='store_true', + help='show explanation of each error') + parser.add_option('-r', '--range', action='store_true', + help='show error start..end positions') + parser.add_option('-q', '--quote', action='store_true', + help='quote erroneous lines') + return parser.parse_args() + + +def print_error(message): + sys.stderr.write(message) + sys.stderr.write('\n') + sys.stderr.flush() + + +def main(options, arguments): + print('=' * 80) + print('Note: checks are relaxed for scripts (with #!) compared to modules') + Error.explain = options.explain + Error.range = options.range + Error.quote = options.quote + errors = [] + + for filename in find_input_files(arguments): + try: + f = open(filename) + except IOError: + print_error("Error opening file %s" % filename) + else: + try: + errors.extend(check_source(f.read(), filename)) + except IOError: + print_error("Error reading file %s" % filename) + except tk.TokenError: + print_error("Error parsing file %s" % filename) + finally: + f.close() + for error in sorted(errors): + print_error(str(error)) + + +# +# Check functions +# + + +def check_modules_have_docstrings(module_docstring, context, is_script): + """All modules should have docstrings. + + All modules should normally have docstrings. + + """ + if not module_docstring: # or not eval(module_docstring).strip(): + return 0, min(79, len(context)) + if not eval(module_docstring).strip(): + return True + + +def check_def_has_docstring(def_docstring, context, is_script): + """Exported definitions should have docstrings. + + ...all functions and classes exported by a module should also have + docstrings. Public methods (including the __init__ constructor) + should also have docstrings. + + """ + if is_script: + return # assume nothing is exported + def_name = context.split()[1] + if def_name.startswith('_') and not def_name.endswith('__'): + return # private, not exported + if not def_docstring: + return 0, len(context.split('\n')[0]) + if not eval(def_docstring).strip(): + return True + + +def check_class_has_docstring(class_docstring, context, is_script): + """Exported classes should have docstrings. + + ...all functions and classes exported by a module should also have + docstrings. + + """ + if is_script: + return # assume nothing is exported + class_name = context.split()[1] + if class_name.startswith('_'): + return # not exported + if not class_docstring: + return 0, len(context.split('\n')[0]) + if not eval(class_docstring).strip(): + return True + + +def check_triple_double_quotes(docstring, context, is_script): + r"""Use \"\"\"triple double quotes\"\"\". + + For consistency, always use \"\"\"triple double quotes\"\"\" around + docstrings. Use r\"\"\"raw triple double quotes\"\"\" if you use any + backslashes in your docstrings. For Unicode docstrings, use + u\"\"\"Unicode triple-quoted strings\"\"\". + + """ + if docstring and not (docstring.startswith('"""') or + docstring.startswith('r"""') or + docstring.startswith('u"""')): + return True + + +def check_backslashes(docstring, context, is_script): + r"""Use r\"\"\" if any backslashes in your docstrings. + + Use r\"\"\"raw triple double quotes\"\"\" if you use any backslashes + (\\) in your docstrings. + + """ + if docstring and "\\" in docstring and not docstring.startswith('r"""'): + return True + + +def check_unicode_docstring(docstring, context, is_script): + r"""Use u\"\"\" for Unicode docstrings. + + For Unicode docstrings, use u\"\"\"Unicode triple-quoted stringsr\"\"\". + + """ + if (docstring and not all(isascii(char) for char in docstring) and + not docstring.startswith('u"""')): + return True + + +def check_one_liners(docstring, context, is_script): + """One-liner docstrings should fit on one line with quotes. + + The closing quotes are on the same line as the opening quotes. + This looks better for one-liners. + + """ + if not docstring: + return + lines = docstring.split('\n') + if len(lines) > 1: + non_empty = [l for l in lines if any([c.isalpha() for c in l])] + if len(non_empty) == 1: + return True + + +def check_no_blank_before(def_docstring, context, is_script): + """No blank line before docstring in definitions. + + There's no blank line either before or after the docstring. + + """ + if not def_docstring: + return + before = remove_comments(context.split(def_docstring)[0]) + if before.split(':')[-1].count('\n') > 1: + return True + + +def check_ends_with_period(docstring, context, is_script): + """First line should end with a period. + + The [first line of a] docstring is a phrase ending in a period. + + """ + if docstring and not eval(docstring).split('\n')[0].strip().endswith('.'): + return True + + +def check_imperative_mood(def_docstring, context, is_script): + """First line should be in imperative mood ('Do', not 'Does'). + + [Docstring] prescribes the function or method's effect as a command: + ("Do this", "Return that"), not as a description; e.g. don't write + "Returns the pathname ...". + + """ + if def_docstring and eval(def_docstring).strip(): + first_word = eval(def_docstring).strip().split()[0] + if first_word.endswith('s') and not first_word.endswith('ss'): + return True + + +def check_no_signature(def_docstring, context, is_script): + """First line should not be function's or method's "signature". + + The one-line docstring should NOT be a "signature" reiterating + the function/method parameters (which can be obtained by introspection). + + """ + if not def_docstring: + return + def_name = context.split(def_docstring)[0].split()[1].split('(')[0] + first_line = eval(def_docstring).split('\n')[0] + if def_name + '(' in first_line.replace(' ', ''): + return True + + +def check_return_type(def_docstring, context, is_script): + """Return value type should be mentioned. + + However, the nature of the return value cannot be determined by + introspection, so it should be mentioned. + + """ + if (not def_docstring) or is_script: + return + if 'return' not in def_docstring.lower(): + tokens = list(tk.generate_tokens(StringIO(context).readline)) + after_return = [tokens[i + 1][0] for i, token in enumerate(tokens) + if token[1] == 'return'] + # not very precise (tk.OP ';' is not taken into account) + if set(after_return) - set([tk.COMMENT, tk.NL, tk.NEWLINE]) != set([]): + return True + + +def check_blank_after_summary(docstring, context, is_script): + """Blank line missing after one-line summary. + + Multi-line docstrings consist of a summary line just like a one-line + docstring, followed by a blank line, followed by a more elaborate + description. The summary line may be used by automatic indexing tools; + it is important that it fits on one line and is separated from the + rest of the docstring by a blank line. + + """ + if not docstring: + return + lines = eval(docstring).split('\n') + if len(lines) > 1 and lines[1].strip() != '': + return True + + +def check_indent(docstring, context, is_script): + """The entire docstring should be indented same as code. + + The entire docstring is indented the same as the quotes at its + first line. + + """ + if (not docstring) or len(eval(docstring).split('\n')) == 1: + return + non_empty_lines = [line for line in eval(docstring).split('\n')[1:] + if line.strip()] + if not non_empty_lines: + return + indent = min([len(l) - len(l.lstrip()) for l in non_empty_lines]) + if indent != len(context.split(docstring)[0].split('\n')[-1]): + return True + + +def check_blank_before_after_class(class_docstring, context, is_script): + """Class docstring should have 1 blank line around them. + + Insert a blank line before and after all docstrings (one-line or + multi-line) that document a class -- generally speaking, the class's + methods are separated from each other by a single blank line, and the + docstring needs to be offset from the first method by a blank line; + for symmetry, put a blank line between the class header and the + docstring. + + """ + if not class_docstring: + return + before, after = context.split(class_docstring) + before_blanks = [not line.strip() for line in before.split('\n')] + after_blanks = [not line.strip() for line in after.split('\n')] + if before_blanks[-3:] != [False, True, True]: + return True + if not all(after_blanks) and after_blanks[:3] != [True, True, False]: + return True + + +def check_blank_after_last_paragraph(docstring, context, is_script): + """Multiline docstring should end with 1 blank line. + + The BDFL recommends inserting a blank line between the last + paragraph in a multi-line docstring and its closing quotes, + placing the closing quotes on a line by themselves. + + """ + if (not docstring) or len(eval(docstring).split('\n')) == 1: + return + blanks = [not line.strip() for line in eval(docstring).split('\n')] + if blanks[-3:] != [False, True, True]: + return True + + +if __name__ == '__main__': + try: + main(*parse_options()) + except KeyboardInterrupt: + pass diff --git a/pylibs/pylama/tasks.py b/pylibs/pylama/tasks.py new file mode 100644 index 00000000..3d301c04 --- /dev/null +++ b/pylibs/pylama/tasks.py @@ -0,0 +1,105 @@ +""" Async code checking. +""" +import logging +import threading +from os import path as op +try: + import Queue +except ImportError: + import queue as Queue + +from .core import run + + +try: + import multiprocessing + + CPU_COUNT = multiprocessing.cpu_count() + +except (ImportError, NotImplementedError): + CPU_COUNT = 1 + +LOGGER = logging.getLogger('pylama') + + +class Worker(threading.Thread): + + """ Get tasks from queue and run. """ + + def __init__(self, path_queue, result_queue): + threading.Thread.__init__(self) + self.path_queue = path_queue + self.result_queue = result_queue + + def run(self): + """ Run tasks from queue. """ + while True: + path, params = self.path_queue.get() + errors = check_path(path, **params) + self.result_queue.put(errors) + self.path_queue.task_done() + + +def async_check_files(paths, async=False, linters=None, **params): + """ Check paths. + + :return list: list of errors + + """ + + errors = [] + + # Disable async if pylint enabled + async = async and not 'pylint' in linters + params['linters'] = linters + + if not async: + for path in paths: + errors += check_path(path, **params) + return errors + + LOGGER.info('Async code checking is enabled.') + path_queue = Queue.Queue() + result_queue = Queue.Queue() + + for _ in range(CPU_COUNT): + worker = Worker(path_queue, result_queue) + worker.setDaemon(True) + worker.start() + + for path in paths: + path_queue.put((path, params)) + + path_queue.join() + + while True: + try: + errors += result_queue.get(False) + except Queue.Empty: + break + + return errors + + +def check_path(path, rootpath='.', ignore=None, select=None, linters=None, + complexity=None, params=None): + """ Check path. + + :return list: list of errors + + """ + + LOGGER.info("Parse file: %s", path) + params = params or dict() + + errors = [] + for error in run(path, ignore=ignore, select=select, linters=linters, + complexity=complexity, config=params.get(path)): + try: + error['rel'] = op.relpath( + error['filename'], op.dirname(rootpath)) + error['col'] = error.get('col', 1) + errors.append(error) + except KeyError: + continue + return errors diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py index 78460ce4..2a135cf8 100644 --- a/pylibs/pylama/utils.py +++ b/pylibs/pylama/utils.py @@ -1,31 +1,33 @@ +""" Interfaces for code checking. +""" from __future__ import absolute_import, with_statement import _ast from os import path as op, environ -from .mccabe import get_code_complexity from .pep8 import BaseReport, StyleGuide -from .pyflakes import checker -__all__ = 'pep8', 'mccabe', 'pyflakes', 'pylint' +__all__ = 'pep8', 'pep257', 'mccabe', 'pyflakes', 'pylint' PYLINT_RC = op.abspath(op.join(op.dirname(__file__), 'pylint.rc')) -class PEP8Report(BaseReport): +class _PEP8Report(BaseReport): def __init__(self, *args, **kwargs): - super(PEP8Report, self).__init__(*args, **kwargs) + super(_PEP8Report, self).__init__(*args, **kwargs) self.errors = [] def init_file(self, filename, lines, expected, line_offset): - super(PEP8Report, self).init_file( + """ Prepare storage for errors. """ + super(_PEP8Report, self).init_file( filename, lines, expected, line_offset) self.errors = [] def error(self, line_number, offset, text, check): - code = super(PEP8Report, self).error( + """ Save errors. """ + code = super(_PEP8Report, self).error( line_number, offset, text, check) self.errors.append(dict( @@ -36,25 +38,42 @@ def error(self, line_number, offset, text, check): )) def get_file_results(self): - return self.errors + """ Get errors. + + :return list: List of errors. -P8Style = StyleGuide(reporter=PEP8Report) + """ + return self.errors def pep8(path, **meta): - " PEP8 code checking. " + """ PEP8 code checking. + :return list: List of errors. + + """ + P8Style = StyleGuide(reporter=_PEP8Report) return P8Style.input_file(path) def mccabe(path, code=None, complexity=8, **meta): - " MCCabe code checking. " + """ MCCabe code checking. + + :return list: List of errors. + + """ + from .mccabe import get_code_complexity return get_code_complexity(code, complexity, filename=path) or [] def pyflakes(path, code=None, **meta): - " PyFlakes code checking. " + """ Pyflake code checking. + + :return list: List of errors. + + """ + from .pyflakes import checker errors = [] tree = compile(code, path, "exec", _ast.PyCF_ONLY_AST) @@ -69,6 +88,11 @@ def pyflakes(path, code=None, **meta): def pylint(path, **meta): + """ Pylint code checking. + + :return list: List of errors. + + """ from sys import version_info if version_info > (3, 0): import logging @@ -77,8 +101,8 @@ def pylint(path, **meta): from .pylint.lint import Run from .pylint.reporters import BaseReporter + from .pylint.logilab.astng import MANAGER - from .pylint.logilab.astng.builder import MANAGER MANAGER.astng_cache.clear() class Reporter(BaseReporter): @@ -109,4 +133,25 @@ def add_message(self, msg_id, location, msg): [path] + attrs, reporter=Reporter(), exit=False) return runner.linter.reporter.errors + +def pep257(path, **meta): + """ PEP257 code checking. + + :return list: List of errors. + + """ + f = open(path) + from .pep257 import check_source + + errors = [] + for er in check_source(f.read(), path): + errors.append(dict( + lnum=er.line, + col=er.char, + text='C0110 %s' % er.explanation.split('\n')[0].strip(), + type='W', + )) + return errors + + # pymode:lint_ignore=W0231 diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index 51930bce..0f315c8a 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -3,7 +3,8 @@ import locale import json -from pylama.main import run, prepare_params +from pylama.core import run +from pylama.main import prepare_params from pylama.inirama import Namespace from os import path as op From d17f039cbfdd231979c37cfb9f1b407cfaf7dec9 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Fri, 7 Jun 2013 11:01:58 +0800 Subject: [PATCH 211/513] Update docs --- Changelog.rst | 1 + README.rst | 1 + doc/pymode.txt | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index f6fdb811..a212925e 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -3,6 +3,7 @@ Changelog * Added `g:pymode_rope_autocomplete_map` option; * Removed `g:pymode_rope_map_space` option; +* Added PEP257 checker ## 2013-05-15 0.6.18 -------------------- diff --git a/README.rst b/README.rst index deb602c5..a8f67c24 100644 --- a/README.rst +++ b/README.rst @@ -144,6 +144,7 @@ Default values: :: " Switch pylint, pyflakes, pep8, mccabe code-checkers " Can have multiply values "pep8,pyflakes,mcccabe" + " Choices are pyflakes, pep8, mccabe, pylint, pep257 let g:pymode_lint_checker = "pyflakes,pep8,mccabe" " Skip errors and warnings diff --git a/doc/pymode.txt b/doc/pymode.txt index af8917b8..01c150f6 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -223,7 +223,7 @@ If this option is set to 0 then the pylint script is disabled. ------------------------------------------------------------------------------ *'pymode_lint_checker'* -Values: "pylint", "pyflakes", "pep8", "mccabe" +Values: "pylint", "pyflakes", "pep8", "mccabe", "pep257" You can set many checkers. E.g. "pyflakes,pep8,mccabe" ~ Default: "pyflakes,pep8,mccabe". From 70d8eab3bee42fbde292b4fa2813e69d9a5e45fe Mon Sep 17 00:00:00 2001 From: Jan Gosmann Date: Mon, 10 Jun 2013 13:54:52 +0200 Subject: [PATCH 212/513] Fix slow saving by not clearing cache every time. --- pylibs/rope/base/pycore.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pylibs/rope/base/pycore.py b/pylibs/rope/base/pycore.py index 32056a0f..164f669c 100644 --- a/pylibs/rope/base/pycore.py +++ b/pylibs/rope/base/pycore.py @@ -258,7 +258,6 @@ def analyze_module(self, resource, should_analyze=lambda py: True, if followed_calls is None: followed_calls = self.project.prefs.get('soa_followed_calls', 0) pymodule = self.resource_to_pyobject(resource) - self.module_cache.forget_all_data() rope.base.oi.soa.analyze_module( self, pymodule, should_analyze, search_subscopes, followed_calls) @@ -306,7 +305,7 @@ def __init__(self, pycore): def _invalidate_resource(self, resource): if resource in self.module_map: - self.forget_all_data() + self.module_map[resource]._forget_concluded_data() self.observer.remove_resource(resource) del self.module_map[resource] From 328111ebfba91826ca21fb78ccff3523dfd37853 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Sat, 15 Jun 2013 16:39:43 +0100 Subject: [PATCH 213/513] Remove leftovers of auto session support. Should have been removed on a previous commit. --- autoload/pymode/troubleshooting.vim | 1 - ftplugin/python/init-pymode.vim | 8 -------- 2 files changed, 9 deletions(-) diff --git a/autoload/pymode/troubleshooting.vim b/autoload/pymode/troubleshooting.vim index dca1ea7d..1c883a6e 100644 --- a/autoload/pymode/troubleshooting.vim +++ b/autoload/pymode/troubleshooting.vim @@ -71,7 +71,6 @@ fun! pymode#troubleshooting#Test() "{{{ call append('$', 'let pymode_rope_autocomplete_map = ' . string(g:pymode_rope_autocomplete_map)) call append('$', 'let pymode_rope_auto_project = ' . string(g:pymode_rope_auto_project)) call append('$', 'let pymode_rope_auto_project_open = ' . string(g:pymode_rope_auto_project_open)) - call append('$', 'let pymode_rope_auto_session_manage = ' . string(g:pymode_rope_auto_session_manage)) end call append('$', 'let pymode_folding = ' . string(g:pymode_folding)) call append('$', 'let pymode_breakpoint = ' . string(g:pymode_breakpoint)) diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index 59e287ea..2998e14e 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -201,9 +201,6 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope " `.ropeproject` subdirectory. call pymode#Default("g:pymode_rope_auto_project_open", 1) - " OPTION: g:pymode_rope_auto_session_manage -- bool - call pymode#Default("g:pymode_rope_auto_session_manage", 0) - " OPTION: g:pymode_rope_enable_autoimport -- bool. Enable autoimport call pymode#Default("g:pymode_rope_enable_autoimport", 1) @@ -313,11 +310,6 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope call RopeOpenExistingProject() endif - if !pymode#Default("g:pymode_rope_auto_session_manage", 0) || g:pymode_rope_auto_session_manage - autocmd VimLeave * call RopeSaveSession() - autocmd VimEnter * call RopeRestoreSession() - endif - endif " }}} From 740bd743ec96cd3d7866effddbe8eb61a28df12c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 20 Jun 2013 23:09:35 +0800 Subject: [PATCH 214/513] Update pylama --- pylibs/pylama/__init__.py | 2 +- .../{pylint/logilab => checkers}/__init__.py | 0 pylibs/pylama/{ => checkers}/mccabe.py | 0 pylibs/pylama/{ => checkers}/pep257.py | 0 pylibs/pylama/{ => checkers}/pep8.py | 0 .../{ => checkers}/pyflakes/__init__.py | 0 .../pylama/{ => checkers}/pyflakes/checker.py | 0 .../{ => checkers}/pyflakes/messages.py | 0 .../pylama/{ => checkers}/pylint/__init__.py | 0 .../{ => checkers}/pylint/__pkginfo__.py | 0 .../pylint/checkers/__init__.py | 0 .../{ => checkers}/pylint/checkers/base.py | 0 .../{ => checkers}/pylint/checkers/classes.py | 0 .../pylint/checkers/design_analysis.py | 0 .../pylint/checkers/exceptions.py | 0 .../{ => checkers}/pylint/checkers/format.py | 0 .../{ => checkers}/pylint/checkers/imports.py | 0 .../{ => checkers}/pylint/checkers/logging.py | 0 .../{ => checkers}/pylint/checkers/misc.py | 0 .../pylint/checkers/newstyle.py | 0 .../pylint/checkers/raw_metrics.py | 0 .../{ => checkers}/pylint/checkers/similar.py | 0 .../{ => checkers}/pylint/checkers/strings.py | 0 .../pylint/checkers/typecheck.py | 0 .../{ => checkers}/pylint/checkers/utils.py | 0 .../pylint/checkers/variables.py | 0 pylibs/pylama/{ => checkers}/pylint/config.py | 0 .../{ => checkers}/pylint/interfaces.py | 0 pylibs/pylama/{ => checkers}/pylint/lint.py | 0 .../pylint/logilab}/__init__.py | 0 .../pylint/logilab/astng/__init__.py | 0 .../pylint/logilab/astng/__pkginfo__.py | 0 .../pylint/logilab/astng/as_string.py | 0 .../pylint/logilab/astng/bases.py | 0 .../pylint/logilab/astng/brain/__init__.py | 0 .../logilab/astng/brain/py2mechanize.py | 0 .../pylint/logilab/astng/brain/py2qt4.py | 0 .../pylint/logilab/astng/brain/py2stdlib.py | 0 .../pylint/logilab/astng/builder.py | 0 .../pylint/logilab/astng/exceptions.py | 0 .../pylint/logilab/astng/inference.py | 0 .../pylint/logilab/astng/manager.py | 0 .../pylint/logilab/astng/mixins.py | 0 .../pylint/logilab/astng/node_classes.py | 0 .../pylint/logilab/astng/nodes.py | 0 .../pylint/logilab/astng/protocols.py | 0 .../pylint/logilab/astng/raw_building.py | 0 .../pylint/logilab/astng/rebuilder.py | 0 .../pylint/logilab/astng/scoped_nodes.py | 0 .../pylint/logilab/astng/utils.py | 0 .../pylint/logilab/common/__init__.py | 0 .../pylint/logilab/common/__pkginfo__.py | 0 .../pylint/logilab/common/changelog.py | 0 .../pylint/logilab/common/compat.py | 0 .../pylint/logilab/common/configuration.py | 0 .../pylint/logilab/common/decorators.py | 0 .../pylint/logilab/common/deprecation.py | 0 .../pylint/logilab/common/graph.py | 0 .../pylint/logilab/common/interface.py | 0 .../pylint/logilab/common/modutils.py | 0 .../pylint/logilab/common/optik_ext.py | 0 .../pylint/logilab/common/textutils.py | 0 .../pylint/logilab/common/tree.py | 0 .../logilab/common/ureports/__init__.py | 0 .../logilab/common/ureports/docbook_writer.py | 0 .../logilab/common/ureports/html_writer.py | 0 .../pylint/logilab/common/ureports/nodes.py | 0 .../logilab/common/ureports/text_writer.py | 0 .../pylint/logilab/common/visitor.py | 0 .../pylint/reporters/__init__.py | 0 .../pylint/reporters/guireporter.py | 0 .../{ => checkers}/pylint/reporters/html.py | 0 .../{ => checkers}/pylint/reporters/text.py | 0 pylibs/pylama/{ => checkers}/pylint/utils.py | 0 pylibs/pylama/core.py | 7 +- pylibs/pylama/hook.py | 12 ++- pylibs/pylama/inirama.py | 100 ++++++++++++------ pylibs/pylama/main.py | 16 ++- pylibs/pylama/tasks.py | 10 +- pylibs/pylama/utils.py | 14 +-- pylibs/pymode/lint.py | 22 +++- 81 files changed, 122 insertions(+), 61 deletions(-) rename pylibs/pylama/{pylint/logilab => checkers}/__init__.py (100%) rename pylibs/pylama/{ => checkers}/mccabe.py (100%) rename pylibs/pylama/{ => checkers}/pep257.py (100%) rename pylibs/pylama/{ => checkers}/pep8.py (100%) rename pylibs/pylama/{ => checkers}/pyflakes/__init__.py (100%) rename pylibs/pylama/{ => checkers}/pyflakes/checker.py (100%) rename pylibs/pylama/{ => checkers}/pyflakes/messages.py (100%) rename pylibs/pylama/{ => checkers}/pylint/__init__.py (100%) rename pylibs/pylama/{ => checkers}/pylint/__pkginfo__.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/__init__.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/base.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/classes.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/design_analysis.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/exceptions.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/format.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/imports.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/logging.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/misc.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/newstyle.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/raw_metrics.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/similar.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/strings.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/typecheck.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/utils.py (100%) rename pylibs/pylama/{ => checkers}/pylint/checkers/variables.py (100%) rename pylibs/pylama/{ => checkers}/pylint/config.py (100%) rename pylibs/pylama/{ => checkers}/pylint/interfaces.py (100%) rename pylibs/pylama/{ => checkers}/pylint/lint.py (100%) rename pylibs/pylama/{pylint/logilab/astng/brain => checkers/pylint/logilab}/__init__.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/__init__.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/__pkginfo__.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/as_string.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/bases.py (100%) create mode 100644 pylibs/pylama/checkers/pylint/logilab/astng/brain/__init__.py rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/brain/py2mechanize.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/brain/py2qt4.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/brain/py2stdlib.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/builder.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/exceptions.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/inference.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/manager.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/mixins.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/node_classes.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/nodes.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/protocols.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/raw_building.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/rebuilder.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/scoped_nodes.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/astng/utils.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/__init__.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/__pkginfo__.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/changelog.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/compat.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/configuration.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/decorators.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/deprecation.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/graph.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/interface.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/modutils.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/optik_ext.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/textutils.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/tree.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/ureports/__init__.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/ureports/docbook_writer.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/ureports/html_writer.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/ureports/nodes.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/ureports/text_writer.py (100%) rename pylibs/pylama/{ => checkers}/pylint/logilab/common/visitor.py (100%) rename pylibs/pylama/{ => checkers}/pylint/reporters/__init__.py (100%) rename pylibs/pylama/{ => checkers}/pylint/reporters/guireporter.py (100%) rename pylibs/pylama/{ => checkers}/pylint/reporters/html.py (100%) rename pylibs/pylama/{ => checkers}/pylint/reporters/text.py (100%) rename pylibs/pylama/{ => checkers}/pylint/utils.py (100%) diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index 6d528b30..4cc579fc 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -5,7 +5,7 @@ :license: BSD, see LICENSE for more details. """ -version_info = 1, 0, 4 +version_info = 1, 1, 0 __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/pylint/logilab/__init__.py b/pylibs/pylama/checkers/__init__.py similarity index 100% rename from pylibs/pylama/pylint/logilab/__init__.py rename to pylibs/pylama/checkers/__init__.py diff --git a/pylibs/pylama/mccabe.py b/pylibs/pylama/checkers/mccabe.py similarity index 100% rename from pylibs/pylama/mccabe.py rename to pylibs/pylama/checkers/mccabe.py diff --git a/pylibs/pylama/pep257.py b/pylibs/pylama/checkers/pep257.py similarity index 100% rename from pylibs/pylama/pep257.py rename to pylibs/pylama/checkers/pep257.py diff --git a/pylibs/pylama/pep8.py b/pylibs/pylama/checkers/pep8.py similarity index 100% rename from pylibs/pylama/pep8.py rename to pylibs/pylama/checkers/pep8.py diff --git a/pylibs/pylama/pyflakes/__init__.py b/pylibs/pylama/checkers/pyflakes/__init__.py similarity index 100% rename from pylibs/pylama/pyflakes/__init__.py rename to pylibs/pylama/checkers/pyflakes/__init__.py diff --git a/pylibs/pylama/pyflakes/checker.py b/pylibs/pylama/checkers/pyflakes/checker.py similarity index 100% rename from pylibs/pylama/pyflakes/checker.py rename to pylibs/pylama/checkers/pyflakes/checker.py diff --git a/pylibs/pylama/pyflakes/messages.py b/pylibs/pylama/checkers/pyflakes/messages.py similarity index 100% rename from pylibs/pylama/pyflakes/messages.py rename to pylibs/pylama/checkers/pyflakes/messages.py diff --git a/pylibs/pylama/pylint/__init__.py b/pylibs/pylama/checkers/pylint/__init__.py similarity index 100% rename from pylibs/pylama/pylint/__init__.py rename to pylibs/pylama/checkers/pylint/__init__.py diff --git a/pylibs/pylama/pylint/__pkginfo__.py b/pylibs/pylama/checkers/pylint/__pkginfo__.py similarity index 100% rename from pylibs/pylama/pylint/__pkginfo__.py rename to pylibs/pylama/checkers/pylint/__pkginfo__.py diff --git a/pylibs/pylama/pylint/checkers/__init__.py b/pylibs/pylama/checkers/pylint/checkers/__init__.py similarity index 100% rename from pylibs/pylama/pylint/checkers/__init__.py rename to pylibs/pylama/checkers/pylint/checkers/__init__.py diff --git a/pylibs/pylama/pylint/checkers/base.py b/pylibs/pylama/checkers/pylint/checkers/base.py similarity index 100% rename from pylibs/pylama/pylint/checkers/base.py rename to pylibs/pylama/checkers/pylint/checkers/base.py diff --git a/pylibs/pylama/pylint/checkers/classes.py b/pylibs/pylama/checkers/pylint/checkers/classes.py similarity index 100% rename from pylibs/pylama/pylint/checkers/classes.py rename to pylibs/pylama/checkers/pylint/checkers/classes.py diff --git a/pylibs/pylama/pylint/checkers/design_analysis.py b/pylibs/pylama/checkers/pylint/checkers/design_analysis.py similarity index 100% rename from pylibs/pylama/pylint/checkers/design_analysis.py rename to pylibs/pylama/checkers/pylint/checkers/design_analysis.py diff --git a/pylibs/pylama/pylint/checkers/exceptions.py b/pylibs/pylama/checkers/pylint/checkers/exceptions.py similarity index 100% rename from pylibs/pylama/pylint/checkers/exceptions.py rename to pylibs/pylama/checkers/pylint/checkers/exceptions.py diff --git a/pylibs/pylama/pylint/checkers/format.py b/pylibs/pylama/checkers/pylint/checkers/format.py similarity index 100% rename from pylibs/pylama/pylint/checkers/format.py rename to pylibs/pylama/checkers/pylint/checkers/format.py diff --git a/pylibs/pylama/pylint/checkers/imports.py b/pylibs/pylama/checkers/pylint/checkers/imports.py similarity index 100% rename from pylibs/pylama/pylint/checkers/imports.py rename to pylibs/pylama/checkers/pylint/checkers/imports.py diff --git a/pylibs/pylama/pylint/checkers/logging.py b/pylibs/pylama/checkers/pylint/checkers/logging.py similarity index 100% rename from pylibs/pylama/pylint/checkers/logging.py rename to pylibs/pylama/checkers/pylint/checkers/logging.py diff --git a/pylibs/pylama/pylint/checkers/misc.py b/pylibs/pylama/checkers/pylint/checkers/misc.py similarity index 100% rename from pylibs/pylama/pylint/checkers/misc.py rename to pylibs/pylama/checkers/pylint/checkers/misc.py diff --git a/pylibs/pylama/pylint/checkers/newstyle.py b/pylibs/pylama/checkers/pylint/checkers/newstyle.py similarity index 100% rename from pylibs/pylama/pylint/checkers/newstyle.py rename to pylibs/pylama/checkers/pylint/checkers/newstyle.py diff --git a/pylibs/pylama/pylint/checkers/raw_metrics.py b/pylibs/pylama/checkers/pylint/checkers/raw_metrics.py similarity index 100% rename from pylibs/pylama/pylint/checkers/raw_metrics.py rename to pylibs/pylama/checkers/pylint/checkers/raw_metrics.py diff --git a/pylibs/pylama/pylint/checkers/similar.py b/pylibs/pylama/checkers/pylint/checkers/similar.py similarity index 100% rename from pylibs/pylama/pylint/checkers/similar.py rename to pylibs/pylama/checkers/pylint/checkers/similar.py diff --git a/pylibs/pylama/pylint/checkers/strings.py b/pylibs/pylama/checkers/pylint/checkers/strings.py similarity index 100% rename from pylibs/pylama/pylint/checkers/strings.py rename to pylibs/pylama/checkers/pylint/checkers/strings.py diff --git a/pylibs/pylama/pylint/checkers/typecheck.py b/pylibs/pylama/checkers/pylint/checkers/typecheck.py similarity index 100% rename from pylibs/pylama/pylint/checkers/typecheck.py rename to pylibs/pylama/checkers/pylint/checkers/typecheck.py diff --git a/pylibs/pylama/pylint/checkers/utils.py b/pylibs/pylama/checkers/pylint/checkers/utils.py similarity index 100% rename from pylibs/pylama/pylint/checkers/utils.py rename to pylibs/pylama/checkers/pylint/checkers/utils.py diff --git a/pylibs/pylama/pylint/checkers/variables.py b/pylibs/pylama/checkers/pylint/checkers/variables.py similarity index 100% rename from pylibs/pylama/pylint/checkers/variables.py rename to pylibs/pylama/checkers/pylint/checkers/variables.py diff --git a/pylibs/pylama/pylint/config.py b/pylibs/pylama/checkers/pylint/config.py similarity index 100% rename from pylibs/pylama/pylint/config.py rename to pylibs/pylama/checkers/pylint/config.py diff --git a/pylibs/pylama/pylint/interfaces.py b/pylibs/pylama/checkers/pylint/interfaces.py similarity index 100% rename from pylibs/pylama/pylint/interfaces.py rename to pylibs/pylama/checkers/pylint/interfaces.py diff --git a/pylibs/pylama/pylint/lint.py b/pylibs/pylama/checkers/pylint/lint.py similarity index 100% rename from pylibs/pylama/pylint/lint.py rename to pylibs/pylama/checkers/pylint/lint.py diff --git a/pylibs/pylama/pylint/logilab/astng/brain/__init__.py b/pylibs/pylama/checkers/pylint/logilab/__init__.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/brain/__init__.py rename to pylibs/pylama/checkers/pylint/logilab/__init__.py diff --git a/pylibs/pylama/pylint/logilab/astng/__init__.py b/pylibs/pylama/checkers/pylint/logilab/astng/__init__.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/__init__.py rename to pylibs/pylama/checkers/pylint/logilab/astng/__init__.py diff --git a/pylibs/pylama/pylint/logilab/astng/__pkginfo__.py b/pylibs/pylama/checkers/pylint/logilab/astng/__pkginfo__.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/__pkginfo__.py rename to pylibs/pylama/checkers/pylint/logilab/astng/__pkginfo__.py diff --git a/pylibs/pylama/pylint/logilab/astng/as_string.py b/pylibs/pylama/checkers/pylint/logilab/astng/as_string.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/as_string.py rename to pylibs/pylama/checkers/pylint/logilab/astng/as_string.py diff --git a/pylibs/pylama/pylint/logilab/astng/bases.py b/pylibs/pylama/checkers/pylint/logilab/astng/bases.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/bases.py rename to pylibs/pylama/checkers/pylint/logilab/astng/bases.py diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/brain/__init__.py b/pylibs/pylama/checkers/pylint/logilab/astng/brain/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pylibs/pylama/pylint/logilab/astng/brain/py2mechanize.py b/pylibs/pylama/checkers/pylint/logilab/astng/brain/py2mechanize.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/brain/py2mechanize.py rename to pylibs/pylama/checkers/pylint/logilab/astng/brain/py2mechanize.py diff --git a/pylibs/pylama/pylint/logilab/astng/brain/py2qt4.py b/pylibs/pylama/checkers/pylint/logilab/astng/brain/py2qt4.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/brain/py2qt4.py rename to pylibs/pylama/checkers/pylint/logilab/astng/brain/py2qt4.py diff --git a/pylibs/pylama/pylint/logilab/astng/brain/py2stdlib.py b/pylibs/pylama/checkers/pylint/logilab/astng/brain/py2stdlib.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/brain/py2stdlib.py rename to pylibs/pylama/checkers/pylint/logilab/astng/brain/py2stdlib.py diff --git a/pylibs/pylama/pylint/logilab/astng/builder.py b/pylibs/pylama/checkers/pylint/logilab/astng/builder.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/builder.py rename to pylibs/pylama/checkers/pylint/logilab/astng/builder.py diff --git a/pylibs/pylama/pylint/logilab/astng/exceptions.py b/pylibs/pylama/checkers/pylint/logilab/astng/exceptions.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/exceptions.py rename to pylibs/pylama/checkers/pylint/logilab/astng/exceptions.py diff --git a/pylibs/pylama/pylint/logilab/astng/inference.py b/pylibs/pylama/checkers/pylint/logilab/astng/inference.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/inference.py rename to pylibs/pylama/checkers/pylint/logilab/astng/inference.py diff --git a/pylibs/pylama/pylint/logilab/astng/manager.py b/pylibs/pylama/checkers/pylint/logilab/astng/manager.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/manager.py rename to pylibs/pylama/checkers/pylint/logilab/astng/manager.py diff --git a/pylibs/pylama/pylint/logilab/astng/mixins.py b/pylibs/pylama/checkers/pylint/logilab/astng/mixins.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/mixins.py rename to pylibs/pylama/checkers/pylint/logilab/astng/mixins.py diff --git a/pylibs/pylama/pylint/logilab/astng/node_classes.py b/pylibs/pylama/checkers/pylint/logilab/astng/node_classes.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/node_classes.py rename to pylibs/pylama/checkers/pylint/logilab/astng/node_classes.py diff --git a/pylibs/pylama/pylint/logilab/astng/nodes.py b/pylibs/pylama/checkers/pylint/logilab/astng/nodes.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/nodes.py rename to pylibs/pylama/checkers/pylint/logilab/astng/nodes.py diff --git a/pylibs/pylama/pylint/logilab/astng/protocols.py b/pylibs/pylama/checkers/pylint/logilab/astng/protocols.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/protocols.py rename to pylibs/pylama/checkers/pylint/logilab/astng/protocols.py diff --git a/pylibs/pylama/pylint/logilab/astng/raw_building.py b/pylibs/pylama/checkers/pylint/logilab/astng/raw_building.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/raw_building.py rename to pylibs/pylama/checkers/pylint/logilab/astng/raw_building.py diff --git a/pylibs/pylama/pylint/logilab/astng/rebuilder.py b/pylibs/pylama/checkers/pylint/logilab/astng/rebuilder.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/rebuilder.py rename to pylibs/pylama/checkers/pylint/logilab/astng/rebuilder.py diff --git a/pylibs/pylama/pylint/logilab/astng/scoped_nodes.py b/pylibs/pylama/checkers/pylint/logilab/astng/scoped_nodes.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/scoped_nodes.py rename to pylibs/pylama/checkers/pylint/logilab/astng/scoped_nodes.py diff --git a/pylibs/pylama/pylint/logilab/astng/utils.py b/pylibs/pylama/checkers/pylint/logilab/astng/utils.py similarity index 100% rename from pylibs/pylama/pylint/logilab/astng/utils.py rename to pylibs/pylama/checkers/pylint/logilab/astng/utils.py diff --git a/pylibs/pylama/pylint/logilab/common/__init__.py b/pylibs/pylama/checkers/pylint/logilab/common/__init__.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/__init__.py rename to pylibs/pylama/checkers/pylint/logilab/common/__init__.py diff --git a/pylibs/pylama/pylint/logilab/common/__pkginfo__.py b/pylibs/pylama/checkers/pylint/logilab/common/__pkginfo__.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/__pkginfo__.py rename to pylibs/pylama/checkers/pylint/logilab/common/__pkginfo__.py diff --git a/pylibs/pylama/pylint/logilab/common/changelog.py b/pylibs/pylama/checkers/pylint/logilab/common/changelog.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/changelog.py rename to pylibs/pylama/checkers/pylint/logilab/common/changelog.py diff --git a/pylibs/pylama/pylint/logilab/common/compat.py b/pylibs/pylama/checkers/pylint/logilab/common/compat.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/compat.py rename to pylibs/pylama/checkers/pylint/logilab/common/compat.py diff --git a/pylibs/pylama/pylint/logilab/common/configuration.py b/pylibs/pylama/checkers/pylint/logilab/common/configuration.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/configuration.py rename to pylibs/pylama/checkers/pylint/logilab/common/configuration.py diff --git a/pylibs/pylama/pylint/logilab/common/decorators.py b/pylibs/pylama/checkers/pylint/logilab/common/decorators.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/decorators.py rename to pylibs/pylama/checkers/pylint/logilab/common/decorators.py diff --git a/pylibs/pylama/pylint/logilab/common/deprecation.py b/pylibs/pylama/checkers/pylint/logilab/common/deprecation.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/deprecation.py rename to pylibs/pylama/checkers/pylint/logilab/common/deprecation.py diff --git a/pylibs/pylama/pylint/logilab/common/graph.py b/pylibs/pylama/checkers/pylint/logilab/common/graph.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/graph.py rename to pylibs/pylama/checkers/pylint/logilab/common/graph.py diff --git a/pylibs/pylama/pylint/logilab/common/interface.py b/pylibs/pylama/checkers/pylint/logilab/common/interface.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/interface.py rename to pylibs/pylama/checkers/pylint/logilab/common/interface.py diff --git a/pylibs/pylama/pylint/logilab/common/modutils.py b/pylibs/pylama/checkers/pylint/logilab/common/modutils.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/modutils.py rename to pylibs/pylama/checkers/pylint/logilab/common/modutils.py diff --git a/pylibs/pylama/pylint/logilab/common/optik_ext.py b/pylibs/pylama/checkers/pylint/logilab/common/optik_ext.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/optik_ext.py rename to pylibs/pylama/checkers/pylint/logilab/common/optik_ext.py diff --git a/pylibs/pylama/pylint/logilab/common/textutils.py b/pylibs/pylama/checkers/pylint/logilab/common/textutils.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/textutils.py rename to pylibs/pylama/checkers/pylint/logilab/common/textutils.py diff --git a/pylibs/pylama/pylint/logilab/common/tree.py b/pylibs/pylama/checkers/pylint/logilab/common/tree.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/tree.py rename to pylibs/pylama/checkers/pylint/logilab/common/tree.py diff --git a/pylibs/pylama/pylint/logilab/common/ureports/__init__.py b/pylibs/pylama/checkers/pylint/logilab/common/ureports/__init__.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/ureports/__init__.py rename to pylibs/pylama/checkers/pylint/logilab/common/ureports/__init__.py diff --git a/pylibs/pylama/pylint/logilab/common/ureports/docbook_writer.py b/pylibs/pylama/checkers/pylint/logilab/common/ureports/docbook_writer.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/ureports/docbook_writer.py rename to pylibs/pylama/checkers/pylint/logilab/common/ureports/docbook_writer.py diff --git a/pylibs/pylama/pylint/logilab/common/ureports/html_writer.py b/pylibs/pylama/checkers/pylint/logilab/common/ureports/html_writer.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/ureports/html_writer.py rename to pylibs/pylama/checkers/pylint/logilab/common/ureports/html_writer.py diff --git a/pylibs/pylama/pylint/logilab/common/ureports/nodes.py b/pylibs/pylama/checkers/pylint/logilab/common/ureports/nodes.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/ureports/nodes.py rename to pylibs/pylama/checkers/pylint/logilab/common/ureports/nodes.py diff --git a/pylibs/pylama/pylint/logilab/common/ureports/text_writer.py b/pylibs/pylama/checkers/pylint/logilab/common/ureports/text_writer.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/ureports/text_writer.py rename to pylibs/pylama/checkers/pylint/logilab/common/ureports/text_writer.py diff --git a/pylibs/pylama/pylint/logilab/common/visitor.py b/pylibs/pylama/checkers/pylint/logilab/common/visitor.py similarity index 100% rename from pylibs/pylama/pylint/logilab/common/visitor.py rename to pylibs/pylama/checkers/pylint/logilab/common/visitor.py diff --git a/pylibs/pylama/pylint/reporters/__init__.py b/pylibs/pylama/checkers/pylint/reporters/__init__.py similarity index 100% rename from pylibs/pylama/pylint/reporters/__init__.py rename to pylibs/pylama/checkers/pylint/reporters/__init__.py diff --git a/pylibs/pylama/pylint/reporters/guireporter.py b/pylibs/pylama/checkers/pylint/reporters/guireporter.py similarity index 100% rename from pylibs/pylama/pylint/reporters/guireporter.py rename to pylibs/pylama/checkers/pylint/reporters/guireporter.py diff --git a/pylibs/pylama/pylint/reporters/html.py b/pylibs/pylama/checkers/pylint/reporters/html.py similarity index 100% rename from pylibs/pylama/pylint/reporters/html.py rename to pylibs/pylama/checkers/pylint/reporters/html.py diff --git a/pylibs/pylama/pylint/reporters/text.py b/pylibs/pylama/checkers/pylint/reporters/text.py similarity index 100% rename from pylibs/pylama/pylint/reporters/text.py rename to pylibs/pylama/checkers/pylint/reporters/text.py diff --git a/pylibs/pylama/pylint/utils.py b/pylibs/pylama/checkers/pylint/utils.py similarity index 100% rename from pylibs/pylama/pylint/utils.py rename to pylibs/pylama/checkers/pylint/utils.py diff --git a/pylibs/pylama/core.py b/pylibs/pylama/core.py index 70915075..0349dd87 100644 --- a/pylibs/pylama/core.py +++ b/pylibs/pylama/core.py @@ -1,4 +1,4 @@ -""" Pylama core. +""" Pylama core functionality. Get params and runs a checkers. """ import logging import re @@ -10,7 +10,7 @@ LOGGER = logging.getLogger('pylama') MODERE = re.compile(r'^\s*#\s+(?:pymode\:)?((?:lint[\w_]*=[^:\n\s]+:?)+)', re.I | re.M) -SKIP_PATTERN = '# nolint' +SKIP_PATTERN = '# noqa' STREAM = logging.StreamHandler() LOGGER.addHandler(STREAM) @@ -32,6 +32,7 @@ def run(path, ignore=None, select=None, linters=DEFAULT_LINTERS, config=None, code = f.read() + '\n\n' params = config or __parse_modeline(code) params['skip'] = [False] + for line in code.split('\n'): params['skip'].append(line.endswith(SKIP_PATTERN)) @@ -82,7 +83,7 @@ def run(path, ignore=None, select=None, linters=DEFAULT_LINTERS, config=None, except Exception: import traceback - logging.error(traceback.format_exc()) + logging.debug(traceback.format_exc()) errors = [er for er in errors if __ignore_error(er, select, ignore)] return sorted(errors, key=lambda x: x['lnum']) diff --git a/pylibs/pylama/hook.py b/pylibs/pylama/hook.py index fa50899c..846321db 100644 --- a/pylibs/pylama/hook.py +++ b/pylibs/pylama/hook.py @@ -1,4 +1,4 @@ -""" SCM hooks. +""" SCM hooks. Integration with git and mercurial. """ from __future__ import absolute_import @@ -10,7 +10,7 @@ try: - from configparser import ConfigParser # nolint + from configparser import ConfigParser # noqa except ImportError: # Python 2 from ConfigParser import ConfigParser @@ -95,11 +95,15 @@ def install_hook(path): git = op.join(path, '.git', 'hooks') hg = op.join(path, '.hg') if op.exists(git): - install_git(git) and LOGGER.warn('Git hook has been installed.') # nolint + install_git(git) + LOGGER.warn('Git hook has been installed.') elif op.exists(hg): - install_hg(git) and LOGGER.warn('Mercurial hook has been installed.') # nolint + install_hg(git) + LOGGER.warn('Mercurial hook has been installed.') else: LOGGER.error('VCS has not found. Check your path.') sys.exit(1) + +# lint_ignore=F0401 diff --git a/pylibs/pylama/inirama.py b/pylibs/pylama/inirama.py index 22998b2b..8e665724 100644 --- a/pylibs/pylama/inirama.py +++ b/pylibs/pylama/inirama.py @@ -1,9 +1,15 @@ """ Inirama is a python module that parses INI files. + .. _badges: .. include:: ../README.rst - :start-line: 5 - :end-line: 12 + :start-after: .. _badges: + :end-before: .. _contents: + + .. _description: + .. include:: ../README.rst + :start-after: .. _description: + :end-before: .. _badges: :copyright: 2013 by Kirill Klenov. :license: BSD, see LICENSE for more details. @@ -20,7 +26,7 @@ from ordereddict import OrderedDict -__version__ = '0.4.0' +__version__ = '0.4.1' __project__ = 'Inirama' __author__ = "Kirill Klenov " __license__ = "BSD" @@ -31,11 +37,14 @@ class Scanner(object): + """ Split a code string on tokens. """ + def __init__(self, source, ignore=None, patterns=None): """ Init Scanner instance. - :param patterns: List of token patterns [(token, regexp)] - :param ignore: List of ignored tokens + :param patterns: List of token patterns [(token, regexp)] + :param ignore: List of ignored tokens + """ self.reset(source) if patterns: @@ -47,17 +56,18 @@ def __init__(self, source, ignore=None, patterns=None): self.ignore = ignore def reset(self, source): - """ Reset scanner. + """ Reset scanner's state. + + :param source: Source for parsing - :param source: Source for parsing """ self.tokens = [] self.source = source self.pos = 0 def scan(self): - """ Scan source and grab tokens. - """ + """ Scan source and grab tokens. """ + self.pre_scan() token = None @@ -98,18 +108,23 @@ def scan(self): self.tokens.append(token) def pre_scan(self): - """ Prepare source. - """ + """ Prepare source. """ pass def __repr__(self): - """ Print the last 5 tokens that have been scanned in + """ Print the last 5 tokens that have been scanned in. + + :return str: + """ return '" class INIScanner(Scanner): + + """ Get tokens for INI. """ + patterns = [ ('SECTION', re.compile(r'\[[^]]+\]')), ('IGNORE', re.compile(r'[ \r\t\n]+')), @@ -119,6 +134,7 @@ class INIScanner(Scanner): ignore = ['IGNORE'] def pre_scan(self): + """ Prepare string for scaning. """ escape_re = re.compile(r'\\\n[\t ]+') self.source = escape_re.sub('', self.source) @@ -128,6 +144,8 @@ def pre_scan(self): class Section(MutableMapping): + """ Representation of INI section. """ + def __init__(self, namespace, *args, **kwargs): super(Section, self).__init__(*args, **kwargs) self.namespace = namespace @@ -152,6 +170,7 @@ def __repr__(self): return "<{0} {1}>".format(self.__class__.__name__, str(dict(self))) def iteritems(self): + """ Impletment iteritems. """ for key in self.__storage__.keys(): yield key, self[key] @@ -160,9 +179,17 @@ def iteritems(self): class InterpolationSection(Section): + """ INI section with interpolation support. """ + var_re = re.compile('{([^}]+)}') def get(self, name, default=None): + """ Get item by name. + + :return object: value or None if name not exists + + """ + if name in self: return self[name] return default @@ -189,26 +216,28 @@ def __getitem__(self, name): class Namespace(object): + """ Default class for parsing INI. - :param **default_items: Default items for default section. + :param **default_items: Default items for default section. - Usage - ----- + Usage + ----- - :: + :: - from inirama import Namespace + from inirama import Namespace - ns = Namespace() - ns.read('config.ini') + ns = Namespace() + ns.read('config.ini') - print ns['section']['key'] + print ns['section']['key'] - ns['other']['new'] = 'value' - ns.write('new_config.ini') + ns['other']['new'] = 'value' + ns.write('new_config.ini') """ + #: Name of default section (:attr:`~inirama.Namespace.default`) default_section = 'DEFAULT' @@ -226,16 +255,20 @@ def __init__(self, **default_items): @property def default(self): """ Return default section or empty dict. + + :return :class:`inirama.Section`: section + """ return self.sections.get(self.default_section, dict()) def read(self, *files, **params): """ Read and parse INI files. - :param *files: Files for reading - :param **params: Params for parsing + :param *files: Files for reading + :param **params: Params for parsing + + Set `update=False` for prevent values redefinition. - Set `update=False` for prevent values redefinition. """ for f in files: try: @@ -248,10 +281,10 @@ def read(self, *files, **params): raise def write(self, f): - """ - Write namespace as INI file. + """ Write namespace as INI file. + + :param f: File object or path to file. - :param f: File object or path to file. """ if isinstance(f, str): f = io.open(f, 'w', encoding='utf-8') @@ -270,8 +303,9 @@ def write(self, f): def parse(self, source, update=True, **params): """ Parse INI source as string. - :param source: Source of INI - :param update: Replace alredy defined items + :param source: Source of INI + :param update: Replace alredy defined items + """ scanner = INIScanner(source) scanner.scan() @@ -291,6 +325,9 @@ def parse(self, source, update=True, **params): def __getitem__(self, name): """ Look name in self sections. + + :return :class:`inirama.Section`: section + """ if not name in self.sections: self.sections[name] = self.section_type(self) @@ -304,6 +341,7 @@ def __repr__(self): class InterpolationNamespace(Namespace): + """ That implements the interpolation feature. :: @@ -323,4 +361,4 @@ class InterpolationNamespace(Namespace): section_type = InterpolationSection -# lint_ignore=W0201,R0924 +# lint_ignore=W0201,R0924,F0401 diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index a9cf9473..dfe38ece 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -102,12 +102,14 @@ def shell(): if op.isdir(options.path): paths = [] for root, _, files in walk(options.path): - paths += [op.join(root, f) for f in files if f.endswith('.py')] + paths += [ + op.relpath(op.join(root, f), curdir) + for f in files if f.endswith('.py')] check_files( paths, async=options.async, - rootpath=options.path, + rootpath=curdir, skip=options.skip, frmt=options.format, ignore=options.ignore, @@ -132,12 +134,12 @@ def check_files(paths, rootpath=None, skip=None, frmt="pep8", async=False, params = dict() if config: for key, section in config.sections.items(): - if key != 'main': - params[op.abspath(key)] = prepare_params(section) + if key != config.default_section: + mask = re.compile(fnmatch.translate(key)) + params[mask] = prepare_params(section) work_paths = [] for path in paths: - path = op.abspath(path) if skip and any(pattern.match(path) for pattern in skip): LOGGER.info('Skip path: %s', path) continue @@ -164,9 +166,5 @@ def prepare_params(section): return params -def __parse_options(args=None): - pass - - if __name__ == '__main__': shell() diff --git a/pylibs/pylama/tasks.py b/pylibs/pylama/tasks.py index 3d301c04..cf934477 100644 --- a/pylibs/pylama/tasks.py +++ b/pylibs/pylama/tasks.py @@ -91,10 +91,16 @@ def check_path(path, rootpath='.', ignore=None, select=None, linters=None, LOGGER.info("Parse file: %s", path) params = params or dict() + config = dict() + + for mask in params: + if mask.match(path): + config.update(params[mask]) errors = [] - for error in run(path, ignore=ignore, select=select, linters=linters, - complexity=complexity, config=params.get(path)): + for error in run( + path, ignore=ignore, select=select, linters=linters, + complexity=complexity, config=config): try: error['rel'] = op.relpath( error['filename'], op.dirname(rootpath)) diff --git a/pylibs/pylama/utils.py b/pylibs/pylama/utils.py index 2a135cf8..022b535d 100644 --- a/pylibs/pylama/utils.py +++ b/pylibs/pylama/utils.py @@ -5,7 +5,7 @@ import _ast from os import path as op, environ -from .pep8 import BaseReport, StyleGuide +from .checkers.pep8 import BaseReport, StyleGuide __all__ = 'pep8', 'pep257', 'mccabe', 'pyflakes', 'pylint' @@ -62,7 +62,7 @@ def mccabe(path, code=None, complexity=8, **meta): :return list: List of errors. """ - from .mccabe import get_code_complexity + from .checkers.mccabe import get_code_complexity return get_code_complexity(code, complexity, filename=path) or [] @@ -73,7 +73,7 @@ def pyflakes(path, code=None, **meta): :return list: List of errors. """ - from .pyflakes import checker + from .checkers.pyflakes import checker errors = [] tree = compile(code, path, "exec", _ast.PyCF_ONLY_AST) @@ -99,9 +99,9 @@ def pylint(path, **meta): logging.warn("Pylint don't supported python3 and will be disabled.") return [] - from .pylint.lint import Run - from .pylint.reporters import BaseReporter - from .pylint.logilab.astng import MANAGER + from .checkers.pylint.lint import Run + from .checkers.pylint.reporters import BaseReporter + from .checkers.pylint.logilab.astng import MANAGER MANAGER.astng_cache.clear() @@ -141,7 +141,7 @@ def pep257(path, **meta): """ f = open(path) - from .pep257 import check_source + from .checkers.pep257 import check_source errors = [] for er in check_source(f.read(), path): diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index 0f315c8a..96a7134a 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -1,12 +1,15 @@ +""" Pylama support. """ from __future__ import absolute_import -import locale +import fnmatch import json +import locale +from os import path as op +from re import compile as re from pylama.core import run -from pylama.main import prepare_params from pylama.inirama import Namespace -from os import path as op +from pylama.main import prepare_params from . import interface from .queue import add_task @@ -19,6 +22,7 @@ def check_file(): + """ Check current buffer. """ checkers = interface.get_option('lint_checker').split(',') buf = interface.get_current_buffer() @@ -46,8 +50,13 @@ def check_file(): complexity = int(interface.get_option('lint_mccabe_complexity') or 0) - params = None + params = dict() relpath = op.relpath(buf.name, curdir) + for mask in config.sections: + mask_re = re(fnmatch.translate(mask)) + if mask_re.match(relpath): + params.update(prepare_params(config[mask])) + if relpath in config: params = prepare_params(config[relpath]) @@ -64,7 +73,11 @@ def check_file(): def run_checkers(checkers=None, ignore=None, buf=None, select=None, complexity=None, callback=None, config=None): + """ Run pylama code. + + :return list: errors + """ pylint_options = '--rcfile={0} -r n'.format( interface.get_var('lint_config')).split() @@ -74,6 +87,7 @@ def run_checkers(checkers=None, ignore=None, buf=None, select=None, def parse_result(result, buf=None, **kwargs): + """ Parse results. """ interface.command('let g:qf_list = ' + json.dumps(result)) interface.command('call pymode#lint#Parse({0})'.format(buf.number)) From 2ec13e2991e905ae4c30d1fcc09312a1bff62e1b Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Sun, 23 Jun 2013 14:01:02 +0800 Subject: [PATCH 215/513] Fix autopep --- pylibs/autopep8.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylibs/autopep8.py b/pylibs/autopep8.py index 0cef7623..aa211dc4 100644 --- a/pylibs/autopep8.py +++ b/pylibs/autopep8.py @@ -54,7 +54,7 @@ import difflib import tempfile -from pylama import pep8 +from pylama.checkers import pep8 try: From 82b769d45284b66d5d7aae9b04235804da816ec4 Mon Sep 17 00:00:00 2001 From: Matt Stevens Date: Tue, 2 Jul 2013 01:35:48 +0100 Subject: [PATCH 216/513] Remove trailing whitespace from README and docs --- README.rst | 4 ++-- doc/pymode.txt | 28 ++++++++++++++-------------- doc/ropevim.txt | 6 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index a8f67c24..cf930d06 100644 --- a/README.rst +++ b/README.rst @@ -191,7 +191,7 @@ Default values: :: let g:pymode_lint_maxheight = 6 -.. note:: +.. note:: Pylint options (ex. disable messages) may be defined in ``$HOME/pylint.rc`` See pylint documentation: http://pylint-messages.wikidot.com/all-codes @@ -511,7 +511,7 @@ License Licensed under a `GNU lesser general public license`_. -If you like this plugin, you can send me postcard :) +If you like this plugin, you can send me postcard :) My address is here: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Klenov". **Thanks for support!** diff --git a/doc/pymode.txt b/doc/pymode.txt index 01c150f6..202b420c 100644 --- a/doc/pymode.txt +++ b/doc/pymode.txt @@ -104,13 +104,13 @@ PythonMode. These options should be set in your vimrc. |'pymode_syntax_print_as_function'| Hightlight `print` as function -|'pymode_syntax_highlight_equal_operator'| Hightlight `=` +|'pymode_syntax_highlight_equal_operator'| Hightlight `=` -|'pymode_syntax_highlight_stars_operator'| Hightlight `*` +|'pymode_syntax_highlight_stars_operator'| Hightlight `*` -|'pymode_syntax_highlight_self'| Hightlight `self` +|'pymode_syntax_highlight_self'| Hightlight `self` -|'pymode_syntax_indent_errors'| Hightlight indentation errors +|'pymode_syntax_indent_errors'| Hightlight indentation errors |'pymode_syntax_space_errors'| Hightlight trailing spaces as errors @@ -152,7 +152,7 @@ To enable any of the options below you should put the given line in your 2.2. Modeline ~ *PythonModeModeline* -The VIM modeline `:help modeline` feature allows you to change pymode +The VIM modeline `:help modeline` feature allows you to change pymode options for the current file. Pymode modeline should always be the last line in the vimrc file and look like: @@ -393,28 +393,28 @@ Hightlight `print` as function Values: 0 or 1. Default: |'pymode_syntax_all'|. -Hightlight `=` +Hightlight `=` ------------------------------------------------------------------------------ *'pymode_syntax_highlight_stars_operator'* Values: 0 or 1. Default: |'pymode_syntax_all'|. -Hightlight `*` +Hightlight `*` ------------------------------------------------------------------------------ *'pymode_syntax_highlight_self'* Values: 0 or 1. Default: |'pymode_syntax_all'|. -Hightlight `self` +Hightlight `self` ------------------------------------------------------------------------------ *'pymode_syntax_indent_errors'* Values: 0 or 1. Default: |'pymode_syntax_all'|. -Hightlight indentation errors +Hightlight indentation errors ------------------------------------------------------------------------------ *'pymode_syntax_space_errors'* @@ -576,7 +576,7 @@ iM Operation with inner function or method. *:PyLintAuto* *PyLintAuto* Automatically fix PEP8 errors in the current buffer - + *:Pyrun* *Pyrun* Run current buffer @@ -599,7 +599,7 @@ To work, rope_ creates a service directory: `.ropeproject`. If |'pymode_rope_guess_project'| is set on (as it is by default) and `.ropeproject` is not found in the current dir, rope will scan for `.ropeproject` in every dir in the parent path. If rope finds `.ropeproject` -in parent dirs, rope sets projectis for all child dirs and the scan may be +in parent dirs, rope sets projectis for all child dirs and the scan may be slow for many dirs and files. Solutions: @@ -621,7 +621,7 @@ modules if possible. Try using pyflakes: see |'pymode_lint_checker'|. You may set |exrc| and |secure| in your |vimrc| to auto-set custom settings from `.vimrc` from your projects directories. > - Example: On Flask projects I automatically set + Example: On Flask projects I automatically set 'g:pymode_lint_checker = "pyflakes"'. On Django 'g:pymode_lint_checker = "pylint"' < @@ -643,7 +643,7 @@ The sequence of commands that fixed this: ============================================================================== 6. Credits ~ - *PythonModeCredits* + *PythonModeCredits* Kirill Klenov http://klen.github.com/ http://github.com/klen/ @@ -684,7 +684,7 @@ The sequence of commands that fixed this: Python-mode is released under the GNU lesser general public license. See: http://www.gnu.org/copyleft/lesser.html -If you like this plugin, you can send me a postcard :) +If you like this plugin, you can send me a postcard :) My address is: "Russia, 143401, Krasnogorsk, Shkolnaya 1-19" to "Kirill Klenov". Thanks for your support! diff --git a/doc/ropevim.txt b/doc/ropevim.txt index 94a76216..0bf13bb8 100644 --- a/doc/ropevim.txt +++ b/doc/ropevim.txt @@ -151,7 +151,7 @@ those under ``ropetest`` folder. The find occurrences command (" f" by default) can be used to find the occurrences of a python name. If ``unsure`` option is ``yes``, it will also show unsure occurrences; unsure occurrences are -indicated with a ``?`` mark in the end. +indicated with a ``?`` mark in the end. Note: That ropevim uses the quickfix feature of vim for @@ -183,7 +183,7 @@ values you can use: > name2 line3 < -Each line of the definition should start with a space or a tab. +Each line of the definition should start with a space or a tab. Note: That blank lines before the name of config definitions are ignored. @@ -222,7 +222,7 @@ restructuring can be: > *RopeVariables* *'pymode_rope_codeassist_maxfixes'* The maximum number of syntax errors - to fix for code assists. + to fix for code assists. The default value is `1`. *'pymode_rope_local_prefix'* The prefix for ropevim refactorings. From 9756409fcb0697a342a3cf73fa464aedbf6c0b58 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 8 Jul 2013 16:17:03 +0800 Subject: [PATCH 217/513] Update pylama to version 1.3.0 --- pylibs/pylama/__init__.py | 2 +- pylibs/pylama/checkers/pep8.py | 27 +++-- pylibs/pylama/checkers/pyflakes/checker.py | 47 ++++---- pylibs/pylama/core.py | 131 +++++++++++++-------- pylibs/pylama/hook.py | 21 ++-- pylibs/pylama/main.py | 35 ++++-- pylibs/pylama/tasks.py | 3 +- 7 files changed, 163 insertions(+), 103 deletions(-) diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index 4cc579fc..acbcf35b 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -5,7 +5,7 @@ :license: BSD, see LICENSE for more details. """ -version_info = 1, 1, 0 +version_info = 1, 3, 0 __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/checkers/pep8.py b/pylibs/pylama/checkers/pep8.py index 602466d9..8413270f 100644 --- a/pylibs/pylama/checkers/pep8.py +++ b/pylibs/pylama/checkers/pep8.py @@ -45,7 +45,7 @@ 700 statements 900 syntax error """ -__version__ = '1.4.6a0' +__version__ = '1.4.6' import os import sys @@ -63,7 +63,7 @@ from ConfigParser import RawConfigParser DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git,__pycache__' -DEFAULT_IGNORE = 'E226,E24' +DEFAULT_IGNORE = 'E123,E226,E24' if sys.platform == 'win32': DEFAULT_CONFIG = os.path.expanduser(r'~\.pep8') else: @@ -381,7 +381,8 @@ def indentation(logical_line, previous_logical, indent_char, yield 0, "E113 unexpected indentation" -def continued_indentation(logical_line, tokens, indent_level, noqa, verbose): +def continued_indentation(logical_line, tokens, indent_level, hang_closing, + noqa, verbose): r""" Continuation lines should align wrapped elements either vertically using Python's implicit line joining inside parentheses, brackets and braces, or @@ -458,7 +459,8 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose): open_row = 0 hang = rel_indent[row] - rel_indent[open_row] close_bracket = (token_type == tokenize.OP and text in ']})') - visual_indent = not close_bracket and indent_chances.get(start[1]) + visual_indent = (not close_bracket and hang > 0 and + indent_chances.get(start[1])) if close_bracket and indent[depth]: # closing bracket for visual indent @@ -467,7 +469,8 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose): "visual indentation") elif close_bracket and not hang: # closing bracket matches indentation of opening bracket's line - pass + if hang_closing: + yield start, "E133 closing bracket is missing indentation" elif visual_indent is True: # visual indent is verified if not indent[depth]: @@ -481,7 +484,7 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose): "under-indented for visual indent") elif hang == 4 or (indent_next and rel_indent[row] == 8): # hanging indent is verified - if close_bracket: + if close_bracket and not hang_closing: yield (start, "E123 closing bracket does not match " "indentation of opening bracket's line") else: @@ -535,6 +538,7 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose): for idx in range(row, -1, -1): if parens[idx]: parens[idx] -= 1 + rel_indent[row] = rel_indent[idx] break assert len(indent) == depth + 1 if start[1] not in indent_chances: @@ -543,7 +547,7 @@ def continued_indentation(logical_line, tokens, indent_level, noqa, verbose): last_token_multiline = (start[0] != end[0]) - if indent_next and rel_indent[-1] == 4: + if indent_next and expand_indent(line) == indent_level + 4: yield (last_indent, "E125 continuation line does not distinguish " "itself from next logical line") @@ -1183,6 +1187,7 @@ def __init__(self, filename=None, lines=None, self._logical_checks = options.logical_checks self._ast_checks = options.ast_checks self.max_line_length = options.max_line_length + self.hang_closing = options.hang_closing self.verbose = options.verbose self.filename = filename if filename is None: @@ -1693,8 +1698,9 @@ def get_parser(prog='pep8', version=__version__): parser = OptionParser(prog=prog, version=version, usage="%prog [options] input ...") parser.config_options = [ - 'exclude', 'filename', 'select', 'ignore', 'max-line-length', 'count', - 'format', 'quiet', 'show-pep8', 'show-source', 'statistics', 'verbose'] + 'exclude', 'filename', 'select', 'ignore', 'max-line-length', + 'hang-closing', 'count', 'format', 'quiet', 'show-pep8', + 'show-source', 'statistics', 'verbose'] parser.add_option('-v', '--verbose', default=0, action='count', help="print status messages, or debug with -vv") parser.add_option('-q', '--quiet', default=0, action='count', @@ -1729,6 +1735,9 @@ def get_parser(prog='pep8', version=__version__): default=MAX_LINE_LENGTH, help="set maximum allowed line length " "(default: %default)") + parser.add_option('--hang-closing', action='store_true', + help="hang closing bracket instead of matching " + "indentation of opening bracket's line") parser.add_option('--format', metavar='format', default='default', help="set the error format [default|pylint|]") parser.add_option('--diff', action='store_true', diff --git a/pylibs/pylama/checkers/pyflakes/checker.py b/pylibs/pylama/checkers/pyflakes/checker.py index 3d4c6475..272caa6d 100644 --- a/pylibs/pylama/checkers/pyflakes/checker.py +++ b/pylibs/pylama/checkers/pyflakes/checker.py @@ -192,6 +192,10 @@ def unusedAssignments(self): yield name, binding +class GeneratorScope(Scope): + pass + + class ModuleScope(Scope): pass @@ -319,11 +323,14 @@ def checkDeadScopes(self): self.report(messages.UnusedImport, importation.source, importation.name) - def pushFunctionScope(self): - self.scopeStack.append(FunctionScope()) + def pushScope(self, scopeClass=FunctionScope): + self.scopeStack.append(scopeClass()) + + def pushFunctionScope(self): # XXX Deprecated + self.pushScope(FunctionScope) - def pushClassScope(self): - self.scopeStack.append(ClassScope()) + def pushClassScope(self): # XXX Deprecated + self.pushScope(ClassScope) def report(self, messageClass, *args, **kwargs): self.messages.append(messageClass(self.filename, *args, **kwargs)) @@ -437,12 +444,15 @@ def handleNodeLoad(self, node): else: return - # try enclosing function scopes + scopes = [scope for scope in self.scopeStack[:-1] + if isinstance(scope, (FunctionScope, ModuleScope))] + if isinstance(self.scope, GeneratorScope) and scopes[-1] != self.scopeStack[-2]: + scopes.append(self.scopeStack[-2]) + + # try enclosing function scopes and global scope importStarred = self.scope.importStarred - for scope in self.scopeStack[-2:0:-1]: + for scope in reversed(scopes): importStarred = importStarred or scope.importStarred - if not isinstance(scope, FunctionScope): - continue try: scope[name].used = (self.scope, node) except KeyError: @@ -450,15 +460,6 @@ def handleNodeLoad(self, node): else: return - # try global scope - importStarred = importStarred or self.scopeStack[0].importStarred - try: - self.scopeStack[0][name].used = (self.scope, node) - except KeyError: - pass - else: - return - # look in the built-ins if importStarred or name in self.builtIns: return @@ -570,7 +571,7 @@ def handleDoctests(self, node): # leading whitespace: ... return node_offset = self.offset or (0, 0) - self.pushFunctionScope() + self.pushScope() for example in examples: try: tree = compile(example.source, "", "exec", ast.PyCF_ONLY_AST) @@ -632,7 +633,7 @@ def LISTCOMP(self, node): self.handleNode(node.elt, node) def GENERATOREXP(self, node): - self.pushFunctionScope() + self.pushScope(GeneratorScope) # handle generators before element for gen in node.generators: self.handleNode(gen, node) @@ -642,7 +643,7 @@ def GENERATOREXP(self, node): SETCOMP = GENERATOREXP def DICTCOMP(self, node): - self.pushFunctionScope() + self.pushScope(GeneratorScope) for gen in node.generators: self.handleNode(gen, node) self.handleNode(node.key, node) @@ -742,7 +743,7 @@ def addArgs(arglist): def runFunction(): - self.pushFunctionScope() + self.pushScope() for name in args: self.addBinding(node, Argument(name, node), reportRedef=False) if isinstance(node.body, list): @@ -777,7 +778,7 @@ def CLASSDEF(self, node): if not PY2: for keywordNode in node.keywords: self.handleNode(keywordNode, node) - self.pushClassScope() + self.pushScope(ClassScope) if self.withDoctest: self.deferFunction(lambda: self.handleDoctests(node)) for stmt in node.body: @@ -847,5 +848,3 @@ def EXCEPTHANDLER(self, node): if isinstance(node.name, str): self.handleNodeStore(node) self.handleChildren(node) - -# pymode:lint=0 diff --git a/pylibs/pylama/core.py b/pylibs/pylama/core.py index 0349dd87..8dddc4e1 100644 --- a/pylibs/pylama/core.py +++ b/pylibs/pylama/core.py @@ -1,4 +1,4 @@ -""" Pylama core functionality. Get params and runs a checkers. +""" Pylama core functionality. Get params and runs the checkers. """ import logging import re @@ -6,13 +6,19 @@ from . import utils +#: A default checkers DEFAULT_LINTERS = 'pep8', 'pyflakes', 'mccabe' -LOGGER = logging.getLogger('pylama') -MODERE = re.compile(r'^\s*#\s+(?:pymode\:)?((?:lint[\w_]*=[^:\n\s]+:?)+)', - re.I | re.M) + +#: The skip pattern SKIP_PATTERN = '# noqa' -STREAM = logging.StreamHandler() +# Parse a modelines +MODELINE_RE = re.compile( + r'^\s*#\s+(?:pymode\:)?((?:lint[\w_]*=[^:\n\s]+:?)+)', re.I | re.M) + +# Setup a logger +LOGGER = logging.getLogger('pylama') +STREAM = logging.StreamHandler() LOGGER.addHandler(STREAM) @@ -24,47 +30,43 @@ def run(path, ignore=None, select=None, linters=DEFAULT_LINTERS, config=None, """ errors = [] - ignore = ignore and list(ignore) or [] - select = select and list(select) or [] try: with open(path, 'rU') as f: code = f.read() + '\n\n' - params = config or __parse_modeline(code) - params['skip'] = [False] + + params = prepare_params( + parse_modeline(code), config, ignore=ignore, select=select + ) for line in code.split('\n'): params['skip'].append(line.endswith(SKIP_PATTERN)) - if params.get('lint_ignore'): - ignore += params.get('lint_ignore').split(',') - - if params.get('lint_select'): - select += params.get('lint_select').split(',') - - if params.get('lint'): - for lint in linters: + if not params['lint']: + return errors + + for lint in linters: + try: + linter = getattr(utils, lint) + except AttributeError: + LOGGER.warning("Linter `%s` not found.", lint) + continue + + result = linter(path, code=code, **meta) + for e in result: + e['col'] = e.get('col') or 0 + e['lnum'] = e.get('lnum') or 0 + e['type'] = e.get('type') or 'E' + e['text'] = "{0} [{1}]".format((e.get( + 'text') or '').strip() + .replace("'", "\"").split('\n')[0], lint) + e['filename'] = path or '' try: - linter = getattr(utils, lint) - except AttributeError: - LOGGER.warning("Linter `%s` not found.", lint) + if not params['skip'][e['lnum']]: + errors.append(e) + except IndexError: continue - result = linter(path, code=code, **meta) - for e in result: - e['col'] = e.get('col') or 0 - e['lnum'] = e.get('lnum') or 0 - e['type'] = e.get('type') or 'E' - e['text'] = "{0} [{1}]".format((e.get( - 'text') or '').strip() - .replace("'", "\"").split('\n')[0], lint) - e['filename'] = path or '' - try: - if not params['skip'][e['lnum']]: - errors.append(e) - except IndexError: - continue - except IOError as e: errors.append(dict( lnum=0, @@ -85,29 +87,62 @@ def run(path, ignore=None, select=None, linters=DEFAULT_LINTERS, config=None, import traceback logging.debug(traceback.format_exc()) - errors = [er for er in errors if __ignore_error(er, select, ignore)] + errors = [er for er in errors if filter_errors(er, **params)] return sorted(errors, key=lambda x: x['lnum']) -def __parse_modeline(code): +def parse_modeline(code): """ Parse modeline params from file. :return dict: Linter params. """ - seek = MODERE.search(code) - params = dict(lint=1) + seek = MODELINE_RE.search(code) if seek: - params = dict(v.split('=') for v in seek.group(1).split(':')) - params['lint'] = int(params.get('lint', 1)) + return dict(v.split('=') for v in seek.group(1).split(':')) + + return dict() + + +def prepare_params(*configs, **params): + """ Prepare and merge a params from modeline or config. + + :return dict: + + """ + params['ignore'] = params.get('ignore') or [] + params['select'] = params.get('select') or [] + + for config in filter(None, configs): + for key in ('ignore', 'select'): + config.setdefault(key, config.get('lint_' + key, [])) + if not isinstance(config[key], list): + config[key] = config[key].split(',') + params[key] += config[key] + params['lint'] = config.get('lint', 1) + + params['ignore'] = set(params['ignore']) + params['select'] = set(params['select']) + params['skip'] = [False] + params.setdefault('lint', 1) return params -def __ignore_error(e, select, ignore): - for s in select: - if e['text'].startswith(s): - return True - for i in ignore: - if e['text'].startswith(i): - return False +def filter_errors(e, select=None, ignore=None, **params): + """ Filter a erros by select and ignore lists. + + :return bool: + + """ + + if select: + for s in select: + if e['text'].startswith(s): + return True + + if ignore: + for s in ignore: + if e['text'].startswith(s): + return False + return True diff --git a/pylibs/pylama/hook.py b/pylibs/pylama/hook.py index 846321db..0f3bddad 100644 --- a/pylibs/pylama/hook.py +++ b/pylibs/pylama/hook.py @@ -36,20 +36,21 @@ def git_hook(): check_files([f for f in map(str, files_modified) if f.endswith('.py')]) -def hg_hook(ui, repo, **kwargs): +def hg_hook(ui, repo, node=None, **kwargs): """ Run pylama after mercurial commit. """ from .main import check_files seen = set() paths = [] - for rev in range(repo[kwargs['node']], len(repo)): - for file_ in repo[rev].files(): - file_ = op.join(repo.root, file_) - if file_ in seen or not op.exists(file_): - continue - seen.add(file_) - if file_.endswith('.py'): - paths.append(file_) + if len(repo): + for rev in range(repo[node], len(repo)): + for file_ in repo[rev].files(): + file_ = op.join(repo.root, file_) + if file_ in seen or not op.exists(file_): + continue + seen.add(file_) + if file_.endswith('.py'): + paths.append(file_) LOGGER.setLevel('WARN') check_files(paths) @@ -57,7 +58,7 @@ def hg_hook(ui, repo, **kwargs): def install_git(path): """ Install hook in Git repository. """ hook = op.join(path, 'pre-commit') - with open(hook, 'w+') as fd: + with open(hook, 'w') as fd: fd.write("""#!/usr/bin/env python import sys from pylama.hook import git_hook diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index dfe38ece..4b449746 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -16,9 +16,17 @@ DEFAULT_COMPLEXITY = 10 -def shell(): - """ Endpoint for console. """ +def shell(args=None, error=True): + """ Endpoint for console. + + :return list: list of errors + :raise SystemExit: + + """ curdir = getcwd() + if args is None: + args = sys.argv[1:] + parser = ArgumentParser(description="Code audit tool for python.") parser.add_argument("path", nargs='?', default=curdir, help="Path on file or directory.") @@ -64,7 +72,7 @@ def shell(): parser.add_argument( "--options", "-o", default=op.join(curdir, 'pylama.ini'), help="Select configuration file. By default is '/pylama.ini'") - options = parser.parse_args() + options = parser.parse_args(args) actions = dict((a.dest, a) for a in parser._actions) # Read options from configuration file @@ -106,7 +114,7 @@ def shell(): op.relpath(op.join(root, f), curdir) for f in files if f.endswith('.py')] - check_files( + return check_files( paths, async=options.async, rootpath=curdir, @@ -117,13 +125,19 @@ def shell(): linters=options.linters, complexity=options.complexity, config=config, + error=error, ) def check_files(paths, rootpath=None, skip=None, frmt="pep8", async=False, select=None, ignore=None, linters=DEFAULT_LINTERS, - complexity=DEFAULT_COMPLEXITY, config=None): - """ Check files. """ + complexity=DEFAULT_COMPLEXITY, config=None, error=True): + """ Check files. + + :return list: list of errors + :raise SystemExit: + + """ from .tasks import async_check_files rootpath = rootpath or getcwd() @@ -149,10 +163,13 @@ def check_files(paths, rootpath=None, skip=None, frmt="pep8", async=False, work_paths, async=async, rootpath=rootpath, ignore=ignore, select=select, linters=linters, complexity=complexity, params=params) - for error in errors: - LOGGER.warning(pattern, error) + for er in errors: + LOGGER.warning(pattern, er) + + if error: + sys.exit(int(bool(errors))) - sys.exit(int(bool(errors))) + return errors def prepare_params(section): diff --git a/pylibs/pylama/tasks.py b/pylibs/pylama/tasks.py index cf934477..485b8905 100644 --- a/pylibs/pylama/tasks.py +++ b/pylibs/pylama/tasks.py @@ -102,8 +102,7 @@ def check_path(path, rootpath='.', ignore=None, select=None, linters=None, path, ignore=ignore, select=select, linters=linters, complexity=complexity, config=config): try: - error['rel'] = op.relpath( - error['filename'], op.dirname(rootpath)) + error['rel'] = op.relpath(error['filename'], rootpath) error['col'] = error.get('col', 1) errors.append(error) except KeyError: From 0910d5d10ac047f29931d15f374e7a5324e22d18 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 8 Jul 2013 16:33:42 +0800 Subject: [PATCH 218/513] Use pudb if available. --- ftplugin/python/init-pymode.vim | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index 59e287ea..529fc9a0 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -155,14 +155,19 @@ endif if !pymode#Default("g:pymode_breakpoint", 1) || g:pymode_breakpoint - if !pymode#Default("g:pymode_breakpoint_cmd", "import ipdb; ipdb.set_trace() # XXX BREAKPOINT") && has("python") + if !pymode#Default("g:pymode_breakpoint_cmd", "import pdb; pdb.set_trace() # XXX BREAKPOINT") && has("python") + python << EOF from imp import find_module -try: - find_module('ipdb') -except ImportError: - vim.command('let g:pymode_breakpoint_cmd = "import pdb; pdb.set_trace() # XXX BREAKPOINT"') + +for module in ('pudb', 'ipdb'): + try: + find_module(module) + vim.command('let g:pymode_breakpoint_cmd = "import %s; %s.set_trace() # XXX BREAKPOINT"' % (module, module)) + except ImportError: + continue EOF + endif " OPTION: g:pymode_breakpoint_key -- string. Key for set/unset breakpoint. From 4fdae4648959aa29bd1a649a7197be424d10e0b6 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 8 Jul 2013 16:34:16 +0800 Subject: [PATCH 219/513] Update changelog. --- Changelog.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog.rst b/Changelog.rst index a212925e..5536794b 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -3,7 +3,8 @@ Changelog * Added `g:pymode_rope_autocomplete_map` option; * Removed `g:pymode_rope_map_space` option; -* Added PEP257 checker +* Added PEP257 checker; +* Support 'pudb' in breakpoints; ## 2013-05-15 0.6.18 -------------------- From 6ce0fe1d7b128f832a69240fbcda9ed150ecca11 Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Mon, 8 Jul 2013 18:01:04 +0800 Subject: [PATCH 220/513] Fix pylama --- pylibs/pylama/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylibs/pylama/core.py b/pylibs/pylama/core.py index 8dddc4e1..33d5e239 100644 --- a/pylibs/pylama/core.py +++ b/pylibs/pylama/core.py @@ -110,8 +110,8 @@ def prepare_params(*configs, **params): :return dict: """ - params['ignore'] = params.get('ignore') or [] - params['select'] = params.get('select') or [] + params['ignore'] = list(params.get('ignore') or []) + params['select'] = list(params.get('select') or []) for config in filter(None, configs): for key in ('ignore', 'select'): From 63d92714b5f3e9f7c1985b26da9361abea083edd Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Tue, 30 Jul 2013 10:47:15 +0800 Subject: [PATCH 221/513] Update pylama --- pylibs/pylama/__init__.py | 2 +- pylibs/pylama/config.py | 174 ++++++++++++++++++++++++++++++++++++++ pylibs/pylama/core.py | 12 +-- pylibs/pylama/hook.py | 15 +++- pylibs/pylama/inirama.py | 10 --- pylibs/pylama/main.py | 163 +++++++---------------------------- pylibs/pylama/tasks.py | 24 +++--- pylibs/pymode/lint.py | 65 +++----------- 8 files changed, 247 insertions(+), 218 deletions(-) create mode 100644 pylibs/pylama/config.py diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index acbcf35b..3d7ddc52 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -5,7 +5,7 @@ :license: BSD, see LICENSE for more details. """ -version_info = 1, 3, 0 +version_info = 1, 3, 3 __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/config.py b/pylibs/pylama/config.py new file mode 100644 index 00000000..78933872 --- /dev/null +++ b/pylibs/pylama/config.py @@ -0,0 +1,174 @@ +""" Parse arguments from command line and configuration files. """ +import fnmatch +import logging +from argparse import ArgumentParser, Namespace as Options +from os import getcwd, path +from re import compile as re + +from . import version, utils +from .core import DEFAULT_LINTERS, LOGGER, STREAM +from .inirama import Namespace + + +DEFAULT_COMPLEXITY = 10 +CURDIR = getcwd() +DEFAULT_INI_PATH = path.join(CURDIR, 'pylama.ini') + + +def parse_options( + args=None, async=False, select='', ignore='', linters=DEFAULT_LINTERS, + complexity=DEFAULT_COMPLEXITY, options=DEFAULT_INI_PATH): + """ Parse options from command line and configuration files. + + :return argparse.Namespace: + + """ + parser = get_parser() + actions = dict((a.dest, a) for a in parser._actions) + options = Options( + async=_Default(async), format=_Default('pep8'), + select=_Default(select), ignore=_Default(ignore), + report=_Default(None), verbose=_Default(False), + linters=_Default(linters), complexity=_Default(complexity), + options=_Default(options)) + + if not (args is None): + options = parser.parse_args(args) + + config = get_config(str(options.options)) + + for k, v in config.default.items(): + value = getattr(options, k, _Default(None)) + if not isinstance(value, _Default): + continue + + action = actions.get(k) + LOGGER.info('Find option %s (%s)', k, v) + name, value = action.dest, action.type(v)\ + if callable(action.type) else v + if action.const: + value = bool(int(value)) + setattr(options, name, value) + + opts = dict(options.__dict__.items()) + for name, value in opts.items(): + if isinstance(value, _Default): + action = actions.get(name) + if action and callable(action.type): + value.value = action.type(value.value) + + setattr(options, name, value.value) + + options.file_params = dict() + for k, s in config.sections.items(): + if k != config.default_section: + mask = re(fnmatch.translate(k)) + options.file_params[mask] = dict(s) + options.file_params[mask]['lint'] = int( + options.file_params[mask].get('lint', 1) + ) + + return options + + +def setup_logger(options): + """ Setup logger with options. """ + + LOGGER.setLevel(logging.INFO if options.verbose else logging.WARN) + if options.report: + LOGGER.removeHandler(STREAM) + LOGGER.addHandler(logging.FileHandler(options.report, mode='w')) + LOGGER.info('Try to read configuration from: ' + options.options) + + +def get_parser(): + """ Make command parser for pylama. + + :return ArgumentParser: + + """ + split_csp_str = lambda s: list( + set(i for i in s.strip().split(',') if i)) + + parser = ArgumentParser(description="Code audit tool for python.") + parser.add_argument( + "path", nargs='?', default=_Default(CURDIR), + help="Path on file or directory.") + + parser.add_argument( + "--verbose", "-v", action='store_true', help="Verbose mode.") + + parser.add_argument('--version', action='version', + version='%(prog)s ' + version) + + parser.add_argument( + "--format", "-f", default=_Default('pep8'), choices=['pep8', 'pylint'], + help="Error format.") + + parser.add_argument( + "--select", "-s", default=_Default(''), type=split_csp_str, + help="Select errors and warnings. (comma-separated)") + + parser.add_argument( + "--linters", "-l", default=_Default(','.join(DEFAULT_LINTERS)), + type=split_csp_str, + help=( + "Select linters. (comma-separated). Choices are %s." + % ','.join(s for s in utils.__all__) + )) + + parser.add_argument( + "--ignore", "-i", default=_Default(''), type=split_csp_str, + help="Ignore errors and warnings. (comma-separated)") + + parser.add_argument( + "--skip", default=_Default(''), + type=lambda s: [re(fnmatch.translate(p)) for p in s.split(',') if p], + help="Skip files by masks (comma-separated, Ex. */messages.py)") + + parser.add_argument( + "--complexity", "-c", default=_Default(DEFAULT_COMPLEXITY), type=int, + help="Set mccabe complexity.") + + parser.add_argument("--report", "-r", help="Filename for report.") + parser.add_argument( + "--hook", action="store_true", help="Install Git (Mercurial) hook.") + + parser.add_argument( + "--async", action="store_true", + help="Enable async mode. Usefull for checking a lot of files. " + "Dont supported with pylint.") + + parser.add_argument( + "--options", "-o", default=_Default(DEFAULT_INI_PATH), + help="Select configuration file. By default is '/pylama.ini'") + + return parser + + +def get_config(ini_path=DEFAULT_INI_PATH): + """ Load configuration from INI. + + :return Namespace: + + """ + config = Namespace() + config.default_section = 'main' + config.read(ini_path) + + return config + + +class _Default(object): + + def __init__(self, value): + self.value = value + + def __getattr__(self, name): + return getattr(self.value, name) + + def __str__(self): + return str(self.value) + + +# lint_ignore=R0914,W0212,E1103,C901 diff --git a/pylibs/pylama/core.py b/pylibs/pylama/core.py index 33d5e239..81620a13 100644 --- a/pylibs/pylama/core.py +++ b/pylibs/pylama/core.py @@ -1,4 +1,5 @@ -""" Pylama core functionality. Get params and runs the checkers. +""" Pylama's core functionality. Prepare params, check a modeline and run the + checkers. """ import logging import re @@ -24,12 +25,13 @@ def run(path, ignore=None, select=None, linters=DEFAULT_LINTERS, config=None, **meta): - """ Run code checking for path. + """ Run a code checkers with given params. :return errors: list of dictionaries with error's information """ errors = [] + params = dict(ignore=ignore, select=select) try: with open(path, 'rU') as f: @@ -92,7 +94,7 @@ def run(path, ignore=None, select=None, linters=DEFAULT_LINTERS, config=None, def parse_modeline(code): - """ Parse modeline params from file. + """ Parse params from file's modeline. :return dict: Linter params. @@ -105,7 +107,7 @@ def parse_modeline(code): def prepare_params(*configs, **params): - """ Prepare and merge a params from modeline or config. + """ Prepare and merge a params from modelines and configs. :return dict: @@ -129,7 +131,7 @@ def prepare_params(*configs, **params): def filter_errors(e, select=None, ignore=None, **params): - """ Filter a erros by select and ignore lists. + """ Filter a erros by select and ignore options. :return bool: diff --git a/pylibs/pylama/hook.py b/pylibs/pylama/hook.py index 0f3bddad..2a3a5b6c 100644 --- a/pylibs/pylama/hook.py +++ b/pylibs/pylama/hook.py @@ -7,6 +7,7 @@ from subprocess import Popen, PIPE from .main import LOGGER +from .config import parse_options, setup_logger try: @@ -32,8 +33,12 @@ def git_hook(): from .main import check_files _, files_modified, _ = run("git diff-index --cached --name-only HEAD") - LOGGER.setLevel('WARN') - check_files([f for f in map(str, files_modified) if f.endswith('.py')]) + + options = parse_options() + setup_logger(options) + check_files( + [f for f in map(str, files_modified) if f.endswith('.py')], options + ) def hg_hook(ui, repo, node=None, **kwargs): @@ -51,8 +56,10 @@ def hg_hook(ui, repo, node=None, **kwargs): seen.add(file_) if file_.endswith('.py'): paths.append(file_) - LOGGER.setLevel('WARN') - check_files(paths) + + options = parse_options() + setup_logger(options) + check_files(paths, options) def install_git(path): diff --git a/pylibs/pylama/inirama.py b/pylibs/pylama/inirama.py index 8e665724..44a196cb 100644 --- a/pylibs/pylama/inirama.py +++ b/pylibs/pylama/inirama.py @@ -1,16 +1,6 @@ """ Inirama is a python module that parses INI files. - .. _badges: - .. include:: ../README.rst - :start-after: .. _badges: - :end-before: .. _contents: - - .. _description: - .. include:: ../README.rst - :start-after: .. _description: - :end-before: .. _badges: - :copyright: 2013 by Kirill Klenov. :license: BSD, see LICENSE for more details. """ diff --git a/pylibs/pylama/main.py b/pylibs/pylama/main.py index 4b449746..3b0231c3 100644 --- a/pylibs/pylama/main.py +++ b/pylibs/pylama/main.py @@ -1,16 +1,12 @@ -""" Pylama shell integration. +""" Pylama's shell support. """ from __future__ import absolute_import, with_statement -import fnmatch -import logging -import re import sys -from argparse import ArgumentParser -from os import getcwd, walk, path as op +from os import walk, path as op -from . import utils, version -from .core import DEFAULT_LINTERS, LOGGER, STREAM +from .config import parse_options, CURDIR, setup_logger +from .core import LOGGER DEFAULT_COMPLEXITY = 10 @@ -19,119 +15,36 @@ def shell(args=None, error=True): """ Endpoint for console. + Parse a command arguments, configuration files and run a checkers. + :return list: list of errors :raise SystemExit: """ - curdir = getcwd() if args is None: args = sys.argv[1:] - parser = ArgumentParser(description="Code audit tool for python.") - parser.add_argument("path", nargs='?', default=curdir, - help="Path on file or directory.") - parser.add_argument( - "--verbose", "-v", action='store_true', help="Verbose mode.") - parser.add_argument('--version', action='version', - version='%(prog)s ' + version) - - split_csp_list = lambda s: list(set(i for i in s.split(',') if i)) - - parser.add_argument( - "--format", "-f", default='pep8', choices=['pep8', 'pylint'], - help="Error format.") - parser.add_argument( - "--select", "-s", default='', - type=split_csp_list, - help="Select errors and warnings. (comma-separated)") - parser.add_argument( - "--linters", "-l", default=','.join(DEFAULT_LINTERS), - type=split_csp_list, - help=( - "Select linters. (comma-separated). Choices are %s." - % ','.join(s for s in utils.__all__) - )) - parser.add_argument( - "--ignore", "-i", default='', - type=split_csp_list, - help="Ignore errors and warnings. (comma-separated)") - parser.add_argument( - "--skip", default='', - type=lambda s: [re.compile(fnmatch.translate(p)) - for p in s.split(',')], - help="Skip files by masks (comma-separated, Ex. */messages.py)") - parser.add_argument("--complexity", "-c", default=DEFAULT_COMPLEXITY, - type=int, help="Set mccabe complexity.") - parser.add_argument("--report", "-r", help="Filename for report.") - parser.add_argument("--hook", action="store_true", - help="Install Git (Mercurial) hook.") - parser.add_argument( - "--async", action="store_true", - help="Enable async mode. Usefull for checking a lot of files. " - "Dont supported with pylint.") - parser.add_argument( - "--options", "-o", default=op.join(curdir, 'pylama.ini'), - help="Select configuration file. By default is '/pylama.ini'") - options = parser.parse_args(args) - actions = dict((a.dest, a) for a in parser._actions) - - # Read options from configuration file - from .inirama import Namespace - - config = Namespace() - config.default_section = 'main' - config.read(options.options) - for k, v in config.default.items(): - action = actions.get(k) - if action: - LOGGER.info('Find option %s (%s)', k, v) - name, value = action.dest, action.type(v)\ - if callable(action.type) else v - if action.const: - value = bool(int(value)) - setattr(options, name, value) - - # Setup LOGGER - LOGGER.setLevel(logging.INFO if options.verbose else logging.WARN) - if options.report: - LOGGER.removeHandler(STREAM) - LOGGER.addHandler(logging.FileHandler(options.report, mode='w')) - LOGGER.info('Try to read configuration from: ' + options.options) + options = parse_options(args) + setup_logger(options) # Install VSC hook if options.hook: from .hook import install_hook - install_hook(options.path) - - else: - - paths = [options.path] - - if op.isdir(options.path): - paths = [] - for root, _, files in walk(options.path): - paths += [ - op.relpath(op.join(root, f), curdir) - for f in files if f.endswith('.py')] - - return check_files( - paths, - async=options.async, - rootpath=curdir, - skip=options.skip, - frmt=options.format, - ignore=options.ignore, - select=options.select, - linters=options.linters, - complexity=options.complexity, - config=config, - error=error, - ) - - -def check_files(paths, rootpath=None, skip=None, frmt="pep8", async=False, - select=None, ignore=None, linters=DEFAULT_LINTERS, - complexity=DEFAULT_COMPLEXITY, config=None, error=True): + return install_hook(options.path) + + paths = [options.path] + + if op.isdir(options.path): + paths = [] + for root, _, files in walk(options.path): + paths += [ + op.relpath(op.join(root, f), CURDIR) + for f in files if f.endswith('.py')] + + return check_files(paths, options, error=error) + + +def check_files(paths, options, rootpath=None, error=True): """ Check files. :return list: list of errors @@ -140,28 +53,21 @@ def check_files(paths, rootpath=None, skip=None, frmt="pep8", async=False, """ from .tasks import async_check_files - rootpath = rootpath or getcwd() + if rootpath is None: + rootpath = CURDIR + pattern = "%(rel)s:%(lnum)s:%(col)s: %(text)s" - if frmt == 'pylint': + if options.format == 'pylint': pattern = "%(rel)s:%(lnum)s: [%(type)s] %(text)s" - params = dict() - if config: - for key, section in config.sections.items(): - if key != config.default_section: - mask = re.compile(fnmatch.translate(key)) - params[mask] = prepare_params(section) - work_paths = [] for path in paths: - if skip and any(pattern.match(path) for pattern in skip): + if options.skip and any(p.match(path) for p in options.skip): LOGGER.info('Skip path: %s', path) continue work_paths.append(path) - errors = async_check_files( - work_paths, async=async, rootpath=rootpath, ignore=ignore, - select=select, linters=linters, complexity=complexity, params=params) + errors = async_check_files(work_paths, options, rootpath=rootpath) for er in errors: LOGGER.warning(pattern, er) @@ -172,16 +78,5 @@ def check_files(paths, rootpath=None, skip=None, frmt="pep8", async=False, return errors -def prepare_params(section): - """ Parse modeline params from configuration. - - :return dict: Linter params. - - """ - params = dict(section) - params['lint'] = int(params.get('lint', 1)) - return params - - if __name__ == '__main__': shell() diff --git a/pylibs/pylama/tasks.py b/pylibs/pylama/tasks.py index 485b8905..250b07ee 100644 --- a/pylibs/pylama/tasks.py +++ b/pylibs/pylama/tasks.py @@ -40,7 +40,7 @@ def run(self): self.path_queue.task_done() -def async_check_files(paths, async=False, linters=None, **params): +def async_check_files(paths, options, rootpath=None): """ Check paths. :return list: list of errors @@ -50,12 +50,11 @@ def async_check_files(paths, async=False, linters=None, **params): errors = [] # Disable async if pylint enabled - async = async and not 'pylint' in linters - params['linters'] = linters + async = options.async and not 'pylint' in options.linters if not async: for path in paths: - errors += check_path(path, **params) + errors += check_path(path, options=options, rootpath=rootpath) return errors LOGGER.info('Async code checking is enabled.') @@ -68,7 +67,7 @@ def async_check_files(paths, async=False, linters=None, **params): worker.start() for path in paths: - path_queue.put((path, params)) + path_queue.put((path, dict(options=options, rootpath=rootpath))) path_queue.join() @@ -81,8 +80,7 @@ def async_check_files(paths, async=False, linters=None, **params): return errors -def check_path(path, rootpath='.', ignore=None, select=None, linters=None, - complexity=None, params=None): +def check_path(path, options=None, rootpath=None, **meta): """ Check path. :return list: list of errors @@ -90,17 +88,19 @@ def check_path(path, rootpath='.', ignore=None, select=None, linters=None, """ LOGGER.info("Parse file: %s", path) - params = params or dict() config = dict() + if rootpath is None: + rootpath = '.' - for mask in params: + for mask in options.file_params: if mask.match(path): - config.update(params[mask]) + config.update(options.file_params[mask]) errors = [] for error in run( - path, ignore=ignore, select=select, linters=linters, - complexity=complexity, config=config): + path, ignore=options.ignore, select=options.select, + linters=options.linters, complexity=options.complexity, + config=config, **meta): try: error['rel'] = op.relpath(error['filename'], rootpath) error['col'] = error.get('col', 1) diff --git a/pylibs/pymode/lint.py b/pylibs/pymode/lint.py index 96a7134a..3f19dfa5 100644 --- a/pylibs/pymode/lint.py +++ b/pylibs/pymode/lint.py @@ -1,15 +1,11 @@ """ Pylama support. """ from __future__ import absolute_import -import fnmatch import json import locale -from os import path as op -from re import compile as re -from pylama.core import run -from pylama.inirama import Namespace -from pylama.main import prepare_params +from pylama.main import parse_options +from pylama.tasks import check_path from . import interface from .queue import add_task @@ -23,56 +19,23 @@ def check_file(): """ Check current buffer. """ - checkers = interface.get_option('lint_checker').split(',') buf = interface.get_current_buffer() - # Check configuration from `pymode.ini` - curdir = interface.eval_code('getcwd()') - config = Namespace() - config.default_section = 'main' - config.read(op.join(curdir, 'pylama.ini'), op.join(curdir, 'pymode.ini')) - - ignore = set([ - i for i in ( - interface.get_option('lint_ignore').split(',') + - interface.get_var('lint_ignore').split(',') + - config.default.get('ignore', '').split(',') - ) if i - ]) - - select = set([ - s for s in ( - interface.get_option('lint_select').split(',') + - interface.get_var('lint_select').split(',') + - config.default.get('select', '').split(',') - ) if s - ]) - - complexity = int(interface.get_option('lint_mccabe_complexity') or 0) - - params = dict() - relpath = op.relpath(buf.name, curdir) - for mask in config.sections: - mask_re = re(fnmatch.translate(mask)) - if mask_re.match(relpath): - params.update(prepare_params(config[mask])) - - if relpath in config: - params = prepare_params(config[relpath]) + linters = interface.get_option('lint_checker') + ignore = interface.get_option('lint_ignore') + select = interface.get_option('lint_select') + complexity = interface.get_option('lint_mccabe_complexity') or '0' - add_task( - run_checkers, - callback=parse_result, - title='Code checking', + options = parse_options( + ignore=ignore, select=select, complexity=complexity, linters=linters) - # params - checkers=checkers, ignore=ignore, buf=buf, select=select, - complexity=complexity, config=params, + add_task( + run_checkers, callback=parse_result, title='Code checking', buf=buf, + options=options, ) -def run_checkers(checkers=None, ignore=None, buf=None, select=None, - complexity=None, callback=None, config=None): +def run_checkers(callback=None, buf=None, options=None): """ Run pylama code. :return list: errors @@ -81,9 +44,7 @@ def run_checkers(checkers=None, ignore=None, buf=None, select=None, pylint_options = '--rcfile={0} -r n'.format( interface.get_var('lint_config')).split() - return run( - buf.name, ignore=ignore, select=select, linters=checkers, - pylint=pylint_options, complexity=complexity, config=config) + return check_path(buf.name, options=options, pylint=pylint_options) def parse_result(result, buf=None, **kwargs): From dce446a6419061ddd2b715b3ba851b0c32ce6999 Mon Sep 17 00:00:00 2001 From: Lawrence Jacob Siebert Date: Sun, 4 Aug 2013 18:49:26 -0700 Subject: [PATCH 222/513] Update README.rst better english finded is not a word, and be is not past tense. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index cf930d06..70b969d6 100644 --- a/README.rst +++ b/README.rst @@ -165,7 +165,7 @@ Default values: :: " Check code every save let g:pymode_lint_write = 1 - " Auto open cwindow if errors be finded + " Auto open cwindow if errors were found let g:pymode_lint_cwindow = 1 " Show error message if cursor placed at the error line From 5f2a0f78f2d9ecb1c0d12a1c7465caed167db0b0 Mon Sep 17 00:00:00 2001 From: Nathan Hoad Date: Wed, 7 Aug 2013 06:39:13 +1000 Subject: [PATCH 223/513] Don't use a t.co link for the screencast link. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index cf930d06..e32bdb62 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ There is no need to install the pylint_, rope_ or any used python library on you - Powerful customization - And more, more ... -See (very old) screencast here: http://t.co/3b0bzeXA (sorry for quality, this is my first screencast) +See (very old) screencast here: http://www.youtube.com/watch?v=67OZNp9Z0CQ (sorry for quality, this is my first screencast) Another old presentation here: http://www.youtube.com/watch?v=YhqsjUUHj6g From 6e2f135c390b4fc0c2e8804c367ee32196f16aa4 Mon Sep 17 00:00:00 2001 From: Lawrence Akka Date: Sat, 17 Aug 2013 23:43:58 +0200 Subject: [PATCH 224/513] Allow Pyrun to deal with sys.exit termination --- autoload/pymode/run.vim | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index df971178..2d07d9b3 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -17,7 +17,18 @@ fun! pymode#run#Run(line1, line2) "{{{ try py context = globals() py context['raw_input'] = context['input'] = lambda s: vim.eval('input("{0}")'.format(s)) - py execfile(vim.eval('expand("%:p")'), context) +python << ENDPYTHON +try: + execfile(vim.eval('expand("%:p")'), context) +# Vim cannot handle a SystemExit error raised by Python, so we need to trap it here, and +# handle it specially +except SystemExit as e: + if e.code: + # A non-false code indicates abnormal termination. A false code will be treated as a + # successful run, and the error will be hidden from Vim + vim.command('echohl Error | echo "Script exited with code {0}" | echohl none'.format(e.code)) + vim.command('return') +ENDPYTHON py out, err = sys.stdout.getvalue().strip(), sys.stderr.getvalue() py sys.stdout, sys.stderr = stdout_, stderr_ @@ -38,7 +49,7 @@ else: EOF catch /.*/ - + echohl Error | echo "Run-time error." | echohl none endtry From 1d0969e9e5e1c7b0e0cfb70f1ea84bdddb32dfc3 Mon Sep 17 00:00:00 2001 From: Lawrence Akka Date: Mon, 19 Aug 2013 23:06:22 +0200 Subject: [PATCH 225/513] Fixing the quickfix window output. Better errorformat --- autoload/pymode/run.vim | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index 2d07d9b3..cc72c8a7 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -31,11 +31,23 @@ except SystemExit as e: ENDPYTHON py out, err = sys.stdout.getvalue().strip(), sys.stderr.getvalue() py sys.stdout, sys.stderr = stdout_, stderr_ - cexpr "" - py for x in err.strip().split('\n'): vim.command('caddexpr "' + x.replace('"', r'\"') + '"') + let l:traceback = [] let l:oldefm = &efm - set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m + let &efm = '%E File "%f"\, line %l\,%m,' + let &efm .= '%E File "%f"\, line %l,' + let &efm .= '%C%p^,' + let &efm .= '%+C %.%#,' + let &efm .= '%+C %.%#,' + let &efm .= '%Z%m,' + let &efm .= '%-G%.%#' + +python << ENDPYTHON2 +for x in [i for i in err.splitlines() if "" not in i]: + vim.command("call add(l:traceback, '{}')".format(x)) +ENDPYTHON2 + + cgetexpr(l:traceback) call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, 0) let &efm = l:oldefm @@ -49,8 +61,8 @@ else: EOF catch /.*/ - + echohl Error | echo "Run-time error." | echohl none - + endtry endfunction "}}} From e1e63cc72130d03bdce991eadb9bf84dfe9b3ae9 Mon Sep 17 00:00:00 2001 From: Lawrence Akka Date: Wed, 21 Aug 2013 17:07:04 +0100 Subject: [PATCH 226/513] Adding 'Traceback' line to quickfix errors, for clarity --- autoload/pymode/run.vim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index cc72c8a7..8af5a8d3 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -34,7 +34,8 @@ ENDPYTHON cexpr "" let l:traceback = [] let l:oldefm = &efm - let &efm = '%E File "%f"\, line %l\,%m,' + let &efm = '%+GTraceback%.%#,' + let &efm .= '%E File "%f"\, line %l\,%m,' let &efm .= '%E File "%f"\, line %l,' let &efm .= '%C%p^,' let &efm .= '%+C %.%#,' From a064c3c2983e7a6a07d513628e100b60726563a8 Mon Sep 17 00:00:00 2001 From: Lawrence Akka Date: Wed, 21 Aug 2013 17:37:44 +0100 Subject: [PATCH 227/513] Commenting the errorformat string --- autoload/pymode/run.vim | 46 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index 8af5a8d3..cbb3b81f 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -34,20 +34,58 @@ ENDPYTHON cexpr "" let l:traceback = [] let l:oldefm = &efm + + " The following lines set Vim's errorformat variable, to allow the + " quickfix window to show Python tracebacks properly. It is much + " easier to use let than set, because set requires many more + " characters to be escaped. This is much easier to read and + " maintain. % escapes are still needed however before any regex meta + " characters. Hence \S (non-whitespace) becomes %\S etc. Note that + " * becomes %#, so .* (match any character) becomes %.%# Commas must + " also be escaped, with a backslash (\,). See the Vim help on + " quickfix for details. + " + " Python errors are multi-lined. They often start with 'Traceback', so + " we want to capture that (with +G) and show it in the quickfix window + " because it explains the order of error messages. let &efm = '%+GTraceback%.%#,' - let &efm .= '%E File "%f"\, line %l\,%m,' - let &efm .= '%E File "%f"\, line %l,' + + " The error message itself starts with a line with 'File' in it. There + " are a couple of variations, and we need to process a line beginning + " with whitespace followed by File, the filename in "", a line number, + " and optional further text. %E here indicates the start of a multi-line + " error message. The %\C at the end means that a case-sensitive search is + " required. + let &efm .= '%E File "%f"\, line %l\,%m%\C,' + let &efm .= '%E File "%f"\, line %l%\C,' + + " The possible continutation lines are idenitifed to Vim by %C. We deal + " with these in order of most to least specific to ensure a proper + " match. A pointer (^) identifies the column in which the error occurs + " (but will not be entirely accurate due to indention of Python code). let &efm .= '%C%p^,' + " Any text, indented by more than two spaces contain useful information. + " We want this to appear in the quickfix window, hence %+. let &efm .= '%+C %.%#,' let &efm .= '%+C %.%#,' - let &efm .= '%Z%m,' + + " The last line (%Z) does not begin with any whitespace. We use a zero + " width lookahead (\&) to check this. The line contains the error + " message itself (%m) + let &efm .= '%Z%\S%\&%m,' + + " We can ignore any other lines (%-G) let &efm .= '%-G%.%#' python << ENDPYTHON2 +# Remove any error lines containing ''. We don't need them. +# Add the rest to a Vim list. for x in [i for i in err.splitlines() if "" not in i]: vim.command("call add(l:traceback, '{}')".format(x)) ENDPYTHON2 - +" Now we can add the list of errors to the quickfix window, and show it. We have +" to add them all at once in this way, because the errors are multi-lined and +" they won't be parsed properly otherwise. cgetexpr(l:traceback) call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, 0) let &efm = l:oldefm From 57aaea51bfbc5ecdb4297fb14bc546dd415efbdc Mon Sep 17 00:00:00 2001 From: Arun Mahapatra Date: Sat, 24 Aug 2013 15:29:54 +0530 Subject: [PATCH 228/513] Add pymode#Execute function to wrap calls to python interpreter. Extract setting up sys.path responsibility into a separate autoload module. Modify run.vim to support pymode#Execute. --- autoload/pymode.vim | 7 +++++++ autoload/pymode/path.vim | 12 ++++++++++++ autoload/pymode/run.vim | 23 +++++++++++++---------- ftplugin/python/init-pymode.vim | 20 ++++++-------------- ftplugin/python/pymode.vim | 2 +- 5 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 autoload/pymode/path.vim diff --git a/autoload/pymode.vim b/autoload/pymode.vim index c69e6798..89f19a21 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -12,6 +12,13 @@ fun! pymode#Default(name, default) "{{{ endfunction "}}} +fun! pymode#Execute(expression) "{{{ + " DESC: Execute an expression in the default python interpreter + " + execute 'python '.a:expression +endfunction "}}} + + fun! pymode#Option(name) "{{{ let name = 'b:pymode_' . a:name diff --git a/autoload/pymode/path.vim b/autoload/pymode/path.vim new file mode 100644 index 00000000..b692dbe1 --- /dev/null +++ b/autoload/pymode/path.vim @@ -0,0 +1,12 @@ +fun! pymode#path#Activate(plugin_root) "{{{ + +python << EOF +import sys, vim, os + +curpath = vim.eval("getcwd()") +libpath = os.path.join(vim.eval("a:plugin_root"), 'pylibs') + +sys.path = [libpath, curpath] + vim.eval("g:pymode_paths") + sys.path +EOF + +endfunction "}}} diff --git a/autoload/pymode/run.vim b/autoload/pymode/run.vim index df971178..5933f372 100644 --- a/autoload/pymode/run.vim +++ b/autoload/pymode/run.vim @@ -8,21 +8,24 @@ fun! pymode#run#Run(line1, line2) "{{{ return 0 endtry endif - py import StringIO - py sys.stdout, stdout_ = StringIO.StringIO(), sys.stdout - py sys.stderr, stderr_ = StringIO.StringIO(), sys.stderr - py enc = vim.eval('&enc') + call pymode#Execute("import StringIO") + call pymode#Execute("sys.stdout, stdout_ = StringIO.StringIO(), sys.stdout") + call pymode#Execute("sys.stderr, stderr_ = StringIO.StringIO(), sys.stderr") + call pymode#Execute("enc = vim.eval('&enc')") call setqflist([]) call pymode#WideMessage("Code running.") try - py context = globals() - py context['raw_input'] = context['input'] = lambda s: vim.eval('input("{0}")'.format(s)) - py execfile(vim.eval('expand("%:p")'), context) - py out, err = sys.stdout.getvalue().strip(), sys.stderr.getvalue() - py sys.stdout, sys.stderr = stdout_, stderr_ + call pymode#Execute("context = globals()") + call pymode#Execute("context['raw_input'] = context['input'] = lambda s: vim.eval('input(\"{0}\")'.format(s))") + call pymode#Execute("execfile(vim.eval('expand(\"%:p\")'), context)") + call pymode#Execute("out, err = sys.stdout.getvalue().strip(), sys.stderr.getvalue()") + call pymode#Execute("sys.stdout, sys.stderr = stdout_, stderr_") cexpr "" - py for x in err.strip().split('\n'): vim.command('caddexpr "' + x.replace('"', r'\"') + '"') +python << EOF +for x in err.strip().split('\n'): + vim.command('caddexpr "' + x.replace('"', r'\"') + '"') +EOF let l:oldefm = &efm set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m call pymode#QuickfixOpen(0, g:pymode_lint_hold, g:pymode_lint_maxheight, g:pymode_lint_minheight, 0) diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index 96ecd74a..506028cb 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -42,15 +42,7 @@ endif if !pymode#Default('g:pymode_path', 1) || g:pymode_path call pymode#Default('g:pymode_paths', []) - -python << EOF -import sys, vim, os - -curpath = vim.eval("getcwd()") -libpath = os.path.join(vim.eval("expand(':p:h:h:h')"), 'pylibs') - -sys.path = [libpath, curpath] + vim.eval("g:pymode_paths") + sys.path -EOF + call pymode#path#Activate(expand(":p:h:h:h")) endif " }}} @@ -130,9 +122,9 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint let g:pymode_lint_config = expand(":p:h:h:h") . "/pylint.ini" endif - py from pymode import queue + call pymode#Execute("from pymode import queue") - au VimLeavePre * py queue.stop_queue() + au VimLeavePre * call pymode#Execute("queue.stop_queue()") endif @@ -252,7 +244,7 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope call pymode#Default("g:pymode_rope_always_show_complete_menu", 0) " DESC: Init Rope - py import ropevim + call pymode#Execute("import ropevim") fun! RopeCodeAssistInsertMode() "{{{ call RopeCodeAssist() @@ -263,7 +255,7 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope if isdirectory(getcwd() . '/.ropeproject') " In order to pass it the quiet kwarg I need to open the project " using python and not vim, which should be no major issue - py ropevim._interface.open_project(quiet=True) + call pymode#Execute("ropevim._interface.open_project(quiet=True)") return "" endif endfunction "}}} @@ -275,7 +267,7 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope fun! RopeOmni(findstart, base) "{{{ if a:findstart - py ropevim._interface._find_start() + call pymode#Execute("ropevim._interface._find_start()") return g:pymode_offset else call RopeOmniComplete() diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 9b85421e..1560e2e9 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -76,7 +76,7 @@ if pymode#Option('lint') " DESC: Run queue let &l:updatetime = g:pymode_updatetime au CursorHold call pymode#queue#Poll() - au BufLeave py queue.stop_queue() + au BufLeave call pymode#Execute("queue.stop_queue()") endif From 225508defe29a55b24892641c1cf2717c1afdf68 Mon Sep 17 00:00:00 2001 From: Arun Mahapatra Date: Sat, 24 Aug 2013 15:58:16 +0530 Subject: [PATCH 229/513] Add pymode#Execute to doc, lint and queue. --- autoload/pymode/doc.vim | 10 +++++----- autoload/pymode/lint.vim | 8 ++++---- autoload/pymode/queue.vim | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/autoload/pymode/doc.vim b/autoload/pymode/doc.vim index cf997ad8..27e5b5eb 100644 --- a/autoload/pymode/doc.vim +++ b/autoload/pymode/doc.vim @@ -5,12 +5,12 @@ fun! pymode#doc#Show(word) "{{{ if a:word == '' echoerr "No name/symbol under cursor!" else - py import StringIO - py sys.stdout, _ = StringIO.StringIO(), sys.stdout - py help(vim.eval('a:word')) - py sys.stdout, out = _, sys.stdout.getvalue() + call pymode#Execute("import StringIO") + call pymode#Execute("sys.stdout, _ = StringIO.StringIO(), sys.stdout") + call pymode#Execute("help('".a:word."')") + call pymode#Execute("sys.stdout, out = _, sys.stdout.getvalue()") call pymode#TempBuffer() - py vim.current.buffer.append(str(out).split('\n'), 0) + call pymode#Execute("vim.current.buffer.append(str(out).splitlines(), 0)") wincmd p endif endfunction "}}} diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 68782e18..37e9d4fa 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -14,8 +14,8 @@ fun! pymode#lint#Check() "{{{ let g:pymode_lint_buffer = bufnr('%') - py from pymode import lint - py lint.check_file() + call pymode#Execute("from pymode import lint") + call pymode#Execute("lint.check_file()") endfunction " }}} @@ -100,8 +100,8 @@ fun! pymode#lint#Auto() "{{{ return 0 endtry endif - py from pymode import auto - py auto.fix_current_file() + call pymode#Execute("from pymode import auto") + call pymode#Execute("auto.fix_current_file()") cclose edit call pymode#WideMessage("AutoPep8 done.") diff --git a/autoload/pymode/queue.vim b/autoload/pymode/queue.vim index b3160ee0..656049a7 100644 --- a/autoload/pymode/queue.vim +++ b/autoload/pymode/queue.vim @@ -1,7 +1,8 @@ fun! pymode#queue#Poll() "{{{ " Check current tasks - py queue.check_task() + call pymode#Execute("from pymode import queue") + call pymode#Execute("queue.check_task()") " Update interval if mode() == 'i' From b56321512a76b2d273bf04a7c7ef1cc9fdb5bd09 Mon Sep 17 00:00:00 2001 From: Arun Mahapatra Date: Sun, 25 Aug 2013 09:54:21 +0530 Subject: [PATCH 230/513] Do not indent and other ftplugin if g:pymode is zero. --- after/indent/python.vim | 4 ++++ ftplugin/python/pymode.vim | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/after/indent/python.vim b/after/indent/python.vim index 1e324f23..969488c3 100644 --- a/after/indent/python.vim +++ b/after/indent/python.vim @@ -1,3 +1,7 @@ +if pymode#Default('g:pymode', 1) || !g:pymode + finish +endif + if pymode#Default('b:pymode_indent', 1) || !g:pymode_indent finish endif diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 9b85421e..c4722219 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -1,5 +1,9 @@ runtime ftplugin/python/init-pymode.vim +if pymode#Default('g:pymode', 1) || !g:pymode + finish +endif + if pymode#Default('b:pymode', 1) finish endif From 6b94dc20761bd9430dd52e2818b810af213b5530 Mon Sep 17 00:00:00 2001 From: Arun Mahapatra Date: Sun, 25 Aug 2013 10:00:11 +0530 Subject: [PATCH 231/513] Remove unnecessary check for pymode state in buffer scope. --- ftplugin/python/pymode.vim | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index c4722219..de9bc680 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -4,10 +4,6 @@ if pymode#Default('g:pymode', 1) || !g:pymode finish endif -if pymode#Default('b:pymode', 1) - finish -endif - " Parse pymode modeline call pymode#Modeline() From 7f9e3ad7098312a964de85d5e946f651c75eb20c Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 29 Aug 2013 18:46:18 +0700 Subject: [PATCH 232/513] Update changelog. --- Changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.rst b/Changelog.rst index 5536794b..8a166c93 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -5,6 +5,7 @@ Changelog * Removed `g:pymode_rope_map_space` option; * Added PEP257 checker; * Support 'pudb' in breakpoints; +* Pyrun can now operate on a range of lines, and does not need to save (c) lawrenceakka ## 2013-05-15 0.6.18 -------------------- From aa9d049b4492f793217b4ab107b4bf7ad29c3edc Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 29 Aug 2013 18:46:36 +0700 Subject: [PATCH 233/513] Prepare to python3 support --- AUTHORS | 1 + autoload/pymode.vim | 7 ------- autoload/pymode/breakpoint.vim | 3 ++- autoload/pymode/doc.vim | 9 ++++----- autoload/pymode/lint.vim | 8 ++++---- autoload/pymode/path.vim | 29 +++++++++++++++++------------ autoload/pymode/queue.vim | 4 ++-- autoload/pymode/virtualenv.vim | 2 +- ftplugin/python/init-pymode.vim | 19 ++++++++++++------- ftplugin/python/pymode.vim | 4 ++-- 10 files changed, 45 insertions(+), 41 deletions(-) diff --git a/AUTHORS b/AUTHORS index e0bc5bbb..797fb34e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,6 +11,7 @@ Contributors: * Denis Kasak (dkasak); * Steve Losh (sjl); * nixon; +* lawrenceakka; * tramchamploo; * Benjamin Ruston (bruston); * Robert David Grant (bgrant); diff --git a/autoload/pymode.vim b/autoload/pymode.vim index 89f19a21..c69e6798 100644 --- a/autoload/pymode.vim +++ b/autoload/pymode.vim @@ -12,13 +12,6 @@ fun! pymode#Default(name, default) "{{{ endfunction "}}} -fun! pymode#Execute(expression) "{{{ - " DESC: Execute an expression in the default python interpreter - " - execute 'python '.a:expression -endfunction "}}} - - fun! pymode#Option(name) "{{{ let name = 'b:pymode_' . a:name diff --git a/autoload/pymode/breakpoint.vim b/autoload/pymode/breakpoint.vim index b71bc596..82dd8e82 100644 --- a/autoload/pymode/breakpoint.vim +++ b/autoload/pymode/breakpoint.vim @@ -1,3 +1,4 @@ +" Quick set or delete a breakpoints fun! pymode#breakpoint#Set(lnum) "{{{ let line = getline(a:lnum) if strridx(line, g:pymode_breakpoint_cmd) != -1 @@ -8,7 +9,7 @@ fun! pymode#breakpoint#Set(lnum) "{{{ normal k endif - " Save file + " Save file without any events if &modifiable && &modified | noautocmd write | endif endfunction "}}} diff --git a/autoload/pymode/doc.vim b/autoload/pymode/doc.vim index 27e5b5eb..f5800f99 100644 --- a/autoload/pymode/doc.vim +++ b/autoload/pymode/doc.vim @@ -5,12 +5,11 @@ fun! pymode#doc#Show(word) "{{{ if a:word == '' echoerr "No name/symbol under cursor!" else - call pymode#Execute("import StringIO") - call pymode#Execute("sys.stdout, _ = StringIO.StringIO(), sys.stdout") - call pymode#Execute("help('".a:word."')") - call pymode#Execute("sys.stdout, out = _, sys.stdout.getvalue()") + Python import StringIO + Python sys.stdout, _ = StringIO.StringIO(), sys.stdout + Python help(vim.eval('a:word')) call pymode#TempBuffer() - call pymode#Execute("vim.current.buffer.append(str(out).splitlines(), 0)") + Python vim.current.buffer.append(str(out).splitlines(), 0) wincmd p endif endfunction "}}} diff --git a/autoload/pymode/lint.vim b/autoload/pymode/lint.vim index 37e9d4fa..7e6bc9b2 100644 --- a/autoload/pymode/lint.vim +++ b/autoload/pymode/lint.vim @@ -14,8 +14,8 @@ fun! pymode#lint#Check() "{{{ let g:pymode_lint_buffer = bufnr('%') - call pymode#Execute("from pymode import lint") - call pymode#Execute("lint.check_file()") + Python from pymode import lint + Python lint.check_file() endfunction " }}} @@ -100,8 +100,8 @@ fun! pymode#lint#Auto() "{{{ return 0 endtry endif - call pymode#Execute("from pymode import auto") - call pymode#Execute("auto.fix_current_file()") + Python from pymode import auto + Python auto.fix_current_file() cclose edit call pymode#WideMessage("AutoPep8 done.") diff --git a/autoload/pymode/path.vim b/autoload/pymode/path.vim index b692dbe1..e765ac7d 100644 --- a/autoload/pymode/path.vim +++ b/autoload/pymode/path.vim @@ -1,12 +1,17 @@ -fun! pymode#path#Activate(plugin_root) "{{{ - -python << EOF -import sys, vim, os - -curpath = vim.eval("getcwd()") -libpath = os.path.join(vim.eval("a:plugin_root"), 'pylibs') - -sys.path = [libpath, curpath] + vim.eval("g:pymode_paths") + sys.path -EOF - -endfunction "}}} +fun! pymode#path#Activate(plugin_root) "{{{ + +Python << EOF +import sys, vim, os + +pymode_lib = 'pylibs' + +if sys.version >= (3, 0, 0): + pymode_lib = 'pylibs3' + +curpath = vim.eval("getcwd()") +libpath = os.path.join(vim.eval("a:plugin_root"), pymode_lib) + +sys.path = [libpath, curpath] + vim.eval("g:pymode_paths") + sys.path +EOF + +endfunction "}}} diff --git a/autoload/pymode/queue.vim b/autoload/pymode/queue.vim index 656049a7..d38208b7 100644 --- a/autoload/pymode/queue.vim +++ b/autoload/pymode/queue.vim @@ -1,8 +1,8 @@ fun! pymode#queue#Poll() "{{{ " Check current tasks - call pymode#Execute("from pymode import queue") - call pymode#Execute("queue.check_task()") + Python from pymode import queue + Python queue.check_task() " Update interval if mode() == 'i' diff --git a/autoload/pymode/virtualenv.vim b/autoload/pymode/virtualenv.vim index c771d907..04267343 100644 --- a/autoload/pymode/virtualenv.vim +++ b/autoload/pymode/virtualenv.vim @@ -12,7 +12,7 @@ fun! pymode#virtualenv#Activate() "{{{ call add(g:pymode_virtualenv_enabled, $VIRTUAL_ENV) -python << EOF +Python << EOF import sys, vim, os ve_dir = vim.eval('$VIRTUAL_ENV') diff --git a/ftplugin/python/init-pymode.vim b/ftplugin/python/init-pymode.vim index 506028cb..a14082e2 100644 --- a/ftplugin/python/init-pymode.vim +++ b/ftplugin/python/init-pymode.vim @@ -13,7 +13,7 @@ if pymode#Default('g:pymode', 1) || !g:pymode endif " DESC: Check python support -if !has('python') +if !has('python') && !has('python3') let g:pymode_virtualenv = 0 let g:pymode_path = 0 let g:pymode_lint = 0 @@ -23,6 +23,11 @@ if !has('python') let g:pymode_run = 0 endif +if has('python') + command! -nargs=1 Python python +elseif has('python3') + command! -nargs=1 Python python3 +end " Virtualenv {{{ @@ -122,9 +127,9 @@ if !pymode#Default("g:pymode_lint", 1) || g:pymode_lint let g:pymode_lint_config = expand(":p:h:h:h") . "/pylint.ini" endif - call pymode#Execute("from pymode import queue") + Python from pymode import queue - au VimLeavePre * call pymode#Execute("queue.stop_queue()") + au VimLeavePre * Python queue.stop_queue() endif @@ -149,7 +154,7 @@ if !pymode#Default("g:pymode_breakpoint", 1) || g:pymode_breakpoint if !pymode#Default("g:pymode_breakpoint_cmd", "import pdb; pdb.set_trace() # XXX BREAKPOINT") && has("python") -python << EOF +Python << EOF from imp import find_module for module in ('pudb', 'ipdb'): @@ -244,7 +249,7 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope call pymode#Default("g:pymode_rope_always_show_complete_menu", 0) " DESC: Init Rope - call pymode#Execute("import ropevim") + Python import ropevim fun! RopeCodeAssistInsertMode() "{{{ call RopeCodeAssist() @@ -255,7 +260,7 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope if isdirectory(getcwd() . '/.ropeproject') " In order to pass it the quiet kwarg I need to open the project " using python and not vim, which should be no major issue - call pymode#Execute("ropevim._interface.open_project(quiet=True)") + Python ropevim._interface.open_project(quiet=True) return "" endif endfunction "}}} @@ -267,7 +272,7 @@ if !pymode#Default("g:pymode_rope", 1) || g:pymode_rope fun! RopeOmni(findstart, base) "{{{ if a:findstart - call pymode#Execute("ropevim._interface._find_start()") + Python ropevim._interface._find_start() return g:pymode_offset else call RopeOmniComplete() diff --git a/ftplugin/python/pymode.vim b/ftplugin/python/pymode.vim index 25a839b3..d9b7a755 100644 --- a/ftplugin/python/pymode.vim +++ b/ftplugin/python/pymode.vim @@ -1,6 +1,6 @@ runtime ftplugin/python/init-pymode.vim -if pymode#Default('g:pymode', 1) || !g:pymode +if !g:pymode finish endif @@ -76,7 +76,7 @@ if pymode#Option('lint') " DESC: Run queue let &l:updatetime = g:pymode_updatetime au CursorHold call pymode#queue#Poll() - au BufLeave call pymode#Execute("queue.stop_queue()") + au BufLeave Python queue.stop_queue() endif From f32a5124d03983754ff7f7c46b36460890ab395a Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 29 Aug 2013 19:00:47 +0700 Subject: [PATCH 234/513] Change paths. --- autoload/pymode/path.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autoload/pymode/path.vim b/autoload/pymode/path.vim index e765ac7d..a2793e4e 100644 --- a/autoload/pymode/path.vim +++ b/autoload/pymode/path.vim @@ -5,8 +5,8 @@ import sys, vim, os pymode_lib = 'pylibs' -if sys.version >= (3, 0, 0): - pymode_lib = 'pylibs3' +# if sys.version >= (3, 0, 0): +# pymode_lib = 'pylibs3' curpath = vim.eval("getcwd()") libpath = os.path.join(vim.eval("a:plugin_root"), pymode_lib) From b1beaae3fc63c117ef533cd80e3092aba16c231e Mon Sep 17 00:00:00 2001 From: Kirill Klenov Date: Thu, 29 Aug 2013 23:34:03 +0700 Subject: [PATCH 235/513] Update pylama to version 1.5.0 --- Changelog.rst | 1 + pylibs/pylama/__init__.py | 10 +- pylibs/pylama/checkers/mccabe.py | 30 +- pylibs/pylama/checkers/pep257.py | 43 ++- pylibs/pylama/checkers/pep8.py | 5 +- pylibs/pylama/checkers/pylint/__init__.py | 5 +- pylibs/pylama/checkers/pylint/__pkginfo__.py | 5 +- .../checkers/pylint/astroid/__init__.py | 118 ++++++ .../{logilab/astng => astroid}/__pkginfo__.py | 22 +- .../{logilab/astng => astroid}/as_string.py | 142 +++---- .../{logilab/astng => astroid}/bases.py | 51 ++- .../{logilab/astng => astroid}/builder.py | 54 ++- .../{logilab/astng => astroid}/exceptions.py | 29 +- .../{logilab/astng => astroid}/inference.py | 70 ++-- .../{logilab/astng => astroid}/manager.py | 153 ++++---- .../{logilab/astng => astroid}/mixins.py | 12 +- .../astng => astroid}/node_classes.py | 129 ++++--- .../{logilab/astng => astroid}/nodes.py | 10 +- .../{logilab/astng => astroid}/protocols.py | 17 +- .../astng => astroid}/raw_building.py | 60 +-- .../{logilab/astng => astroid}/rebuilder.py | 125 ++++-- .../astng => astroid}/scoped_nodes.py | 107 ++--- .../{logilab/astng => astroid}/utils.py | 30 +- .../checkers/pylint/checkers/__init__.py | 57 ++- .../pylama/checkers/pylint/checkers/base.py | 365 +++++++++++------- .../checkers/pylint/checkers/classes.py | 72 ++-- .../pylint/checkers/design_analysis.py | 94 ++--- .../checkers/pylint/checkers/exceptions.py | 57 +-- .../pylama/checkers/pylint/checkers/format.py | 72 ++-- .../checkers/pylint/checkers/imports.py | 29 +- .../checkers/pylint/checkers/logging.py | 29 +- .../pylama/checkers/pylint/checkers/misc.py | 58 +-- .../checkers/pylint/checkers/newstyle.py | 55 ++- .../checkers/pylint/checkers/raw_metrics.py | 14 +- .../checkers/pylint/checkers/similar.py | 34 +- .../pylama/checkers/pylint/checkers/stdlib.py | 68 ++++ .../checkers/pylint/checkers/strings.py | 41 +- .../checkers/pylint/checkers/typecheck.py | 84 ++-- .../pylama/checkers/pylint/checkers/utils.py | 111 ++++-- .../checkers/pylint/checkers/variables.py | 101 +++-- pylibs/pylama/checkers/pylint/config.py | 16 +- pylibs/pylama/checkers/pylint/interfaces.py | 52 +-- pylibs/pylama/checkers/pylint/lint.py | 197 +++++----- .../checkers/pylint/logilab/astng/__init__.py | 72 ---- .../pylint/logilab/astng/brain/__init__.py | 0 .../logilab/astng/brain/py2mechanize.py | 20 - .../pylint/logilab/astng/brain/py2qt4.py | 25 -- .../pylint/logilab/astng/brain/py2stdlib.py | 182 --------- .../pylint/logilab/common/__pkginfo__.py | 9 +- .../pylint/logilab/common/changelog.py | 2 +- .../pylint/logilab/common/configuration.py | 15 +- .../pylint/logilab/common/deprecation.py | 234 +++++------ .../pylint/logilab/common/modutils.py | 33 +- .../pylint/logilab/common/optik_ext.py | 4 +- .../logilab/common/ureports/docbook_writer.py | 2 +- .../logilab/common/ureports/html_writer.py | 2 +- .../logilab/common/ureports/text_writer.py | 2 +- .../checkers/pylint/reporters/__init__.py | 62 +-- .../checkers/pylint/reporters/guireporter.py | 12 +- .../pylama/checkers/pylint/reporters/html.py | 17 +- .../pylama/checkers/pylint/reporters/text.py | 100 +++-- pylibs/pylama/checkers/pylint/utils.py | 151 +++++--- pylibs/pylama/core.py | 50 ++- pylibs/pylama/hook.py | 5 +- pylibs/pylama/inirama.py | 55 ++- pylibs/pylama/main.py | 3 +- pylibs/pylama/tasks.py | 3 +- pylibs/pylama/utils.py | 7 +- 68 files changed, 2120 insertions(+), 1781 deletions(-) create mode 100644 pylibs/pylama/checkers/pylint/astroid/__init__.py rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/__pkginfo__.py (71%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/as_string.py (78%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/bases.py (92%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/builder.py (83%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/exceptions.py (55%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/inference.py (88%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/manager.py (63%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/mixins.py (91%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/node_classes.py (89%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/nodes.py (88%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/protocols.py (96%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/raw_building.py (87%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/rebuilder.py (90%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/scoped_nodes.py (91%) rename pylibs/pylama/checkers/pylint/{logilab/astng => astroid}/utils.py (90%) create mode 100644 pylibs/pylama/checkers/pylint/checkers/stdlib.py delete mode 100644 pylibs/pylama/checkers/pylint/logilab/astng/__init__.py delete mode 100644 pylibs/pylama/checkers/pylint/logilab/astng/brain/__init__.py delete mode 100644 pylibs/pylama/checkers/pylint/logilab/astng/brain/py2mechanize.py delete mode 100644 pylibs/pylama/checkers/pylint/logilab/astng/brain/py2qt4.py delete mode 100644 pylibs/pylama/checkers/pylint/logilab/astng/brain/py2stdlib.py diff --git a/Changelog.rst b/Changelog.rst index 8a166c93..bf059336 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -6,6 +6,7 @@ Changelog * Added PEP257 checker; * Support 'pudb' in breakpoints; * Pyrun can now operate on a range of lines, and does not need to save (c) lawrenceakka +* Update pylama to version 1.5.0 ## 2013-05-15 0.6.18 -------------------- diff --git a/pylibs/pylama/__init__.py b/pylibs/pylama/__init__.py index 3d7ddc52..c3cb1d7b 100644 --- a/pylibs/pylama/__init__.py +++ b/pylibs/pylama/__init__.py @@ -1,11 +1,11 @@ -""" - Code audit tool for python. +""" Code audit tool for python. + +:copyright: 2013 by Kirill Klenov. +:license: BSD, see LICENSE for more details. - :copyright: 2013 by Kirill Klenov. - :license: BSD, see LICENSE for more details. """ -version_info = 1, 3, 3 +version_info = 1, 5, 0 __version__ = version = '.'.join(map(str, version_info)) __project__ = __name__ diff --git a/pylibs/pylama/checkers/mccabe.py b/pylibs/pylama/checkers/mccabe.py index 71f024a9..4ea34be0 100644 --- a/pylibs/pylama/checkers/mccabe.py +++ b/pylibs/pylama/checkers/mccabe.py @@ -3,17 +3,18 @@ http://nedbatchelder.com/blog/200803/python_code_complexity_microtool.html MIT License. """ -from __future__ import absolute_import, with_statement +from __future__ import with_statement -import sys - -import ast import optparse -from ast import iter_child_nodes +import sys from collections import defaultdict +try: + import ast + from ast import iter_child_nodes +except ImportError: # Python 2.5 + from flake8.util import ast, iter_child_nodes - -__version__ = '0.2' +__version__ = '0.2.1' class ASTVisitor(object): @@ -263,14 +264,13 @@ def get_code_complexity(code, threshold=7, filename='stdin'): complx = [] McCabeChecker.max_complexity = threshold - for lineno, offset, text, _ in McCabeChecker(tree, filename).run(): - complx.append(dict( - type=McCabeChecker._code, - lnum=lineno, - text=text, - )) + for lineno, offset, text, check in McCabeChecker(tree, filename).run(): + complx.append('%s:%d:1: %s' % (filename, lineno, text)) - return complx + if len(complx) == 0: + return 0 + print('\n'.join(complx)) + return len(complx) def get_module_complexity(module_path, threshold=7): @@ -310,5 +310,3 @@ def main(argv): if __name__ == '__main__': main(sys.argv[1:]) - -# lint=0 diff --git a/pylibs/pylama/checkers/pep257.py b/pylibs/pylama/checkers/pep257.py index aab6a91f..a43dbae7 100644 --- a/pylibs/pylama/checkers/pep257.py +++ b/pylibs/pylama/checkers/pep257.py @@ -54,6 +54,7 @@ def your_check(class_docstring, context, is_script): Also, see examples in "Check functions" section. """ +__version__ = '0.2.4' from curses.ascii import isascii import inspect @@ -150,6 +151,23 @@ def rel_pos(abs_pos, source): return len(lines) + 1, abs_pos - len(''.join(lines)) +def get_summary_line_info(thedocstring): + """Get the (summary_line, line_number) tuple for the given docstring. + + The returned 'summary_line' is the pep257 summary line and 'line_number' is + the zero-based docstring line number containing the summary line, which + will be either 0 (zeroth line) or 1 (first line). Any docstring checks + relating to the summary line should use this method to ensure consistent + treatment of the summary line. + + """ + lines = eval(thedocstring).split('\n') + first_line = lines[0].strip() + if len(lines) == 1 or len(first_line) > 0: + return first_line, 0 + return lines[1].strip(), 1 + + # # Parsing # @@ -167,8 +185,11 @@ def parse_module_docstring(source): def parse_docstring(source, what=''): """Parse docstring given `def` or `class` source.""" + module_docstring = parse_module_docstring(source) if what.startswith('module'): - return parse_module_docstring(source) + return module_docstring + if module_docstring: + return module_docstring token_gen = tk.generate_tokens(StringIO(source).readline) try: kind = None @@ -249,7 +270,7 @@ def parse_contexts(source, kind): if kind == 'def_docstring': return parse_functions(source) + parse_methods(source) if kind == 'docstring': - return ([source] + parse_functions(source) + + return ([parse_module_docstring(source)] + parse_functions(source) + parse_classes(source) + parse_methods(source)) @@ -378,7 +399,7 @@ def check_files(filenames): def parse_options(): - parser = OptionParser() + parser = OptionParser(version=__version__) parser.add_option('-e', '--explain', action='store_true', help='show explanation of each error') parser.add_option('-r', '--range', action='store_true', @@ -418,6 +439,7 @@ def main(options, arguments): f.close() for error in sorted(errors): print_error(str(error)) + return 1 if errors else 0 # @@ -546,7 +568,10 @@ def check_ends_with_period(docstring, context, is_script): The [first line of a] docstring is a phrase ending in a period. """ - if docstring and not eval(docstring).split('\n')[0].strip().endswith('.'): + if not docstring: + return + (summary_line, line_number) = get_summary_line_info(docstring) + if not summary_line.endswith('.'): return True @@ -610,8 +635,10 @@ def check_blank_after_summary(docstring, context, is_script): if not docstring: return lines = eval(docstring).split('\n') - if len(lines) > 1 and lines[1].strip() != '': - return True + if len(lines) > 1: + (summary_line, line_number) = get_summary_line_info(docstring) + if len(lines) <= (line_number+1) or lines[line_number+1].strip() != '': + return True def check_indent(docstring, context, is_script): @@ -645,7 +672,7 @@ def check_blank_before_after_class(class_docstring, context, is_script): """ if not class_docstring: return - before, after = context.split(class_docstring) + before, after = context.split(class_docstring)[:2] before_blanks = [not line.strip() for line in before.split('\n')] after_blanks = [not line.strip() for line in after.split('\n')] if before_blanks[-3:] != [False, True, True]: @@ -671,6 +698,6 @@ def check_blank_after_last_paragraph(docstring, context, is_script): if __name__ == '__main__': try: - main(*parse_options()) + sys.exit(main(*parse_options())) except KeyboardInterrupt: pass diff --git a/pylibs/pylama/checkers/pep8.py b/pylibs/pylama/checkers/pep8.py index 8413270f..e0035b3d 100644 --- a/pylibs/pylama/checkers/pep8.py +++ b/pylibs/pylama/checkers/pep8.py @@ -45,7 +45,7 @@ 700 statements 900 syntax error """ -__version__ = '1.4.6' +__version__ = '1.4.7a0' import os import sys @@ -1678,6 +1678,9 @@ def ignore_code(self, code): return False. Else, if 'options.ignore' contains a prefix of the error code, return True. """ + if len(code) < 4 and any(s.startswith(code) + for s in self.options.select): + return False return (code.startswith(self.options.ignore) and not code.startswith(self.options.select)) diff --git a/pylibs/pylama/checkers/pylint/__init__.py b/pylibs/pylama/checkers/pylint/__init__.py index 697c8c05..dfb4386b 100644 --- a/pylibs/pylama/checkers/pylint/__init__.py +++ b/pylibs/pylama/checkers/pylint/__init__.py @@ -17,7 +17,7 @@ def run_pylint(): """run pylint""" - from .lint import Run + from pylint.lint import Run Run(sys.argv[1:]) def run_pylint_gui(): @@ -40,6 +40,5 @@ def run_pyreverse(): def run_symilar(): """run symilar""" - from .checkers.similar import Run + from pylint.checkers.similar import Run Run(sys.argv[1:]) - diff --git a/pylibs/pylama/checkers/pylint/__pkginfo__.py b/pylibs/pylama/checkers/pylint/__pkginfo__.py index a018f934..997b9a59 100644 --- a/pylibs/pylama/checkers/pylint/__pkginfo__.py +++ b/pylibs/pylama/checkers/pylint/__pkginfo__.py @@ -18,10 +18,10 @@ modname = distname = 'pylint' -numversion = (0, 28, 0) +numversion = (1, 0, 0) version = '.'.join([str(num) for num in numversion]) -install_requires = ['logilab-common >= 0.53.0', 'logilab-astng >= 0.24.3'] +install_requires = ['logilab-common >= 0.53.0', 'astroid >= 0.24.3'] license = 'GPL' description = "python code static checker" @@ -66,3 +66,4 @@ for filename in ('pylint', 'pylint-gui', "symilar", "epylint", "pyreverse")] +include_dirs = ['test'] diff --git a/pylibs/pylama/checkers/pylint/astroid/__init__.py b/pylibs/pylama/checkers/pylint/astroid/__init__.py new file mode 100644 index 00000000..af17875d --- /dev/null +++ b/pylibs/pylama/checkers/pylint/astroid/__init__.py @@ -0,0 +1,118 @@ +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid 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 Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see . +"""Python Abstract Syntax Tree New Generation + +The aim of this module is to provide a common base representation of +python source code for projects such as pychecker, pyreverse, +pylint... Well, actually the development of this library is essentially +governed by pylint's needs. + +It extends class defined in the python's _ast module with some +additional methods and attributes. Instance attributes are added by a +builder object, which can either generate extended ast (let's call +them astroid ;) by visiting an existent ast tree or by inspecting living +object. Methods are added by monkey patching ast classes. + +Main modules are: + +* nodes and scoped_nodes for more information about methods and + attributes added to different node classes + +* the manager contains a high level object to get astroid trees from + source files and living objects. It maintains a cache of previously + constructed tree for quick access + +* builder contains the class responsible to build astroid trees +""" +__doctype__ = "restructuredtext en" + +import sys +import re +from operator import attrgetter + +# WARNING: internal imports order matters ! + +# make all exception classes accessible from astroid package +from .exceptions import * + +# make all node classes accessible from astroid package +from .nodes import * + +# trigger extra monkey-patching +from . import inference + +# more stuff available +from . import raw_building +from .bases import YES, Instance, BoundMethod, UnboundMethod +from .node_classes import are_exclusive, unpack_infer +from .scoped_nodes import builtin_lookup + +# make a manager instance (borg) as well as Project and Package classes +# accessible from astroid package +from .manager import AstroidManager, Project +MANAGER = AstroidManager() +del AstroidManager + +# transform utilities (filters and decorator) + +class AsStringRegexpPredicate(object): + """Class to be used as predicate that may be given to `register_transform` + + First argument is a regular expression that will be searched against the `as_string` + representation of the node onto which it's applied. + + If specified, the second argument is an `attrgetter` expression that will be + applied on the node first to get the actual node on which `as_string` should + be called. + """ + def __init__(self, regexp, expression=None): + self.regexp = re.compile(regexp) + self.expression = expression + + def __call__(self, node): + if self.expression is not None: + node = attrgetter(self.expression)(node) + return self.regexp.search(node.as_string()) + +def inference_tip(infer_function): + """Given an instance specific inference function, return a function to be + given to MANAGER.register_transform to set this inference function. + + Typical usage + + .. sourcecode:: python + + MANAGER.register_transform(CallFunc, inference_tip(infer_named_tuple), + AsStringRegexpPredicate('namedtuple', 'func')) + """ + def transform(node, infer_function=infer_function): + node._explicit_inference = infer_function + return node + return transform + +# load brain plugins +# from os import listdir +# from os.path import join, dirname +# BRAIN_MODULES_DIR = join(dirname(__file__), 'brain') +# if BRAIN_MODULES_DIR not in sys.path: + # # add it to the end of the list so user path take precedence + # sys.path.append(BRAIN_MODULES_DIR) +# load modules in this directory +# for module in listdir(BRAIN_MODULES_DIR): + # if module.endswith('.py'): + # __import__(module[:-3]) diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/__pkginfo__.py b/pylibs/pylama/checkers/pylint/astroid/__pkginfo__.py similarity index 71% rename from pylibs/pylama/checkers/pylint/logilab/astng/__pkginfo__.py rename to pylibs/pylama/checkers/pylint/astroid/__pkginfo__.py index 31de45d0..a74b6b69 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/__pkginfo__.py +++ b/pylibs/pylama/checkers/pylint/astroid/__pkginfo__.py @@ -1,39 +1,37 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . -"""logilab.astng packaging information""" +# with astroid. If not, see . +"""astroid packaging information""" -distname = 'logilab-astng' +distname = 'astroid' -modname = 'astng' -subpackage_of = 'logilab' +modname = 'astroid' -numversion = (0, 24, 3) +numversion = (1, 0, 0) version = '.'.join([str(num) for num in numversion]) -install_requires = ['logilab-common >= 0.53.0'] +install_requires = ['logilab-common >= 0.60.0'] license = 'LGPL' author = 'Logilab' author_email = 'python-projects@lists.logilab.org' mailinglist = "mailto://%s" % author_email -web = "http://www.logilab.org/project/%s" % distname -ftp = "ftp://ftp.logilab.org/pub/%s" % modname +web = 'http://bitbucket.org/logilab/astroid' description = "rebuild a new abstract syntax tree from Python's ast" diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/as_string.py b/pylibs/pylama/checkers/pylint/astroid/as_string.py similarity index 78% rename from pylibs/pylama/checkers/pylint/logilab/astng/as_string.py rename to pylibs/pylama/checkers/pylint/astroid/as_string.py index c21144e5..fcff19eb 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/as_string.py +++ b/pylibs/pylama/checkers/pylint/astroid/as_string.py @@ -1,21 +1,21 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . -"""This module renders ASTNG nodes as string: +# with astroid. If not, see . +"""This module renders Astroid nodes as string: * :func:`to_code` function return equivalent (hopefuly valid) python string @@ -29,7 +29,7 @@ def dump(node, ids=False): - """print a nice astng tree representation. + """print a nice astroid tree representation. :param ids: if true, we also print the ids (usefull for debugging) """ @@ -41,7 +41,7 @@ def _repr_tree(node, result, indent='', _done=None, ids=False): """built a tree representation of a node as a list of lines""" if _done is None: _done = set() - if not hasattr(node, '_astng_fields'): # not a astng node + if not hasattr(node, '_astroid_fields'): # not a astroid node return if node in _done: result.append( indent + 'loop in tree: %s' % node ) @@ -52,7 +52,7 @@ def _repr_tree(node, result, indent='', _done=None, ids=False): node_str += ' . \t%x' % id(node) result.append( indent + node_str ) indent += INDENT - for field in node._astng_fields: + for field in node._astroid_fields: value = getattr(node, field) if isinstance(value, (list, tuple) ): result.append( indent + field + " = [" ) @@ -71,7 +71,7 @@ def _repr_tree(node, result, indent='', _done=None, ids=False): class AsStringVisitor(object): - """Visitor to render an ASTNG node as a valid python code string""" + """Visitor to render an Astroid node as a valid python code string""" def __call__(self, node): """Makes this visitor behave as a simple function""" @@ -86,52 +86,52 @@ def _stmt_list(self, stmts): ## visit_ methods ########################################### def visit_arguments(self, node): - """return an astng.Function node as string""" + """return an astroid.Function node as string""" return node.format_args() def visit_assattr(self, node): - """return an astng.AssAttr node as string""" + """return an astroid.AssAttr node as string""" return self.visit_getattr(node) def visit_assert(self, node): - """return an astng.Assert node as string""" + """return an astroid.Assert node as string""" if node.fail: return 'assert %s, %s' % (node.test.accept(self), node.fail.accept(self)) return 'assert %s' % node.test.accept(self) def visit_assname(self, node): - """return an astng.AssName node as string""" + """return an astroid.AssName node as string""" return node.name def visit_assign(self, node): - """return an astng.Assign node as string""" + """return an astroid.Assign node as string""" lhs = ' = '.join([n.accept(self) for n in node.targets]) return '%s = %s' % (lhs, node.value.accept(self)) def visit_augassign(self, node): - """return an astng.AugAssign node as string""" + """return an astroid.AugAssign node as string""" return '%s %s %s' % (node.target.accept(self), node.op, node.value.accept(self)) def visit_backquote(self, node): - """return an astng.Backquote node as string""" + """return an astroid.Backquote node as string""" return '`%s`' % node.value.accept(self) def visit_binop(self, node): - """return an astng.BinOp node as string""" + """return an astroid.BinOp node as string""" return '(%s) %s (%s)' % (node.left.accept(self), node.op, node.right.accept(self)) def visit_boolop(self, node): - """return an astng.BoolOp node as string""" + """return an astroid.BoolOp node as string""" return (' %s ' % node.op).join(['(%s)' % n.accept(self) for n in node.values]) def visit_break(self, node): - """return an astng.Break node as string""" + """return an astroid.Break node as string""" return 'break' def visit_callfunc(self, node): - """return an astng.CallFunc node as string""" + """return an astroid.CallFunc node as string""" expr_str = node.func.accept(self) args = [arg.accept(self) for arg in node.args] if node.starargs: @@ -141,7 +141,7 @@ def visit_callfunc(self, node): return '%s(%s)' % (expr_str, ', '.join(args)) def visit_class(self, node): - """return an astng.Class node as string""" + """return an astroid.Class node as string""" decorate = node.decorators and node.decorators.accept(self) or '' bases = ', '.join([n.accept(self) for n in node.bases]) bases = bases and '(%s)' % bases or '' @@ -150,54 +150,54 @@ def visit_class(self, node): self._stmt_list( node.body)) def visit_compare(self, node): - """return an astng.Compare node as string""" + """return an astroid.Compare node as string""" rhs_str = ' '.join(['%s %s' % (op, expr.accept(self)) for op, expr in node.ops]) return '%s %s' % (node.left.accept(self), rhs_str) def visit_comprehension(self, node): - """return an astng.Comprehension node as string""" + """return an astroid.Comprehension node as string""" ifs = ''.join([ ' if %s' % n.accept(self) for n in node.ifs]) return 'for %s in %s%s' % (node.target.accept(self), node.iter.accept(self), ifs ) def visit_const(self, node): - """return an astng.Const node as string""" + """return an astroid.Const node as string""" return repr(node.value) def visit_continue(self, node): - """return an astng.Continue node as string""" + """return an astroid.Continue node as string""" return 'continue' def visit_delete(self, node): # XXX check if correct - """return an astng.Delete node as string""" + """return an astroid.Delete node as string""" return 'del %s' % ', '.join([child.accept(self) for child in node.targets]) def visit_delattr(self, node): - """return an astng.DelAttr node as string""" + """return an astroid.DelAttr node as string""" return self.visit_getattr(node) def visit_delname(self, node): - """return an astng.DelName node as string""" + """return an astroid.DelName node as string""" return node.name def visit_decorators(self, node): - """return an astng.Decorators node as string""" + """return an astroid.Decorators node as string""" return '@%s\n' % '\n@'.join([item.accept(self) for item in node.nodes]) def visit_dict(self, node): - """return an astng.Dict node as string""" + """return an astroid.Dict node as string""" return '{%s}' % ', '.join(['%s: %s' % (key.accept(self), value.accept(self)) for key, value in node.items]) def visit_dictcomp(self, node): - """return an astng.DictComp node as string""" + """return an astroid.DictComp node as string""" return '{%s: %s %s}' % (node.key.accept(self), node.value.accept(self), ' '.join([n.accept(self) for n in node.generators])) def visit_discard(self, node): - """return an astng.Discard node as string""" + """return an astroid.Discard node as string""" return node.value.accept(self) def visit_emptynode(self, node): @@ -216,7 +216,7 @@ def visit_excepthandler(self, node): return '%s:\n%s' % (excs, self._stmt_list(node.body)) def visit_ellipsis(self, node): - """return an astng.Ellipsis node as string""" + """return an astroid.Ellipsis node as string""" return '...' def visit_empty(self, node): @@ -224,7 +224,7 @@ def visit_empty(self, node): return '' def visit_exec(self, node): - """return an astng.Exec node as string""" + """return an astroid.Exec node as string""" if node.locals: return 'exec %s in %s, %s' % (node.expr.accept(self), node.locals.accept(self), @@ -235,11 +235,11 @@ def visit_exec(self, node): return 'exec %s' % node.expr.accept(self) def visit_extslice(self, node): - """return an astng.ExtSlice node as string""" + """return an astroid.ExtSlice node as string""" return ','.join( [dim.accept(self) for dim in node.dims] ) def visit_for(self, node): - """return an astng.For node as string""" + """return an astroid.For node as string""" fors = 'for %s in %s:\n%s' % (node.target.accept(self), node.iter.accept(self), self._stmt_list( node.body)) @@ -248,78 +248,78 @@ def visit_for(self, node): return fors def visit_from(self, node): - """return an astng.From node as string""" + """return an astroid.From node as string""" return 'from %s import %s' % ('.' * (node.level or 0) + node.modname, _import_string(node.names)) def visit_function(self, node): - """return an astng.Function node as string""" + """return an astroid.Function node as string""" decorate = node.decorators and node.decorators.accept(self) or '' docs = node.doc and '\n%s"""%s"""' % (INDENT, node.doc) or '' return '\n%sdef %s(%s):%s\n%s' % (decorate, node.name, node.args.accept(self), docs, self._stmt_list(node.body)) def visit_genexpr(self, node): - """return an astng.GenExpr node as string""" + """return an astroid.GenExpr node as string""" return '(%s %s)' % (node.elt.accept(self), ' '.join([n.accept(self) for n in node.generators])) def visit_getattr(self, node): - """return an astng.Getattr node as string""" + """return an astroid.Getattr node as string""" return '%s.%s' % (node.expr.accept(self), node.attrname) def visit_global(self, node): - """return an astng.Global node as string""" + """return an astroid.Global node as string""" return 'global %s' % ', '.join(node.names) def visit_if(self, node): - """return an astng.If node as string""" + """return an astroid.If node as string""" ifs = ['if %s:\n%s' % (node.test.accept(self), self._stmt_list(node.body))] if node.orelse:# XXX use elif ??? ifs.append('else:\n%s' % self._stmt_list(node.orelse)) return '\n'.join(ifs) def visit_ifexp(self, node): - """return an astng.IfExp node as string""" + """return an astroid.IfExp node as string""" return '%s if %s else %s' % (node.body.accept(self), node.test.accept(self), node.orelse.accept(self)) def visit_import(self, node): - """return an astng.Import node as string""" + """return an astroid.Import node as string""" return 'import %s' % _import_string(node.names) def visit_keyword(self, node): - """return an astng.Keyword node as string""" + """return an astroid.Keyword node as string""" return '%s=%s' % (node.arg, node.value.accept(self)) def visit_lambda(self, node): - """return an astng.Lambda node as string""" + """return an astroid.Lambda node as string""" return 'lambda %s: %s' % (node.args.accept(self), node.body.accept(self)) def visit_list(self, node): - """return an astng.List node as string""" + """return an astroid.List node as string""" return '[%s]' % ', '.join([child.accept(self) for child in node.elts]) def visit_listcomp(self, node): - """return an astng.ListComp node as string""" + """return an astroid.ListComp node as string""" return '[%s %s]' % (node.elt.accept(self), ' '.join([n.accept(self) for n in node.generators])) def visit_module(self, node): - """return an astng.Module node as string""" + """return an astroid.Module node as string""" docs = node.doc and '"""%s"""\n\n' % node.doc or '' return docs + '\n'.join([n.accept(self) for n in node.body]) + '\n\n' def visit_name(self, node): - """return an astng.Name node as string""" + """return an astroid.Name node as string""" return node.name def visit_pass(self, node): - """return an astng.Pass node as string""" + """return an astroid.Pass node as string""" return 'pass' def visit_print(self, node): - """return an astng.Print node as string""" + """return an astroid.Print node as string""" nodes = ', '.join([n.accept(self) for n in node.values]) if not node.nl: nodes = '%s,' % nodes @@ -328,7 +328,7 @@ def visit_print(self, node): return 'print %s' % nodes def visit_raise(self, node): - """return an astng.Raise node as string""" + """return an astroid.Raise node as string""" if node.exc: if node.inst: if node.tback: @@ -341,27 +341,27 @@ def visit_raise(self, node): return 'raise' def visit_return(self, node): - """return an astng.Return node as string""" + """return an astroid.Return node as string""" if node.value: return 'return %s' % node.value.accept(self) else: return 'return' def visit_index(self, node): - """return a astng.Index node as string""" + """return a astroid.Index node as string""" return node.value.accept(self) def visit_set(self, node): - """return an astng.Set node as string""" + """return an astroid.Set node as string""" return '{%s}' % ', '.join([child.accept(self) for child in node.elts]) def visit_setcomp(self, node): - """return an astng.SetComp node as string""" + """return an astroid.SetComp node as string""" return '{%s %s}' % (node.elt.accept(self), ' '.join([n.accept(self) for n in node.generators])) def visit_slice(self, node): - """return a astng.Slice node as string""" + """return a astroid.Slice node as string""" lower = node.lower and node.lower.accept(self) or '' upper = node.upper and node.upper.accept(self) or '' step = node.step and node.step.accept(self) or '' @@ -370,11 +370,11 @@ def visit_slice(self, node): return '%s:%s' % (lower, upper) def visit_subscript(self, node): - """return an astng.Subscript node as string""" + """return an astroid.Subscript node as string""" return '%s[%s]' % (node.value.accept(self), node.slice.accept(self)) def visit_tryexcept(self, node): - """return an astng.TryExcept node as string""" + """return an astroid.TryExcept node as string""" trys = ['try:\n%s' % self._stmt_list( node.body)] for handler in node.handlers: trys.append(handler.accept(self)) @@ -383,16 +383,16 @@ def visit_tryexcept(self, node): return '\n'.join(trys) def visit_tryfinally(self, node): - """return an astng.TryFinally node as string""" + """return an astroid.TryFinally node as string""" return 'try:\n%s\nfinally:\n%s' % (self._stmt_list( node.body), self._stmt_list(node.finalbody)) def visit_tuple(self, node): - """return an astng.Tuple node as string""" + """return an astroid.Tuple node as string""" return '(%s)' % ', '.join([child.accept(self) for child in node.elts]) def visit_unaryop(self, node): - """return an astng.UnaryOp node as string""" + """return an astroid.UnaryOp node as string""" if node.op == 'not': operator = 'not ' else: @@ -400,7 +400,7 @@ def visit_unaryop(self, node): return '%s%s' % (operator, node.operand.accept(self)) def visit_while(self, node): - """return an astng.While node as string""" + """return an astroid.While node as string""" whiles = 'while %s:\n%s' % (node.test.accept(self), self._stmt_list(node.body)) if node.orelse: @@ -408,11 +408,11 @@ def visit_while(self, node): return whiles def visit_with(self, node): # 'with' without 'as' is possible - """return an astng.With node as string""" - as_var = node.vars and " as (%s)" % (node.vars.accept(self)) or "" - withs = 'with (%s)%s:\n%s' % (node.expr.accept(self), as_var, - self._stmt_list( node.body)) - return withs + """return an astroid.With node as string""" + items = ', '.join(('(%s)' % expr.accept(self)) + + (vars and ' as (%s)' % (vars.accept(self)) or '') + for expr, vars in node.items) + return 'with %s:\n%s' % (items, self._stmt_list( node.body)) def visit_yield(self, node): """yield an ast.Yield node as string""" @@ -439,11 +439,11 @@ def visit_excepthandler(self, node): return '%s:\n%s' % (excs, self._stmt_list(node.body)) def visit_nonlocal(self, node): - """return an astng.Nonlocal node as string""" + """return an astroid.Nonlocal node as string""" return 'nonlocal %s' % ', '.join(node.names) def visit_raise(self, node): - """return an astng.Raise node as string""" + """return an astroid.Raise node as string""" if node.exc: if node.cause: return 'raise %s from %s' % (node.exc.accept(self), diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/bases.py b/pylibs/pylama/checkers/pylint/astroid/bases.py similarity index 92% rename from pylibs/pylama/checkers/pylint/logilab/astng/bases.py rename to pylibs/pylama/checkers/pylint/astroid/bases.py index 6331dc8e..641f88ae 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/bases.py +++ b/pylibs/pylama/checkers/pylint/astroid/bases.py @@ -1,20 +1,20 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . +# with astroid. If not, see . """This module contains base classes and functions for the nodes and some inference utils. """ @@ -24,8 +24,8 @@ import sys from contextlib import contextmanager -from .exceptions import (InferenceError, ASTNGError, - NotFoundError, UnresolvableName) +from .exceptions import (InferenceError, AstroidError, NotFoundError, + UnresolvableName, UseInferenceDefault) if sys.version_info >= (3, 0): @@ -339,7 +339,7 @@ def wrapper(*args, **kwargs): # Node ###################################################################### class NodeNG(object): - """Base Class for all ASTNG node classes. + """Base Class for all Astroid node classes. It represents a node of the new abstract syntax tree. """ @@ -354,7 +354,24 @@ class NodeNG(object): # parent node in the tree parent = None # attributes containing child node(s) redefined in most concrete classes: - _astng_fields = () + _astroid_fields = () + # instance specific inference function infer(node, context) + _explicit_inference = None + + def infer(self, context=None, **kwargs): + """main interface to the interface system, return a generator on infered + values. + + If the instance has some explicit inference function set, it will be + called instead of the default interface. + """ + if self._explicit_inference is not None: + # explicit_inference is not bound, give it self explicitly + try: + return self._explicit_inference(self, context, **kwargs) + except UseInferenceDefault: + pass + return self._infer(context, **kwargs) def _repr_name(self): """return self.name or self.attrname or '' for nice representation""" @@ -377,7 +394,7 @@ def accept(self, visitor): return func(self) def get_children(self): - for field in self._astng_fields: + for field in self._astroid_fields: attr = getattr(self, field) if attr is None: continue @@ -389,7 +406,7 @@ def get_children(self): def last_child(self): """an optimized version of list(get_children())[-1]""" - for field in self._astng_fields[::-1]: + for field in self._astroid_fields[::-1]: attr = getattr(self, field) if not attr: # None or empty listy / tuple continue @@ -433,7 +450,7 @@ def root(self): def child_sequence(self, child): """search for the right sequence where the child lies in""" - for field in self._astng_fields: + for field in self._astroid_fields: node_or_sequence = getattr(self, field) if node_or_sequence is child: return [node_or_sequence] @@ -441,20 +458,20 @@ def child_sequence(self, child): if isinstance(node_or_sequence, (tuple, list)) and child in node_or_sequence: return node_or_sequence else: - msg = 'Could not found %s in %s\'s children' - raise ASTNGError(msg % (repr(child), repr(self))) + msg = 'Could not find %s in %s\'s children' + raise AstroidError(msg % (repr(child), repr(self))) def locate_child(self, child): """return a 2-uple (child attribute name, sequence or node)""" - for field in self._astng_fields: + for field in self._astroid_fields: node_or_sequence = getattr(self, field) # /!\ compiler.ast Nodes have an __iter__ walking over child nodes if child is node_or_sequence: return field, child if isinstance(node_or_sequence, (tuple, list)) and child in node_or_sequence: return field, node_or_sequence - msg = 'Could not found %s in %s\'s children' - raise ASTNGError(msg % (repr(child), repr(self))) + msg = 'Could not find %s in %s\'s children' + raise AstroidError(msg % (repr(child), repr(self))) # FIXME : should we merge child_sequence and locate_child ? locate_child # is only used in are_exclusive, child_sequence one time in pylint. @@ -543,7 +560,7 @@ def _infer_name(self, frame, name): # overridden for From, Import, Global, TryExcept and Arguments return None - def infer(self, context=None): + def _infer(self, context=None): """we don't know how to resolve a statement by default""" # this method is overridden by most concrete classes raise InferenceError(self.__class__.__name__) diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/builder.py b/pylibs/pylama/checkers/pylint/astroid/builder.py similarity index 83% rename from pylibs/pylama/checkers/pylint/logilab/astng/builder.py rename to pylibs/pylama/checkers/pylint/astroid/builder.py index ae0c7d01..6a4347af 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/builder.py +++ b/pylibs/pylama/checkers/pylint/astroid/builder.py @@ -1,21 +1,21 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . -"""The ASTNGBuilder makes astng from living object and / or from _ast +# with astroid. If not, see . +"""The AstroidBuilder makes astroid from living object and / or from _ast The builder is not thread safe and can't be used to parse different sources at the same time. @@ -26,12 +26,12 @@ import sys from os.path import splitext, basename, exists, abspath -from ..common.modutils import modpath_from_file +from ..logilab.common.modutils import modpath_from_file -from .exceptions import ASTNGBuildingException, InferenceError +from .exceptions import AstroidBuildingException, InferenceError from .raw_building import InspectBuilder from .rebuilder import TreeRebuilder -from .manager import ASTNGManager +from .manager import AstroidManager from .bases import YES, Instance from _ast import PyCF_ONLY_AST @@ -50,7 +50,7 @@ def open_source_file(filename): except UnicodeError, uex: # wrong encodingg # detect_encoding returns utf-8 if no encoding specified msg = 'Wrong (%s) or no encoding specified' % encoding - raise ASTNGBuildingException(msg) + raise AstroidBuildingException(msg) return stream, encoding, data else: @@ -79,18 +79,17 @@ def open_source_file(filename): # ast NG builder ############################################################## -MANAGER = ASTNGManager() +MANAGER = AstroidManager() -class ASTNGBuilder(InspectBuilder): - """provide astng building methods""" - rebuilder = TreeRebuilder() +class AstroidBuilder(InspectBuilder): + """provide astroid building methods""" def __init__(self, manager=None): InspectBuilder.__init__(self) self._manager = manager or MANAGER def module_build(self, module, modname=None): - """build an astng from a living module instance + """build an astroid from a living module instance """ node = None path = getattr(module, '__file__', None) @@ -105,7 +104,7 @@ def module_build(self, module, modname=None): return node def file_build(self, path, modname=None): - """build astng from a source code file (i.e. from an ast) + """build astroid from a source code file (i.e. from an ast) path is expected to be a python source file """ @@ -113,35 +112,32 @@ def file_build(self, path, modname=None): stream, encoding, data = open_source_file(path) except IOError, exc: msg = 'Unable to load file %r (%s)' % (path, exc) - raise ASTNGBuildingException(msg) + raise AstroidBuildingException(msg) except SyntaxError, exc: # py3k encoding specification error - raise ASTNGBuildingException(exc) + raise AstroidBuildingException(exc) except LookupError, exc: # unknown encoding - raise ASTNGBuildingException(exc) + raise AstroidBuildingException(exc) # get module name if necessary if modname is None: try: modname = '.'.join(modpath_from_file(path)) except ImportError: modname = splitext(basename(path))[0] - # build astng representation + # build astroid representation node = self.string_build(data, modname, path) node.file_encoding = encoding return node def string_build(self, data, modname='', path=None): - """build astng from source code string and return rebuilded astng""" + """build astroid from source code string and return rebuilded astroid""" module = self._data_build(data, modname, path) - self._manager.astng_cache[module.name] = module + self._manager.astroid_cache[module.name] = module # post tree building steps after we stored the module in the cache: for from_node in module._from_nodes: self.add_from_names_to_locals(from_node) # handle delayed assattr nodes for delayed in module._delayed_assattr: self.delayed_assattr(delayed) - if modname: - for transformer in self._manager.transformers: - transformer(module) return module def _data_build(self, data, modname, path): @@ -157,11 +153,11 @@ def _data_build(self, data, modname, path): package = True else: package = path and path.find('__init__.py') > -1 or False - self.rebuilder.init() - module = self.rebuilder.visit_module(node, modname, package) + rebuilder = TreeRebuilder(self._manager) + module = rebuilder.visit_module(node, modname, package) module.file = module.path = node_file - module._from_nodes = self.rebuilder._from_nodes - module._delayed_assattr = self.rebuilder._delayed_assattr + module._from_nodes = rebuilder._from_nodes + module._delayed_assattr = rebuilder._delayed_assattr return module def add_from_names_to_locals(self, node): @@ -176,7 +172,7 @@ def sort_locals(my_list): if name == '*': try: imported = node.root().import_module(node.modname) - except ASTNGBuildingException: + except AstroidBuildingException: continue for name in imported.wildcard_import_names(): node.parent.set_local(name, node) diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/exceptions.py b/pylibs/pylama/checkers/pylint/astroid/exceptions.py similarity index 55% rename from pylibs/pylama/checkers/pylint/logilab/astng/exceptions.py rename to pylibs/pylama/checkers/pylint/astroid/exceptions.py index db33f8b9..3889e2e7 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/exceptions.py +++ b/pylibs/pylama/checkers/pylint/astroid/exceptions.py @@ -1,34 +1,34 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . -"""this module contains exceptions used in the astng library +# with astroid. If not, see . +"""this module contains exceptions used in the astroid library """ __doctype__ = "restructuredtext en" -class ASTNGError(Exception): - """base exception class for all astng related exceptions""" +class AstroidError(Exception): + """base exception class for all astroid related exceptions""" -class ASTNGBuildingException(ASTNGError): - """exception class when we are unable to build an astng representation""" +class AstroidBuildingException(AstroidError): + """exception class when we are unable to build an astroid representation""" -class ResolveError(ASTNGError): - """base class of astng resolution/inference error""" +class ResolveError(AstroidError): + """base class of astroid resolution/inference error""" class NotFoundError(ResolveError): """raised when we are unable to resolve a name""" @@ -36,10 +36,15 @@ class NotFoundError(ResolveError): class InferenceError(ResolveError): """raised when we are unable to infer a node""" +class UseInferenceDefault(Exception): + """exception to be raised in custom inference function to indicate that it + should go back to the default behaviour + """ + class UnresolvableName(InferenceError): """raised when we are unable to resolve a name""" -class NoDefault(ASTNGError): +class NoDefault(AstroidError): """raised by function's `default_value` method when an argument has no default value """ diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/inference.py b/pylibs/pylama/checkers/pylint/astroid/inference.py similarity index 88% rename from pylibs/pylama/checkers/pylint/logilab/astng/inference.py rename to pylibs/pylama/checkers/pylint/astroid/inference.py index fda25cf8..f600781c 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/inference.py +++ b/pylibs/pylama/checkers/pylint/astroid/inference.py @@ -1,21 +1,21 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . -"""this module contains a set of functions to handle inference on astng trees +# with astroid. If not, see . +"""this module contains a set of functions to handle inference on astroid trees """ __doctype__ = "restructuredtext en" @@ -24,14 +24,14 @@ from . import nodes -from .manager import ASTNGManager -from .exceptions import (ASTNGError, +from .manager import AstroidManager +from .exceptions import (AstroidError, InferenceError, NoDefault, NotFoundError, UnresolvableName) from .bases import YES, Instance, InferenceContext, \ _infer_stmts, copy_context, path_wrapper, raise_if_nothing_infered from .protocols import _arguments_infer_argname -MANAGER = ASTNGManager() +MANAGER = AstroidManager() class CallContext: @@ -55,7 +55,7 @@ def infer_argument(self, funcnode, name, context): try: return self.nargs[name].infer(context) except KeyError: - # Function.args.args can be None in astng (means that we don't have + # Function.args.args can be None in astroid (means that we don't have # information on argnames) argindex = funcnode.args.find_argname(name)[0] if argindex is not None: @@ -126,15 +126,15 @@ def infer_end(self, context=None): """inference's end for node such as Module, Class, Function, Const... """ yield self -nodes.Module.infer = infer_end -nodes.Class.infer = infer_end -nodes.Function.infer = infer_end -nodes.Lambda.infer = infer_end -nodes.Const.infer = infer_end -nodes.List.infer = infer_end -nodes.Tuple.infer = infer_end -nodes.Dict.infer = infer_end -nodes.Set.infer = infer_end +nodes.Module._infer = infer_end +nodes.Class._infer = infer_end +nodes.Function._infer = infer_end +nodes.Lambda._infer = infer_end +nodes.Const._infer = infer_end +nodes.List._infer = infer_end +nodes.Tuple._infer = infer_end +nodes.Dict._infer = infer_end +nodes.Set._infer = infer_end def infer_name(self, context=None): """infer a Name: use name lookup rules""" @@ -144,7 +144,7 @@ def infer_name(self, context=None): context = context.clone() context.lookupname = self.name return _infer_stmts(stmts, context, frame) -nodes.Name.infer = path_wrapper(infer_name) +nodes.Name._infer = path_wrapper(infer_name) nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper @@ -164,7 +164,7 @@ def infer_callfunc(self, context=None): except InferenceError: ## XXX log error ? continue -nodes.CallFunc.infer = path_wrapper(raise_if_nothing_infered(infer_callfunc)) +nodes.CallFunc._infer = path_wrapper(raise_if_nothing_infered(infer_callfunc)) def infer_import(self, context=None, asname=True): @@ -176,7 +176,7 @@ def infer_import(self, context=None, asname=True): yield self.do_import_module(self.real_name(name)) else: yield self.do_import_module(name) -nodes.Import.infer = path_wrapper(infer_import) +nodes.Import._infer = path_wrapper(infer_import) def infer_name_module(self, name): context = InferenceContext() @@ -199,7 +199,7 @@ def infer_from(self, context=None, asname=True): return _infer_stmts(module.getattr(name, ignore_locals=module is self.root()), context) except NotFoundError: raise InferenceError(name) -nodes.From.infer = path_wrapper(infer_from) +nodes.From._infer = path_wrapper(infer_from) def infer_getattr(self, context=None): @@ -219,7 +219,7 @@ def infer_getattr(self, context=None): except AttributeError: # XXX method / function context.boundnode = None -nodes.Getattr.infer = path_wrapper(raise_if_nothing_infered(infer_getattr)) +nodes.Getattr._infer = path_wrapper(raise_if_nothing_infered(infer_getattr)) nodes.AssAttr.infer_lhs = raise_if_nothing_infered(infer_getattr) # # won't work with a path wrapper @@ -230,7 +230,7 @@ def infer_global(self, context=None): return _infer_stmts(self.root().getattr(context.lookupname), context) except NotFoundError: raise InferenceError() -nodes.Global.infer = path_wrapper(infer_global) +nodes.Global._infer = path_wrapper(infer_global) def infer_subscript(self, context=None): @@ -257,7 +257,7 @@ def infer_subscript(self, context=None): yield infered else: raise InferenceError() -nodes.Subscript.infer = path_wrapper(infer_subscript) +nodes.Subscript._infer = path_wrapper(infer_subscript) nodes.Subscript.infer_lhs = raise_if_nothing_infered(infer_subscript) @@ -287,7 +287,7 @@ def infer_unaryop(self, context=None): raise except: yield YES -nodes.UnaryOp.infer = path_wrapper(infer_unaryop) +nodes.UnaryOp._infer = path_wrapper(infer_unaryop) BIN_OP_METHOD = {'+': '__add__', @@ -332,7 +332,7 @@ def infer_binop(self, context=None): for rhs in self.right.infer(context): for val in _infer_binop(self.op, rhs, lhs, context): yield val -nodes.BinOp.infer = path_wrapper(infer_binop) +nodes.BinOp._infer = path_wrapper(infer_binop) def infer_arguments(self, context=None): @@ -340,7 +340,7 @@ def infer_arguments(self, context=None): if name is None: raise InferenceError() return _arguments_infer_argname(self, name, context) -nodes.Arguments.infer = infer_arguments +nodes.Arguments._infer = infer_arguments def infer_ass(self, context=None): @@ -352,8 +352,8 @@ def infer_ass(self, context=None): return stmt.infer(context) stmts = list(self.assigned_stmts(context=context)) return _infer_stmts(stmts, context) -nodes.AssName.infer = path_wrapper(infer_ass) -nodes.AssAttr.infer = path_wrapper(infer_ass) +nodes.AssName._infer = path_wrapper(infer_ass) +nodes.AssAttr._infer = path_wrapper(infer_ass) def infer_augassign(self, context=None): failures = [] @@ -364,7 +364,7 @@ def infer_augassign(self, context=None): for rhs in self.value.infer(context): for val in _infer_binop(self.op, rhs, lhs, context): yield val -nodes.AugAssign.infer = path_wrapper(infer_augassign) +nodes.AugAssign._infer = path_wrapper(infer_augassign) # no infer method on DelName and DelAttr (expected InferenceError) @@ -375,14 +375,14 @@ def infer_empty_node(self, context=None): yield YES else: try: - for infered in MANAGER.infer_astng_from_something(self.object, + for infered in MANAGER.infer_ast_from_something(self.object, context=context): yield infered - except ASTNGError: + except AstroidError: yield YES -nodes.EmptyNode.infer = path_wrapper(infer_empty_node) +nodes.EmptyNode._infer = path_wrapper(infer_empty_node) def infer_index(self, context=None): return self.value.infer(context) -nodes.Index.infer = infer_index +nodes.Index._infer = infer_index diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/manager.py b/pylibs/pylama/checkers/pylint/astroid/manager.py similarity index 63% rename from pylibs/pylama/checkers/pylint/logilab/astng/manager.py rename to pylibs/pylama/checkers/pylint/astroid/manager.py index d090f80b..3ff2e14c 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/manager.py +++ b/pylibs/pylama/checkers/pylint/astroid/manager.py @@ -1,22 +1,22 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . -"""astng manager: avoid multiple astng build of a same module when -possible by providing a class responsible to get astng representation +# with astroid. If not, see . +"""astroid manager: avoid multiple astroid build of a same module when +possible by providing a class responsible to get astroid representation from various source and using a cache of built modules) """ @@ -25,19 +25,19 @@ import os from os.path import dirname, join, isdir, exists -from ..common.modutils import NoSourceFile, is_python_source, \ +from ..logilab.common.modutils import NoSourceFile, is_python_source, \ file_from_modpath, load_module_from_name, modpath_from_file, \ get_module_files, get_source_file, zipimport -from ..common.configuration import OptionsProviderMixIn +from ..logilab.common.configuration import OptionsProviderMixIn -from .exceptions import ASTNGBuildingException +from .exceptions import AstroidBuildingException -def astng_wrapper(func, modname): - """wrapper to give to ASTNGManager.project_from_files""" +def astroid_wrapper(func, modname): + """wrapper to give to AstroidManager.project_from_files""" print 'parsing %s...' % modname try: return func(modname) - except ASTNGBuildingException, exc: + except AstroidBuildingException, exc: print exc except Exception, exc: import traceback @@ -55,14 +55,14 @@ def safe_repr(obj): -class ASTNGManager(OptionsProviderMixIn): - """the astng manager, responsible to build astng from files +class AstroidManager(OptionsProviderMixIn): + """the astroid manager, responsible to build astroid from files or modules. Use the Borg pattern. """ - name = 'astng loader' + name = 'astroid loader' options = (("ignore", {'type' : "csv", 'metavar' : "", 'dest' : "black_list", "default" : ('CVS',), @@ -76,17 +76,17 @@ class ASTNGManager(OptionsProviderMixIn): ) brain = {} def __init__(self): - self.__dict__ = ASTNGManager.brain + self.__dict__ = AstroidManager.brain if not self.__dict__: OptionsProviderMixIn.__init__(self) self.load_defaults() # NOTE: cache entries are added by the [re]builder - self.astng_cache = {} + self.astroid_cache = {} self._mod_file_cache = {} - self.transformers = [] + self.transforms = {} - def astng_from_file(self, filepath, modname=None, fallback=True, source=False): - """given a module name, return the astng object""" + def ast_from_file(self, filepath, modname=None, fallback=True, source=False): + """given a module name, return the astroid object""" try: filepath = get_source_file(filepath, include_no_ext=True) source = True @@ -97,23 +97,23 @@ def astng_from_file(self, filepath, modname=None, fallback=True, source=False): modname = '.'.join(modpath_from_file(filepath)) except ImportError: modname = filepath - if modname in self.astng_cache: - return self.astng_cache[modname] + if modname in self.astroid_cache: + return self.astroid_cache[modname] if source: - from .builder import ASTNGBuilder - return ASTNGBuilder(self).file_build(filepath, modname) + from .builder import AstroidBuilder + return AstroidBuilder(self).file_build(filepath, modname) elif fallback and modname: - return self.astng_from_module_name(modname) - raise ASTNGBuildingException('unable to get astng for file %s' % + return self.ast_from_module_name(modname) + raise AstroidBuildingException('unable to get astroid for file %s' % filepath) - def astng_from_module_name(self, modname, context_file=None): - """given a module name, return the astng object""" - if modname in self.astng_cache: - return self.astng_cache[modname] + def ast_from_module_name(self, modname, context_file=None): + """given a module name, return the astroid object""" + if modname in self.astroid_cache: + return self.astroid_cache[modname] if modname == '__main__': - from .builder import ASTNGBuilder - return ASTNGBuilder(self).string_build('', modname) + from .builder import AstroidBuilder + return AstroidBuilder(self).string_build('', modname) old_cwd = os.getcwd() if context_file: os.chdir(dirname(context_file)) @@ -128,17 +128,17 @@ def astng_from_module_name(self, modname, context_file=None): module = load_module_from_name(modname) except Exception, ex: msg = 'Unable to load module %s (%s)' % (modname, ex) - raise ASTNGBuildingException(msg) - return self.astng_from_module(module, modname) - return self.astng_from_file(filepath, modname, fallback=False) + raise AstroidBuildingException(msg) + return self.ast_from_module(module, modname) + return self.ast_from_file(filepath, modname, fallback=False) finally: os.chdir(old_cwd) def zip_import_data(self, filepath): if zipimport is None: return None - from .builder import ASTNGBuilder - builder = ASTNGBuilder(self) + from .builder import AstroidBuilder + builder = AstroidBuilder(self) for ext in ('.zip', '.egg'): try: eggpath, resource = filepath.rsplit(ext + '/', 1) @@ -165,41 +165,41 @@ def file_from_module_name(self, modname, contextfile): context_file=contextfile) except ImportError, ex: msg = 'Unable to load module %s (%s)' % (modname, ex) - value = ASTNGBuildingException(msg) + value = AstroidBuildingException(msg) self._mod_file_cache[(modname, contextfile)] = value - if isinstance(value, ASTNGBuildingException): + if isinstance(value, AstroidBuildingException): raise value return value - def astng_from_module(self, module, modname=None): - """given an imported module, return the astng object""" + def ast_from_module(self, module, modname=None): + """given an imported module, return the astroid object""" modname = modname or module.__name__ - if modname in self.astng_cache: - return self.astng_cache[modname] + if modname in self.astroid_cache: + return self.astroid_cache[modname] try: # some builtin modules don't have __file__ attribute filepath = module.__file__ if is_python_source(filepath): - return self.astng_from_file(filepath, modname) + return self.ast_from_file(filepath, modname) except AttributeError: pass - from .builder import ASTNGBuilder - return ASTNGBuilder(self).module_build(module, modname) + from .builder import AstroidBuilder + return AstroidBuilder(self).module_build(module, modname) - def astng_from_class(self, klass, modname=None): - """get astng for the given class""" + def ast_from_class(self, klass, modname=None): + """get astroid for the given class""" if modname is None: try: modname = klass.__module__ except AttributeError: - raise ASTNGBuildingException( + raise AstroidBuildingException( 'Unable to get module for class %s' % safe_repr(klass)) - modastng = self.astng_from_module_name(modname) - return modastng.getattr(klass.__name__)[0] # XXX + modastroid = self.ast_from_module_name(modname) + return modastroid.getattr(klass.__name__)[0] # XXX - def infer_astng_from_something(self, obj, context=None): - """infer astng for the given class""" + def infer_ast_from_something(self, obj, context=None): + """infer astroid for the given class""" if hasattr(obj, '__class__') and not isinstance(obj, type): klass = obj.__class__ else: @@ -207,31 +207,31 @@ def infer_astng_from_something(self, obj, context=None): try: modname = klass.__module__ except AttributeError: - raise ASTNGBuildingException( + raise AstroidBuildingException( 'Unable to get module for %s' % safe_repr(klass)) except Exception, ex: - raise ASTNGBuildingException( + raise AstroidBuildingException( 'Unexpected error while retrieving module for %s: %s' % (safe_repr(klass), ex)) try: name = klass.__name__ except AttributeError: - raise ASTNGBuildingException( + raise AstroidBuildingException( 'Unable to get name for %s' % safe_repr(klass)) except Exception, ex: - raise ASTNGBuildingException( + raise AstroidBuildingException( 'Unexpected error while retrieving name for %s: %s' % (safe_repr(klass), ex)) # take care, on living object __module__ is regularly wrong :( - modastng = self.astng_from_module_name(modname) + modastroid = self.ast_from_module_name(modname) if klass is obj: - for infered in modastng.igetattr(name, context): + for infered in modastroid.igetattr(name, context): yield infered else: - for infered in modastng.igetattr(name, context): + for infered in modastroid.igetattr(name, context): yield infered.instanciate_class() - def project_from_files(self, files, func_wrapper=astng_wrapper, + def project_from_files(self, files, func_wrapper=astroid_wrapper, project_name=None, black_list=None): """return a Project from a list of files or modules""" # build the project representation @@ -245,26 +245,33 @@ def project_from_files(self, files, func_wrapper=astng_wrapper, fpath = join(something, '__init__.py') else: fpath = something - astng = func_wrapper(self.astng_from_file, fpath) - if astng is None: + astroid = func_wrapper(self.ast_from_file, fpath) + if astroid is None: continue # XXX why is first file defining the project.path ? - project.path = project.path or astng.file - project.add_module(astng) - base_name = astng.name + project.path = project.path or astroid.file + project.add_module(astroid) + base_name = astroid.name # recurse in package except if __init__ was explicitly given - if astng.package and something.find('__init__') == -1: + if astroid.package and something.find('__init__') == -1: # recurse on others packages / modules if this is a package - for fpath in get_module_files(dirname(astng.file), + for fpath in get_module_files(dirname(astroid.file), black_list): - astng = func_wrapper(self.astng_from_file, fpath) - if astng is None or astng.name == base_name: + astroid = func_wrapper(self.ast_from_file, fpath) + if astroid is None or astroid.name == base_name: continue - project.add_module(astng) + project.add_module(astroid) return project - def register_transformer(self, transformer): - self.transformers.append(transformer) + def register_transform(self, node_class, transform, predicate=None): + """Register `transform(node)` function to be applied on the given + Astroid's `node_class` if `predicate` is None or return a true value + when called with the node as argument. + + The transform function may return a value which is then used to + substitute the original node in the tree. + """ + self.transforms.setdefault(node_class, []).append( (transform, predicate) ) class Project: """a project handle a set of modules / packages""" diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/mixins.py b/pylibs/pylama/checkers/pylint/astroid/mixins.py similarity index 91% rename from pylibs/pylama/checkers/pylint/logilab/astng/mixins.py rename to pylibs/pylama/checkers/pylint/astroid/mixins.py index 5d4a865c..42bbb404 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/mixins.py +++ b/pylibs/pylama/checkers/pylint/astroid/mixins.py @@ -1,24 +1,24 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . +# with astroid. If not, see . """This module contains some mixins for the different nodes. """ -from .exceptions import (ASTNGBuildingException, InferenceError, +from .exceptions import (AstroidBuildingException, InferenceError, NotFoundError) @@ -101,7 +101,7 @@ def do_import_module(self, modname): return mymodule try: return mymodule.import_module(modname, level=level) - except ASTNGBuildingException: + except AstroidBuildingException: raise InferenceError(modname) except SyntaxError, ex: raise InferenceError(str(ex)) diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/node_classes.py b/pylibs/pylama/checkers/pylint/astroid/node_classes.py similarity index 89% rename from pylibs/pylama/checkers/pylint/logilab/astng/node_classes.py rename to pylibs/pylama/checkers/pylint/astroid/node_classes.py index a7976774..52cd0a27 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/node_classes.py +++ b/pylibs/pylama/checkers/pylint/astroid/node_classes.py @@ -1,20 +1,20 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . +# with astroid. If not, see . """Module for some node classes. More nodes in scoped_nodes.py """ @@ -253,9 +253,11 @@ class Name(LookupMixIn, NodeNG): class Arguments(NodeNG, AssignTypeMixin): """class representing an Arguments node""" - _astng_fields = ('args', 'defaults') + _astroid_fields = ('args', 'defaults', 'kwonlyargs', 'kw_defaults') args = None defaults = None + kwonlyargs = None + kw_defaults = None def __init__(self, vararg=None, kwarg=None): self.vararg = vararg @@ -268,11 +270,17 @@ def _infer_name(self, frame, name): def format_args(self): """return arguments formatted as string""" - result = [_format_args(self.args, self.defaults)] + result = [] + if self.args: + result.append(_format_args(self.args, self.defaults)) if self.vararg: result.append('*%s' % self.vararg) if self.kwarg: result.append('**%s' % self.kwarg) + if self.kwonlyargs: + if not self.vararg: + result.append('*') + result.append(_format_args(self.kwonlyargs, self.kw_defaults)) return ', '.join(result) def default_value(self, argname): @@ -285,6 +293,9 @@ def default_value(self, argname): idx = i - (len(self.args) - len(self.defaults)) if idx >= 0: return self.defaults[idx] + i = _find_arg(argname, self.kwonlyargs)[0] + if i is not None and self.kw_defaults[i] is not None: + return self.kw_defaults[i] raise NoDefault() def is_argument(self, name): @@ -301,6 +312,12 @@ def find_argname(self, argname, rec=False): return _find_arg(argname, self.args, rec) return None, None + def get_children(self): + """override get_children to skip over None elements in kw_defaults""" + for child in super(Arguments, self).get_children(): + if child is not None: + yield child + def _find_arg(argname, args, rec=False): for i, arg in enumerate(args): @@ -326,47 +343,48 @@ def _format_args(args, defaults=None): else: values.append(arg.name) if defaults is not None and i >= default_offset: - values[-1] += '=' + defaults[i-default_offset].as_string() + if defaults[i-default_offset] is not None: + values[-1] += '=' + defaults[i-default_offset].as_string() return ', '.join(values) class AssAttr(NodeNG, ParentAssignTypeMixin): """class representing an AssAttr node""" - _astng_fields = ('expr',) + _astroid_fields = ('expr',) expr = None class Assert(Statement): """class representing an Assert node""" - _astng_fields = ('test', 'fail',) + _astroid_fields = ('test', 'fail',) test = None fail = None class Assign(Statement, AssignTypeMixin): """class representing an Assign node""" - _astng_fields = ('targets', 'value',) + _astroid_fields = ('targets', 'value',) targets = None value = None class AugAssign(Statement, AssignTypeMixin): """class representing an AugAssign node""" - _astng_fields = ('target', 'value',) + _astroid_fields = ('target', 'value',) target = None value = None class Backquote(NodeNG): """class representing a Backquote node""" - _astng_fields = ('value',) + _astroid_fields = ('value',) value = None class BinOp(NodeNG): """class representing a BinOp node""" - _astng_fields = ('left', 'right',) + _astroid_fields = ('left', 'right',) left = None right = None class BoolOp(NodeNG): """class representing a BoolOp node""" - _astng_fields = ('values',) + _astroid_fields = ('values',) values = None class Break(Statement): @@ -375,7 +393,7 @@ class Break(Statement): class CallFunc(NodeNG): """class representing a CallFunc node""" - _astng_fields = ('func', 'args', 'starargs', 'kwargs') + _astroid_fields = ('func', 'args', 'starargs', 'kwargs') func = None args = None starargs = None @@ -387,7 +405,7 @@ def __init__(self): class Compare(NodeNG): """class representing a Compare node""" - _astng_fields = ('left', 'ops',) + _astroid_fields = ('left', 'ops',) left = None ops = None @@ -405,7 +423,7 @@ def last_child(self): class Comprehension(NodeNG): """class representing a Comprehension node""" - _astng_fields = ('target', 'iter' ,'ifs') + _astroid_fields = ('target', 'iter' ,'ifs') target = None iter = None ifs = None @@ -458,7 +476,7 @@ class Continue(Statement): class Decorators(NodeNG): """class representing a Decorators node""" - _astng_fields = ('nodes',) + _astroid_fields = ('nodes',) nodes = None def __init__(self, nodes=None): @@ -470,19 +488,19 @@ def scope(self): class DelAttr(NodeNG, ParentAssignTypeMixin): """class representing a DelAttr node""" - _astng_fields = ('expr',) + _astroid_fields = ('expr',) expr = None class Delete(Statement, AssignTypeMixin): """class representing a Delete node""" - _astng_fields = ('targets',) + _astroid_fields = ('targets',) targets = None class Dict(NodeNG, Instance): """class representing a Dict node""" - _astng_fields = ('items',) + _astroid_fields = ('items',) def __init__(self, items=None): if items is None: @@ -524,7 +542,7 @@ def getitem(self, lookup_key, context=None): class Discard(Statement): """class representing a Discard node""" - _astng_fields = ('value',) + _astroid_fields = ('value',) value = None @@ -538,7 +556,7 @@ class EmptyNode(NodeNG): class ExceptHandler(Statement, AssignTypeMixin): """class representing an ExceptHandler node""" - _astng_fields = ('type', 'name', 'body',) + _astroid_fields = ('type', 'name', 'body',) type = None name = None body = None @@ -566,7 +584,7 @@ def catch(self, exceptions): class Exec(Statement): """class representing an Exec node""" - _astng_fields = ('expr', 'globals', 'locals',) + _astroid_fields = ('expr', 'globals', 'locals',) expr = None globals = None locals = None @@ -574,12 +592,12 @@ class Exec(Statement): class ExtSlice(NodeNG): """class representing an ExtSlice node""" - _astng_fields = ('dims',) + _astroid_fields = ('dims',) dims = None class For(BlockRangeMixIn, AssignTypeMixin, Statement): """class representing a For node""" - _astng_fields = ('target', 'iter', 'body', 'orelse',) + _astroid_fields = ('target', 'iter', 'body', 'orelse',) target = None iter = None body = None @@ -600,7 +618,7 @@ def __init__(self, fromname, names, level=0): class Getattr(NodeNG): """class representing a Getattr node""" - _astng_fields = ('expr',) + _astroid_fields = ('expr',) expr = None @@ -616,7 +634,7 @@ def _infer_name(self, frame, name): class If(BlockRangeMixIn, Statement): """class representing an If node""" - _astng_fields = ('test', 'body', 'orelse') + _astroid_fields = ('test', 'body', 'orelse') test = None body = None orelse = None @@ -636,7 +654,7 @@ def block_range(self, lineno): class IfExp(NodeNG): """class representing an IfExp node""" - _astng_fields = ('test', 'body', 'orelse') + _astroid_fields = ('test', 'body', 'orelse') test = None body = None orelse = None @@ -648,19 +666,19 @@ class Import(FromImportMixIn, Statement): class Index(NodeNG): """class representing an Index node""" - _astng_fields = ('value',) + _astroid_fields = ('value',) value = None class Keyword(NodeNG): """class representing a Keyword node""" - _astng_fields = ('value',) + _astroid_fields = ('value',) value = None class List(NodeNG, Instance, ParentAssignTypeMixin): """class representing a List node""" - _astng_fields = ('elts',) + _astroid_fields = ('elts',) def __init__(self, elts=None): if elts is None: @@ -694,7 +712,7 @@ class Pass(Statement): class Print(Statement): """class representing a Print node""" - _astng_fields = ('dest', 'values',) + _astroid_fields = ('dest', 'values',) dest = None values = None @@ -703,11 +721,11 @@ class Raise(Statement): """class representing a Raise node""" exc = None if sys.version_info < (3, 0): - _astng_fields = ('exc', 'inst', 'tback') + _astroid_fields = ('exc', 'inst', 'tback') inst = None tback = None else: - _astng_fields = ('exc', 'cause') + _astroid_fields = ('exc', 'cause') exc = None cause = None @@ -721,13 +739,13 @@ def raises_not_implemented(self): class Return(Statement): """class representing a Return node""" - _astng_fields = ('value',) + _astroid_fields = ('value',) value = None class Set(NodeNG, Instance, ParentAssignTypeMixin): """class representing a Set node""" - _astng_fields = ('elts',) + _astroid_fields = ('elts',) def __init__(self, elts=None): if elts is None: @@ -744,27 +762,27 @@ def itered(self): class Slice(NodeNG): """class representing a Slice node""" - _astng_fields = ('lower', 'upper', 'step') + _astroid_fields = ('lower', 'upper', 'step') lower = None upper = None step = None class Starred(NodeNG, ParentAssignTypeMixin): """class representing a Starred node""" - _astng_fields = ('value',) + _astroid_fields = ('value',) value = None class Subscript(NodeNG): """class representing a Subscript node""" - _astng_fields = ('value', 'slice') + _astroid_fields = ('value', 'slice') value = None slice = None class TryExcept(BlockRangeMixIn, Statement): """class representing a TryExcept node""" - _astng_fields = ('body', 'handlers', 'orelse',) + _astroid_fields = ('body', 'handlers', 'orelse',) body = None handlers = None orelse = None @@ -790,7 +808,7 @@ def block_range(self, lineno): class TryFinally(BlockRangeMixIn, Statement): """class representing a TryFinally node""" - _astng_fields = ('body', 'finalbody',) + _astroid_fields = ('body', 'finalbody',) body = None finalbody = None @@ -809,7 +827,7 @@ def block_range(self, lineno): class Tuple(NodeNG, Instance, ParentAssignTypeMixin): """class representing a Tuple node""" - _astng_fields = ('elts',) + _astroid_fields = ('elts',) def __init__(self, elts=None): if elts is None: @@ -829,13 +847,13 @@ def itered(self): class UnaryOp(NodeNG): """class representing an UnaryOp node""" - _astng_fields = ('operand',) + _astroid_fields = ('operand',) operand = None class While(BlockRangeMixIn, Statement): """class representing a While node""" - _astng_fields = ('test', 'body', 'orelse',) + _astroid_fields = ('test', 'body', 'orelse',) test = None body = None orelse = None @@ -850,21 +868,24 @@ def block_range(self, lineno): class With(BlockRangeMixIn, AssignTypeMixin, Statement): """class representing a With node""" - _astng_fields = ('expr', 'vars', 'body') - expr = None - vars = None + _astroid_fields = ('items', 'body') + items = None body = None def _blockstart_toline(self): - if self.vars: - return self.vars.tolineno - else: - return self.expr.tolineno + return self.items[-1][0].tolineno + def get_children(self): + for expr, var in self.items: + yield expr + if var: + yield var + for elt in self.body: + yield elt class Yield(NodeNG): """class representing a Yield node""" - _astng_fields = ('value',) + _astroid_fields = ('value',) value = None # constants ############################################################## @@ -889,7 +910,7 @@ def _update_const_classes(): _update_const_classes() def const_factory(value): - """return an astng node for a python value""" + """return an astroid node for a python value""" # XXX we should probably be stricter here and only consider stuff in # CONST_CLS or do better treatment: in case where value is not in CONST_CLS, # we should rather recall the builder on this value than returning an empty diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/nodes.py b/pylibs/pylama/checkers/pylint/astroid/nodes.py similarity index 88% rename from pylibs/pylama/checkers/pylint/logilab/astng/nodes.py rename to pylibs/pylama/checkers/pylint/astroid/nodes.py index b475b902..4deddde3 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/nodes.py +++ b/pylibs/pylama/checkers/pylint/astroid/nodes.py @@ -1,20 +1,20 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . +# with astroid. If not, see . """ on all nodes : .is_statement, returning true if the node should be considered as a @@ -26,7 +26,7 @@ .frame(), returning the first node defining a new local scope (i.e. Module, Function or Class) .set_local(name, node), define an identifier on the first parent frame, - with the node defining it. This is used by the astng builder and should not + with the node defining it. This is used by the astroid builder and should not be used from out there. on From and Import : diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/protocols.py b/pylibs/pylama/checkers/pylint/astroid/protocols.py similarity index 96% rename from pylibs/pylama/checkers/pylint/logilab/astng/protocols.py rename to pylibs/pylama/checkers/pylint/astroid/protocols.py index 05feb246..109d1555 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/protocols.py +++ b/pylibs/pylama/checkers/pylint/astroid/protocols.py @@ -1,20 +1,20 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . +# with astroid. If not, see . """this module contains a set of functions to handle python protocols for nodes where it makes sense. """ @@ -310,10 +310,11 @@ def excepthandler_assigned_stmts(self, node, context=None, asspath=None): def with_assigned_stmts(self, node, context=None, asspath=None): if asspath is None: - for lst in self.vars.infer(context): - if isinstance(lst, (nodes.Tuple, nodes.List)): - for item in lst.nodes: - yield item + for _, vars in self.items: + for lst in vars.infer(context): + if isinstance(lst, (nodes.Tuple, nodes.List)): + for item in lst.nodes: + yield item nodes.With.assigned_stmts = raise_if_nothing_infered(with_assigned_stmts) diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/raw_building.py b/pylibs/pylama/checkers/pylint/astroid/raw_building.py similarity index 87% rename from pylibs/pylama/checkers/pylint/logilab/astng/raw_building.py rename to pylibs/pylama/checkers/pylint/astroid/raw_building.py index 076f28f3..b7144a86 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/raw_building.py +++ b/pylibs/pylama/checkers/pylint/astroid/raw_building.py @@ -1,21 +1,21 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . -"""this module contains a set of functions to create astng trees from scratch +# with astroid. If not, see . +"""this module contains a set of functions to create astroid trees from scratch (build_* functions) or from living object (object_build_* functions) """ @@ -30,8 +30,8 @@ from .nodes import (Module, Class, Const, const_factory, From, Function, EmptyNode, Name, Arguments) from .bases import BUILTINS, Generator -from .manager import ASTNGManager -MANAGER = ASTNGManager() +from .manager import AstroidManager +MANAGER = AstroidManager() _CONSTANTS = tuple(CONST_CLS) # the keys of CONST_CLS eg python builtin types @@ -67,14 +67,14 @@ def attach_import_node(node, modname, membername): def build_module(name, doc=None): - """create and initialize a astng Module node""" + """create and initialize a astroid Module node""" node = Module(name, doc, pure_python=False) node.package = False node.parent = None return node def build_class(name, basenames=(), doc=None): - """create and initialize a astng Class node""" + """create and initialize a astroid Class node""" node = Class(name, doc) for base in basenames: basenode = Name() @@ -84,7 +84,7 @@ def build_class(name, basenames=(), doc=None): return node def build_function(name, args=None, defaults=None, flag=0, doc=None): - """create and initialize a astng Function node""" + """create and initialize a astroid Function node""" args, defaults = args or [], defaults or [] # first argument is now a list of decorators func = Function(name, doc) @@ -107,7 +107,7 @@ def build_function(name, args=None, defaults=None, flag=0, doc=None): def build_from_import(fromname, names): - """create and initialize an astng From import statement""" + """create and initialize an astroid From import statement""" return From(fromname, [(name, None) for name in names]) def register_arguments(func, args=None): @@ -129,13 +129,13 @@ def register_arguments(func, args=None): register_arguments(func, arg.elts) def object_build_class(node, member, localname): - """create astng for a living class object""" + """create astroid for a living class object""" basenames = [base.__name__ for base in member.__bases__] return _base_class_object_build(node, member, basenames, localname=localname) def object_build_function(node, member, localname): - """create astng for a living function object""" + """create astroid for a living function object""" args, varargs, varkw, defaults = getargspec(member) if varargs is not None: args.append(varargs) @@ -146,11 +146,11 @@ def object_build_function(node, member, localname): node.add_local_node(func, localname) def object_build_datadescriptor(node, member, name): - """create astng for a living data descriptor object""" + """create astroid for a living data descriptor object""" return _base_class_object_build(node, member, [], name) def object_build_methoddescriptor(node, member, localname): - """create astng for a living method descriptor object""" + """create astroid for a living method descriptor object""" # FIXME get arguments ? func = build_function(getattr(member, '__name__', None) or localname, doc=member.__doc__) @@ -160,7 +160,7 @@ def object_build_methoddescriptor(node, member, localname): node.add_local_node(func, localname) def _base_class_object_build(node, member, basenames, name=None, localname=None): - """create astng for a living class object, with a given set of base names + """create astroid for a living class object, with a given set of base names (e.g. ancestors) """ klass = build_class(name or getattr(member, '__name__', None) or localname, @@ -197,14 +197,14 @@ class InspectBuilder(object): Function and Class nodes and some others as guessed. """ - # astng from living objects ############################################### + # astroid from living objects ############################################### def __init__(self): self._done = {} self._module = None def inspect_build(self, module, modname=None, path=None): - """build astng from a living module (i.e. using inspect) + """build astroid from a living module (i.e. using inspect) this is used when there is no python source code available (either because it's a built-in module or because the .py is not available) """ @@ -217,7 +217,7 @@ def inspect_build(self, module, modname=None, path=None): # in jython, java modules have no __doc__ (see #109562) node = build_module(modname) node.file = node.path = path and abspath(path) or path - MANAGER.astng_cache[modname] = node + MANAGER.astroid_cache[modname] = node node.package = hasattr(module, '__path__') self._done = {} self.object_build(node, module) @@ -289,7 +289,7 @@ def imported_member(self, node, member, name): modname = getattr(member, '__module__', None) except: # XXX use logging - print 'unexpected error while building astng from living object' + print 'unexpected error while building astroid from living object' import traceback traceback.print_exc() modname = None @@ -315,28 +315,28 @@ def imported_member(self, node, member, name): return False -### astng boot strapping ################################################### ### -ASTNG_BUILDER = InspectBuilder() +### astroid boot strapping ################################################### ### +Astroid_BUILDER = InspectBuilder() _CONST_PROXY = {} -def astng_boot_strapping(): - """astng boot strapping the builtins module""" +def astroid_boot_strapping(): + """astroid boot strapping the builtins module""" # this boot strapping is necessary since we need the Const nodes to # inspect_build builtins, and then we can proxy Const - from ..common.compat import builtins - astng_builtin = ASTNG_BUILDER.inspect_build(builtins) + from ..logilab.common.compat import builtins + astroid_builtin = Astroid_BUILDER.inspect_build(builtins) for cls, node_cls in CONST_CLS.items(): if cls is type(None): proxy = build_class('NoneType') - proxy.parent = astng_builtin + proxy.parent = astroid_builtin else: - proxy = astng_builtin.getattr(cls.__name__)[0] + proxy = astroid_builtin.getattr(cls.__name__)[0] if cls in (dict, list, set, tuple): node_cls._proxied = proxy else: _CONST_PROXY[cls] = proxy -astng_boot_strapping() +astroid_boot_strapping() # TODO : find a nicer way to handle this situation; # However __proxied introduced an @@ -347,5 +347,5 @@ def _set_proxied(const): from types import GeneratorType Generator._proxied = Class(GeneratorType.__name__, GeneratorType.__doc__) -ASTNG_BUILDER.object_build(Generator._proxied, GeneratorType) +Astroid_BUILDER.object_build(Generator._proxied, GeneratorType) diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/rebuilder.py b/pylibs/pylama/checkers/pylint/astroid/rebuilder.py similarity index 90% rename from pylibs/pylama/checkers/pylint/logilab/astng/rebuilder.py rename to pylibs/pylama/checkers/pylint/astroid/rebuilder.py index 03ee6128..d0f6bde2 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/rebuilder.py +++ b/pylibs/pylama/checkers/pylint/astroid/rebuilder.py @@ -1,25 +1,26 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . +# with astroid. If not, see . """this module contains utilities for rebuilding a _ast tree in -order to get a single ASTNG representation +order to get a single Astroid representation """ import sys +from warnings import warn from _ast import (Expr as Discard, Str, # binary operators Add, Div, FloorDiv, Mod, Mult, Pow, Sub, BitAnd, BitOr, BitXor, @@ -117,29 +118,56 @@ def _set_infos(oldnode, newnode, parent): class TreeRebuilder(object): - """Rebuilds the _ast tree to become an ASTNG tree""" + """Rebuilds the _ast tree to become an Astroid tree""" - _visit_meths = {} - def __init__(self): - self.init() - - def init(self): + def __init__(self, manager): + self._manager = manager self.asscontext = None self._metaclass = [''] self._global_names = [] self._from_nodes = [] self._delayed_assattr = [] + self._visit_meths = {} + + def _transform(self, node): + try: + transforms = self._manager.transforms[type(node)] + except KeyError: + return node # no transform registered for this class of node + orig_node = node # copy the reference + for transform_func, predicate in transforms: + if predicate is None or predicate(node): + ret = transform_func(node) + # if the transformation function returns something, it's + # expected to be a replacement for the node + if ret is not None: + if node is not orig_node: + # node has already be modified by some previous + # transformation, warn about it + warn('node %s substitued multiple times' % node) + node = ret + return node + + def visit_module(self, node, modname, package): + """visit a Module node by returning a fresh instance of it""" + newnode = new.Module(modname, None) + newnode.package = package + _lineno_parent(node, newnode, parent=None) + _init_set_doc(node, newnode) + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.set_line_info(newnode.last_child()) + return self._transform(newnode) def visit(self, node, parent): cls = node.__class__ if cls in self._visit_meths: - return self._visit_meths[cls](node, parent) + visit_method = self._visit_meths[cls] else: cls_name = cls.__name__ visit_name = 'visit_' + REDIRECT.get(cls_name, cls_name).lower() visit_method = getattr(self, visit_name) self._visit_meths[cls] = visit_method - return visit_method(node, parent) + return self._transform(visit_method(node, parent)) def _save_assignment(self, node, name=None): """save assignement situation since node.parent is not available yet""" @@ -157,6 +185,8 @@ def visit_arguments(self, node, parent): newnode.args = [self.visit(child, newnode) for child in node.args] self.asscontext = None newnode.defaults = [self.visit(child, newnode) for child in node.defaults] + newnode.kwonlyargs = [] + newnode.kw_defaults = [] newnode.vararg = node.vararg newnode.kwarg = node.kwarg # save argument names in locals: @@ -287,7 +317,7 @@ def visit_callfunc(self, node, parent): return newnode def visit_class(self, node, parent): - """visit a Class node to become astng""" + """visit a Class node to become astroid""" self._metaclass.append(self._metaclass[-1]) newnode = new.Class(node.name, None) _lineno_parent(node, newnode, parent) @@ -342,7 +372,7 @@ def visit_comprehension(self, node, parent): def visit_decorators(self, node, parent): """visit a Decorators node by returning a fresh instance of it""" # /!\ node is actually a _ast.Function node while - # parent is a astng.nodes.Function node + # parent is a astroid.nodes.Function node newnode = new.Decorators() _lineno_parent(node, newnode, parent) if 'decorators' in node._fields: # py < 2.6, i.e. 2.5 @@ -461,7 +491,7 @@ def visit_from(self, node, parent): return newnode def visit_function(self, node, parent): - """visit an Function node to become astng""" + """visit an Function node to become astroid""" self._global_names.append({}) newnode = new.Function(node.name, None) _lineno_parent(node, newnode, parent) @@ -523,7 +553,7 @@ def visit_getattr(self, node, parent): return newnode def visit_global(self, node, parent): - """visit an Global node to become astng""" + """visit an Global node to become astroid""" newnode = new.Global(node.names) _set_infos(node, newnode, parent) if self._global_names: # global at the module level, no effect @@ -606,16 +636,6 @@ def visit_listcomp(self, node, parent): newnode.set_line_info(newnode.last_child()) return newnode - def visit_module(self, node, modname, package): - """visit a Module node by returning a fresh instance of it""" - newnode = new.Module(modname, None) - newnode.package = package - _lineno_parent(node, newnode, parent=None) - _init_set_doc(node, newnode) - newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.set_line_info(newnode.last_child()) - return newnode - def visit_name(self, node, parent): """visit a Name node by returning a fresh instance of it""" # True and False can be assigned to something in py2x, so we have to @@ -787,12 +807,14 @@ def visit_while(self, node, parent): def visit_with(self, node, parent): newnode = new.With() _lineno_parent(node, newnode, parent) - _node = getattr(node, 'items', [node])[0] # python 3.3 XXX - newnode.expr = self.visit(_node.context_expr, newnode) + expr = self.visit(node.context_expr, newnode) self.asscontext = "Ass" - if _node.optional_vars is not None: - newnode.vars = self.visit(_node.optional_vars, newnode) + if node.optional_vars is not None: + vars = self.visit(node.optional_vars, newnode) + else: + vars = None self.asscontext = None + newnode.items = [(expr, vars)] newnode.body = [self.visit(child, newnode) for child in node.body] newnode.set_line_info(newnode.last_child()) return newnode @@ -813,9 +835,17 @@ class TreeRebuilder3k(TreeRebuilder): def visit_arg(self, node, parent): """visit a arg node by returning a fresh AssName instance""" # the node is coming from py>=3.0, but we use AssName in py2.x - # XXX or we should instead introduce a Arg node in astng ? + # XXX or we should instead introduce a Arg node in astroid ? return self.visit_assname(node, parent, node.arg) + def visit_arguments(self, node, parent): + newnode = super(TreeRebuilder3k, self).visit_arguments(node, parent) + self.asscontext = "Ass" + newnode.kwonlyargs = [self.visit(child, newnode) for child in node.kwonlyargs] + self.asscontext = None + newnode.kw_defaults = [self.visit(child, newnode) if child else None for child in node.kw_defaults] + return newnode + def visit_excepthandler(self, node, parent): """visit an ExceptHandler node by returning a fresh instance of it""" newnode = new.ExceptHandler() @@ -862,10 +892,11 @@ def visit_try(self, node, parent): newnode.finalbody = [self.visit(n, newnode) for n in node.finalbody] if node.handlers: excnode = new.TryExcept() - _lineno_parent(node, excnode, parent) - excnode.body = [self.visit(child, newnode) for child in node.body] - excnode.handlers = [self.visit(child, newnode) for child in node.handlers] - excnode.orelse = [self.visit(child, newnode) for child in node.orelse] + _lineno_parent(node, excnode, newnode) + excnode.body = [self.visit(child, excnode) for child in node.body] + excnode.handlers = [self.visit(child, excnode) for child in node.handlers] + excnode.orelse = [self.visit(child, excnode) for child in node.orelse] + excnode.set_line_info(excnode.last_child()) newnode.body = [excnode] else: newnode.body = [self.visit(child, newnode) for child in node.body] @@ -878,6 +909,28 @@ def visit_try(self, node, parent): newnode.set_line_info(newnode.last_child()) return newnode + def visit_with(self, node, parent): + if 'items' not in node._fields: + # python < 3.3 + return super(TreeRebuilder3k, self).visit_with(node, parent) + + newnode = new.With() + _lineno_parent(node, newnode, parent) + def visit_child(child): + expr = self.visit(child.context_expr, newnode) + self.asscontext = 'Ass' + if child.optional_vars: + var = self.visit(child.optional_vars, newnode) + else: + var = None + self.asscontext = None + return expr, var + newnode.items = [visit_child(child) + for child in node.items] + newnode.body = [self.visit(child, newnode) for child in node.body] + newnode.set_line_info(newnode.last_child()) + return newnode + def visit_yieldfrom(self, node, parent): return self.visit_yield(node, parent) diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/scoped_nodes.py b/pylibs/pylama/checkers/pylint/astroid/scoped_nodes.py similarity index 91% rename from pylibs/pylama/checkers/pylint/logilab/astng/scoped_nodes.py rename to pylibs/pylama/checkers/pylint/astroid/scoped_nodes.py index 8e049775..e52c89f7 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/scoped_nodes.py +++ b/pylibs/pylama/checkers/pylint/astroid/scoped_nodes.py @@ -1,20 +1,20 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . +# with astroid. If not, see . """This module contains the classes for "scoped" node, i.e. which are opening a new local scope in the language definition : Module, Class, Function (and Lambda, GenExpr, DictComp and SetComp to some extent). @@ -26,11 +26,11 @@ import sys from itertools import chain -from ..common.compat import builtins -from ..common.decorators import cached +from ..logilab.common.compat import builtins +from ..logilab.common.decorators import cached from .exceptions import NotFoundError, \ - ASTNGBuildingException, InferenceError + AstroidBuildingException, InferenceError from .node_classes import Const, DelName, DelAttr, \ Dict, From, List, Pass, Raise, Return, Tuple, Yield, \ LookupMixIn, const_factory as cf, unpack_infer @@ -39,7 +39,7 @@ BUILTINS from .mixins import FilterStmtsMixin from .bases import Statement -from .manager import ASTNGManager +from .manager import AstroidManager def remove_nodes(func, cls): @@ -72,20 +72,20 @@ def std_special_attributes(self, name, add_locals=True): return [Dict()] + locals.get(name, []) raise NotFoundError(name) -MANAGER = ASTNGManager() +MANAGER = AstroidManager() def builtin_lookup(name): """lookup a name into the builtin module - return the list of matching statements and the astng for the builtin + return the list of matching statements and the astroid for the builtin module """ - builtin_astng = MANAGER.astng_from_module(builtins) + builtin_astroid = MANAGER.ast_from_module(builtins) if name == '__dict__': - return builtin_astng, () + return builtin_astroid, () try: - stmts = builtin_astng.locals[name] + stmts = builtin_astroid.locals[name] except KeyError: stmts = () - return builtin_astng, stmts + return builtin_astroid, stmts # TODO move this Mixin to mixins.py; problem: 'Function' in _scope_lookup @@ -207,14 +207,14 @@ def __contains__(self, name): # Module ##################################################################### class Module(LocalsDictNodeNG): - _astng_fields = ('body',) + _astroid_fields = ('body',) fromlineno = 0 lineno = 0 # attributes below are set by the builder module or by raw factories - # the file from which as been extracted the astng representation. It may + # the file from which as been extracted the astroid representation. It may # be None if the representation has been built from a built-in module file = None # encoding of python source file, so we can get unicode out of it (python2 @@ -222,7 +222,7 @@ class Module(LocalsDictNodeNG): file_encoding = None # the module name name = None - # boolean for astng built from source (i.e. ast) + # boolean for astroid built from source (i.e. ast) pure_python = None # boolean for package module package = None @@ -246,7 +246,7 @@ def __init__(self, name, doc, pure_python=True): @property def file_stream(self): if self.file is not None: - return open(self.file) + return open(self.file, 'rb') return None def block_range(self, lineno): @@ -282,7 +282,7 @@ def getattr(self, name, context=None, ignore_locals=False): if self.package: try: return [self.import_module(name, relative_only=True)] - except ASTNGBuildingException: + except AstroidBuildingException: raise NotFoundError(name) except Exception:# XXX pylint tests never pass here; do we need it? import traceback @@ -336,13 +336,13 @@ def import_module(self, modname, relative_only=False, level=None): level = 0 absmodname = self.relative_to_absolute_name(modname, level) try: - return MANAGER.astng_from_module_name(absmodname) - except ASTNGBuildingException: + return MANAGER.ast_from_module_name(absmodname) + except AstroidBuildingException: # we only want to import a sub module or package of this module, # skip here if relative_only: raise - return MANAGER.astng_from_module_name(modname) + return MANAGER.ast_from_module_name(modname) def relative_to_absolute_name(self, modname, level): """return the absolute module name for a relative import. @@ -350,7 +350,7 @@ def relative_to_absolute_name(self, modname, level): The relative import can be implicit or explicit. """ # XXX this returns non sens when called on an absolute import - # like 'pylint.checkers.logilab.astng.utils' + # like 'pylint.checkers.astroid.utils' # XXX doesn't return absolute name if self.name isn't absolute name if self.absolute_import_activated() and level is None: return modname @@ -387,7 +387,7 @@ def wildcard_import_names(self): except AttributeError: return [name for name in living.__dict__.keys() if not name.startswith('_')] - # else lookup the astng + # else lookup the astroid # # We separate the different steps of lookup in try/excepts # to avoid catching too many Exceptions @@ -419,7 +419,7 @@ def frame(self): class GenExpr(ComprehensionScope): - _astng_fields = ('elt', 'generators') + _astroid_fields = ('elt', 'generators') def __init__(self): self.locals = {} @@ -428,7 +428,7 @@ def __init__(self): class DictComp(ComprehensionScope): - _astng_fields = ('key', 'value', 'generators') + _astroid_fields = ('key', 'value', 'generators') def __init__(self): self.locals = {} @@ -438,7 +438,7 @@ def __init__(self): class SetComp(ComprehensionScope): - _astng_fields = ('elt', 'generators') + _astroid_fields = ('elt', 'generators') def __init__(self): self.locals = {} @@ -448,7 +448,7 @@ def __init__(self): class _ListComp(NodeNG): """class representing a ListComp node""" - _astng_fields = ('elt', 'generators') + _astroid_fields = ('elt', 'generators') elt = None generators = None @@ -465,7 +465,7 @@ class ListComp(_ListComp): class Lambda(LocalsDictNodeNG, FilterStmtsMixin): - _astng_fields = ('args', 'body',) + _astroid_fields = ('args', 'body',) name = '' # function's type, 'function' | 'method' | 'staticmethod' | 'classmethod' @@ -506,7 +506,7 @@ def infer_call_result(self, caller, context=None): return self.body.infer(context) def scope_lookup(self, node, name, offset=0): - if node in self.args.defaults: + if node in self.args.defaults or node in self.args.kw_defaults: frame = self.parent.frame() # line offset to avoid that def func(f=func) resolve the default # value to the defined function @@ -518,7 +518,7 @@ def scope_lookup(self, node, name, offset=0): class Function(Statement, Lambda): - _astng_fields = ('decorators', 'args', 'body') + _astroid_fields = ('decorators', 'args', 'body') special_attributes = set(('__name__', '__doc__', '__dict__')) is_function = True @@ -542,6 +542,8 @@ def set_line_info(self, lastchild): if self.decorators is not None: self.fromlineno += sum(node.tolineno - node.lineno + 1 for node in self.decorators.nodes) + if self.args.fromlineno < self.fromlineno: + self.args.fromlineno = self.fromlineno self.tolineno = lastchild.tolineno self.blockstart_tolineno = self.args.tolineno @@ -586,10 +588,23 @@ def is_bound(self): return self.type == 'classmethod' def is_abstract(self, pass_is_abstract=True): - """return true if the method is abstract - It's considered as abstract if the only statement is a raise of - NotImplementError, or, if pass_is_abstract, a pass statement + """Returns True if the method is abstract. + + A method is considered abstract if + - the only statement is 'raise NotImplementedError', or + - the only statement is 'pass' and pass_is_abstract is True, or + - the method is annotated with abc.astractproperty/abc.abstractmethod """ + if self.decorators: + for node in self.decorators.nodes: + try: + infered = node.infer().next() + except InferenceError: + continue + if infered and infered.qname() in ('abc.abstractproperty', + 'abc.abstractmethod'): + return True + for child_node in self.body: if isinstance(child_node, Raise): if child_node.raises_not_implemented(): @@ -683,7 +698,7 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): # by a raw factories # a dictionary of class instances attributes - _astng_fields = ('decorators', 'bases', 'body') # name + _astroid_fields = ('decorators', 'bases', 'body') # name decorators = None special_attributes = set(('__name__', '__doc__', '__dict__', '__module__', @@ -802,20 +817,20 @@ def ancestors(self, recurs=True, context=None): continue def local_attr_ancestors(self, name, context=None): - """return an iterator on astng representation of parent classes + """return an iterator on astroid representation of parent classes which have defined in their locals """ - for astng in self.ancestors(context=context): - if name in astng: - yield astng + for astroid in self.ancestors(context=context): + if name in astroid: + yield astroid def instance_attr_ancestors(self, name, context=None): - """return an iterator on astng representation of parent classes + """return an iterator on astroid representation of parent classes which have defined in their instance attribute dictionary """ - for astng in self.ancestors(context=context): - if name in astng.instance_attrs: - yield astng + for astroid in self.ancestors(context=context): + if name in astroid.instance_attrs: + yield astroid def has_base(self, node): return node in self.bases @@ -838,7 +853,7 @@ def local_attr(self, name, context=None): local_attr = remove_nodes(local_attr, DelAttr) def instance_attr(self, name, context=None): - """return the astng nodes associated to name in this class instance + """return the astroid nodes associated to name in this class instance attributes dictionary and in its parents :raises `NotFoundError`: @@ -940,8 +955,8 @@ def methods(self): its ancestors """ done = {} - for astng in chain(iter((self,)), self.ancestors()): - for meth in astng.mymethods(): + for astroid in chain(iter((self,)), self.ancestors()): + for meth in astroid.mymethods(): if meth.name in done: continue done[meth.name] = None diff --git a/pylibs/pylama/checkers/pylint/logilab/astng/utils.py b/pylibs/pylama/checkers/pylint/astroid/utils.py similarity index 90% rename from pylibs/pylama/checkers/pylint/logilab/astng/utils.py rename to pylibs/pylama/checkers/pylint/astroid/utils.py index 325535b0..322e25b2 100644 --- a/pylibs/pylama/checkers/pylint/logilab/astng/utils.py +++ b/pylibs/pylama/checkers/pylint/astroid/utils.py @@ -1,27 +1,27 @@ # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # -# This file is part of logilab-astng. +# This file is part of astroid. # -# logilab-astng is free software: you can redistribute it and/or modify it +# astroid is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 2.1 of the License, or (at your # option) any later version. # -# logilab-astng is distributed in the hope that it will be useful, but +# astroid 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License along -# with logilab-astng. If not, see . +# with astroid. If not, see . """this module contains some utilities to navigate in the tree or to extract information from it """ __docformat__ = "restructuredtext en" -from .exceptions import ASTNGBuildingException +from .exceptions import AstroidBuildingException from .builder import parse @@ -123,12 +123,12 @@ def _check_children(node): if not ok: print "lines;", node.lineno, child.lineno print "of module", node.root(), node.root().name - raise ASTNGBuildingException + raise AstroidBuildingException _check_children(child) class TreeTester(object): - '''A helper class to see _ast tree and compare with astng tree + '''A helper class to see _ast tree and compare with astroid tree indent: string for tree indent representation lineno: bool to tell if we should print the line numbers @@ -141,7 +141,7 @@ class TreeTester(object): . . . nl = True . ] - >>> print tester.astng_tree_repr() + >>> print tester.astroid_tree_repr() Module() body = [ Print() @@ -219,16 +219,16 @@ def _native_repr_tree(self, node, indent, _done=None): self._string += '\n' + indent + field + " = " + repr(attr) - def build_astng_tree(self): - """build astng tree from the _ast tree + def build_astroid_tree(self): + """build astroid tree from the _ast tree """ - from logilab.astng.builder import ASTNGBuilder - tree = ASTNGBuilder().string_build(self.sourcecode) + from .builder import AstroidBuilder + tree = AstroidBuilder().string_build(self.sourcecode) return tree - def astng_tree_repr(self, ids=False): - """build the astng tree and return a nice tree representation""" - mod = self.build_astng_tree() + def astroid_tree_repr(self, ids=False): + """build the astroid tree and return a nice tree representation""" + mod = self.build_astroid_tree() return mod.repr_tree(ids) diff --git a/pylibs/pylama/checkers/pylint/checkers/__init__.py b/pylibs/pylama/checkers/pylint/checkers/__init__.py index bc722279..dddc1bab 100644 --- a/pylibs/pylama/checkers/pylint/checkers/__init__.py +++ b/pylibs/pylama/checkers/pylint/checkers/__init__.py @@ -38,15 +38,16 @@ """ +import sys import tokenize -from os import listdir -from os.path import dirname, join, isdir, splitext +import warnings +from os.path import dirname -from ..logilab.astng.utils import ASTWalker -from ..logilab.common.modutils import load_module_from_file +from ..astroid.utils import ASTWalker from ..logilab.common.configuration import OptionsProviderMixIn -from ..reporters import diff_string, EmptyReport +from ..reporters import diff_string +from ..utils import register_plugins def table_lines_from_stats(stats, old_stats, columns): """get values listed in from and , @@ -121,43 +122,31 @@ def process_module(self, node): stream must implement the readline method """ + warnings.warn("Modules that need access to the tokens should " + "use the ITokenChecker interface.", + DeprecationWarning) stream = node.file_stream - stream.seek(0) # XXX may be removed with astng > 0.23 - self.process_tokens(tokenize.generate_tokens(stream.readline)) + stream.seek(0) # XXX may be removed with astroid > 0.23 + if sys.version_info <= (3, 0): + self.process_tokens(tokenize.generate_tokens(stream.readline)) + else: + self.process_tokens(tokenize.tokenize(stream.readline)) def process_tokens(self, tokens): """should be overridden by subclasses""" raise NotImplementedError() -PY_EXTS = ('.py', '.pyc', '.pyo', '.pyw', '.so', '.dll') +class BaseTokenChecker(BaseChecker): + """Base class for checkers that want to have access to the token stream.""" + + def process_tokens(self, tokens): + """Should be overridden by subclasses.""" + raise NotImplementedError() + def initialize(linter): """initialize linter with checkers in this package """ - package_load(linter, __path__[0]) + register_plugins(linter, __path__[0]) -def package_load(linter, directory): - """load all module and package in the given directory, looking for a - 'register' function in each one, used to register pylint checkers - """ - imported = {} - for filename in listdir(directory): - basename, extension = splitext(filename) - if basename in imported or basename == '__pycache__': - continue - if extension in PY_EXTS and basename != '__init__' or ( - not extension and isdir(join(directory, basename))): - try: - module = load_module_from_file(join(directory, filename)) - except ValueError: - # empty module name (usually emacs auto-save files) - continue - except ImportError, exc: - import sys - print >> sys.stderr, "Problem importing module %s: %s" % (filename, exc) - else: - if hasattr(module, 'register'): - module.register(linter) - imported[basename] = 1 - -__all__ = ('BaseChecker', 'initialize', 'package_load') +__all__ = ('BaseChecker', 'initialize') diff --git a/pylibs/pylama/checkers/pylint/checkers/base.py b/pylibs/pylama/checkers/pylint/checkers/base.py index 495076f8..5282898e 100644 --- a/pylibs/pylama/checkers/pylint/checkers/base.py +++ b/pylibs/pylama/checkers/pylint/checkers/base.py @@ -1,6 +1,7 @@ # Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# Copyright (c) 2009-2010 Arista Networks, Inc. # http://www.logilab.fr/ -- mailto:contact@logilab.fr +# Copyright (c) 2009-2010 Arista Networks, Inc. +# # 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 @@ -15,30 +16,35 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """basic checker for Python code""" - -from ..logilab import astng +import sys +from .. import astroid from ..logilab.common.ureports import Table -from ..logilab.astng import are_exclusive +from ..astroid import are_exclusive, bases -from ..interfaces import IASTNGChecker +from ..interfaces import IAstroidChecker +from ..utils import EmptyReport from ..reporters import diff_string -from ..checkers import BaseChecker, EmptyReport -from ..checkers.utils import ( +from . import BaseChecker +from .utils import ( check_messages, clobber_in_except, + is_builtin_object, is_inside_except, + overrides_a_method, safe_infer, ) import re + # regex for class/function/variable/constant name CLASS_NAME_RGX = re.compile('[A-Z_][a-zA-Z0-9]+$') MOD_NAME_RGX = re.compile('(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$') CONST_NAME_RGX = re.compile('(([A-Z_][A-Z0-9_]*)|(__.*__))$') COMP_VAR_RGX = re.compile('[A-Za-z_][A-Za-z0-9_]*$') DEFAULT_NAME_RGX = re.compile('[a-z_][a-z0-9_]{2,30}$') +CLASS_ATTRIBUTE_RGX = re.compile(r'([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$') # do not require a doc string on system methods NO_REQUIRED_DOC_RGX = re.compile('__.*__') @@ -48,8 +54,8 @@ def in_loop(node): """return True if the node is inside a kind of for loop""" parent = node.parent while parent is not None: - if isinstance(parent, (astng.For, astng.ListComp, astng.SetComp, - astng.DictComp, astng.GenExpr)): + if isinstance(parent, (astroid.For, astroid.ListComp, astroid.SetComp, + astroid.DictComp, astroid.GenExpr)): return True parent = parent.parent return False @@ -68,17 +74,48 @@ def in_nested_list(nested_list, obj): def _loop_exits_early(loop): """Returns true if a loop has a break statement in its body.""" - loop_nodes = (astng.For, astng.While) + loop_nodes = (astroid.For, astroid.While) # Loop over body explicitly to avoid matching break statements # in orelse. for child in loop.body: if isinstance(child, loop_nodes): continue - for _ in child.nodes_of_class(astng.Break, skip_klass=loop_nodes): + for _ in child.nodes_of_class(astroid.Break, skip_klass=loop_nodes): return True return False + +def _determine_function_name_type(node): + """Determine the name type whose regex the a function's name should match. + + :param node: A function node. + :returns: One of ('function', 'method', 'attr') + """ + if not node.is_method(): + return 'function' + if node.decorators: + decorators = node.decorators.nodes + else: + decorators = [] + for decorator in decorators: + # If the function is a property (decorated with @property + # or @abc.abstractproperty), the name type is 'attr'. + if (isinstance(decorator, astroid.Name) or + (isinstance(decorator, astroid.Getattr) and + decorator.attrname == 'abstractproperty')): + infered = safe_infer(decorator) + if (infered and + infered.qname() in ('__builtin__.property', 'abc.abstractproperty')): + return 'attr' + # If the function is decorated using the prop_method.{setter,getter} + # form, treat it like an attribute as well. + elif (isinstance(decorator, astroid.Getattr) and + decorator.attrname in ('setter', 'deleter')): + return 'attr' + return 'method' + + def report_by_type_stats(sect, stats, old_stats): """make a report of @@ -130,13 +167,13 @@ def x(self, value): self._x = value """ if node.decorators: for decorator in node.decorators.nodes: - if (isinstance(decorator, astng.Getattr) and + if (isinstance(decorator, astroid.Getattr) and getattr(decorator.expr, 'name', None) == node.name): return True return False class _BasicChecker(BaseChecker): - __implements__ = IASTNGChecker + __implements__ = IAstroidChecker name = 'basic' class BasicErrorChecker(_BasicChecker): @@ -187,79 +224,85 @@ class BasicErrorChecker(_BasicChecker): def __init__(self, linter): _BasicChecker.__init__(self, linter) - @check_messages('E0102') + @check_messages('function-redefined') def visit_class(self, node): self._check_redefinition('class', node) - @check_messages('E0100', 'E0101', 'E0102', 'E0106', 'E0108') + @check_messages('init-is-generator', 'return-in-init', + 'function-redefined', 'return-arg-in-generator', + 'duplicate-argument-name') def visit_function(self, node): if not redefined_by_decorator(node): self._check_redefinition(node.is_method() and 'method' or 'function', node) # checks for max returns, branch, return in __init__ - returns = node.nodes_of_class(astng.Return, - skip_klass=(astng.Function, astng.Class)) + returns = node.nodes_of_class(astroid.Return, + skip_klass=(astroid.Function, astroid.Class)) if node.is_method() and node.name == '__init__': if node.is_generator(): - self.add_message('E0100', node=node) + self.add_message('init-is-generator', node=node) else: values = [r.value for r in returns] - if [v for v in values if not (v is None or - (isinstance(v, astng.Const) and v.value is None) - or (isinstance(v, astng.Name) and v.name == 'None'))]: - self.add_message('E0101', node=node) + # Are we returning anything but None from constructors + if [v for v in values if + not (v is None or + (isinstance(v, astroid.Const) and v.value is None) or + (isinstance(v, astroid.Name) and v.name == 'None') + ) ]: + self.add_message('return-in-init', node=node) elif node.is_generator(): # make sure we don't mix non-None returns and yields for retnode in returns: - if isinstance(retnode.value, astng.Const) and \ + if isinstance(retnode.value, astroid.Const) and \ retnode.value.value is not None: - self.add_message('E0106', node=node, + self.add_message('return-arg-in-generator', node=node, line=retnode.fromlineno) + # Check for duplicate names args = set() for name in node.argnames(): if name in args: - self.add_message('E0108', node=node, args=(name,)) + self.add_message('duplicate-argument-name', node=node, args=(name,)) else: args.add(name) - @check_messages('E0104') + @check_messages('return-outside-function') def visit_return(self, node): - if not isinstance(node.frame(), astng.Function): - self.add_message('E0104', node=node) + if not isinstance(node.frame(), astroid.Function): + self.add_message('return-outside-function', node=node) - @check_messages('E0105') + @check_messages('yield-outside-function') def visit_yield(self, node): - if not isinstance(node.frame(), (astng.Function, astng.Lambda)): - self.add_message('E0105', node=node) + if not isinstance(node.frame(), (astroid.Function, astroid.Lambda)): + self.add_message('yield-outside-function', node=node) - @check_messages('E0103') + @check_messages('not-in-loop') def visit_continue(self, node): self._check_in_loop(node, 'continue') - @check_messages('E0103') + @check_messages('not-in-loop') def visit_break(self, node): self._check_in_loop(node, 'break') - @check_messages('W0120') + @check_messages('useless-else-on-loop') def visit_for(self, node): self._check_else_on_loop(node) - @check_messages('W0120') + @check_messages('useless-else-on-loop') def visit_while(self, node): self._check_else_on_loop(node) - @check_messages('E0107') + @check_messages('nonexistent-operator') def visit_unaryop(self, node): - """check use of the non-existent ++ adn -- operator operator""" + """check use of the non-existent ++ and -- operator operator""" if ((node.op in '+-') and - isinstance(node.operand, astng.UnaryOp) and + isinstance(node.operand, astroid.UnaryOp) and (node.operand.op == node.op)): - self.add_message('E0107', node=node, args=node.op*2) + self.add_message('nonexistent-operator', node=node, args=node.op*2) def _check_else_on_loop(self, node): """Check that any loop with an else clause has a break statement.""" if node.orelse and not _loop_exits_early(node): - self.add_message('W0120', node=node, + self.add_message('useless-else-on-loop', node=node, # This is not optimal, but the line previous # to the first statement in the else clause # will usually be the one that contains the else:. @@ -269,17 +312,17 @@ def _check_in_loop(self, node, node_name): """check that a node is inside a for or while loop""" _node = node.parent while _node: - if isinstance(_node, (astng.For, astng.While)): + if isinstance(_node, (astroid.For, astroid.While)): break _node = _node.parent else: - self.add_message('E0103', node=node, args=node_name) + self.add_message('not-in-loop', node=node, args=node_name) def _check_redefinition(self, redeftype, node): """check for redefinition of a function / method / class name""" defined_self = node.parent.frame()[node.name] if defined_self is not node and not are_exclusive(node, defined_self): - self.add_message('E0102', node=node, + self.add_message('function-redefined', node=node, args=(redeftype, defined_self.fromlineno)) @@ -287,7 +330,6 @@ def _check_redefinition(self, redeftype, node): class BasicChecker(_BasicChecker): """checks for : * doc strings - * modules / classes / functions / methods / arguments / variables name * number of arguments, local variables, branches, returns and statements in functions, methods * required module attributes @@ -296,7 +338,7 @@ class BasicChecker(_BasicChecker): * uses of the global statement """ - __implements__ = IASTNGChecker + __implements__ = IAstroidChecker name = 'basic' msgs = { @@ -332,11 +374,10 @@ class BasicChecker(_BasicChecker): 'duplicate-key', "Used when a dictionary expression binds the same key multiple \ times."), - 'W0122': ('Use of the exec statement', - 'exec-statement', - 'Used when you use the "exec" statement, to discourage its \ + 'W0122': ('Use of exec', + 'exec-used', + 'Used when you use the "exec" statement (function for Python 3), to discourage its \ usage. That doesn\'t mean you can not use it !'), - 'W0141': ('Used builtin function %r', 'bad-builtin', 'Used when a black listed builtin function is used (see the ' @@ -359,11 +400,15 @@ class BasicChecker(_BasicChecker): 'A call of assert on a tuple will always evaluate to true if ' 'the tuple is not empty, and will always evaluate to false if ' 'it is.'), + 'W0121': ('Use raise ErrorClass(args) instead of raise ErrorClass, args.', + 'old-raise-syntax', + "Used when the alternate raise syntax 'raise foo, bar' is used " + "instead of 'raise foo(bar)'.", + {'maxversion': (3, 0)}), 'C0121': ('Missing required attribute "%s"', # W0103 'missing-module-attribute', 'Used when an attribute required for modules is missing.'), - } options = (('required-attributes', @@ -392,14 +437,14 @@ def open(self): self._tryfinallys = [] self.stats = self.linter.add_stats(module=0, function=0, method=0, class_=0) - + @check_messages('missing-module-attribute') def visit_module(self, node): """check module name, docstring and required arguments """ self.stats['module'] += 1 for attr in self.config.required_attributes: if attr not in node: - self.add_message('C0121', node=node, args=attr) + self.add_message('missing-module-attribute', node=node, args=attr) def visit_class(self, node): """check module name, docstring and redefinition @@ -407,31 +452,32 @@ def visit_class(self, node): """ self.stats['class'] += 1 - @check_messages('W0104', 'W0105') + @check_messages('pointless-statement', 'pointless-string-statement', + 'expression-not-assigned') def visit_discard(self, node): """check for various kind of statements without effect""" expr = node.value - if isinstance(expr, astng.Const) and isinstance(expr.value, + if isinstance(expr, astroid.Const) and isinstance(expr.value, basestring): # treat string statement in a separated message - self.add_message('W0105', node=node) + self.add_message('pointless-string-statement', node=node) return # ignore if this is : # * a direct function call # * the unique child of a try/except body # * a yield (which are wrapped by a discard node in _ast XXX) # warn W0106 if we have any underlying function call (we can't predict - # side effects), else W0104 - if (isinstance(expr, (astng.Yield, astng.CallFunc)) or - (isinstance(node.parent, astng.TryExcept) and + # side effects), else pointless-statement + if (isinstance(expr, (astroid.Yield, astroid.CallFunc)) or + (isinstance(node.parent, astroid.TryExcept) and node.parent.body == [node])): return - if any(expr.nodes_of_class(astng.CallFunc)): - self.add_message('W0106', node=node, args=expr.as_string()) + if any(expr.nodes_of_class(astroid.CallFunc)): + self.add_message('expression-not-assigned', node=node, args=expr.as_string()) else: - self.add_message('W0104', node=node) + self.add_message('pointless-statement', node=node) - @check_messages('W0108') + @check_messages('unnecessary-lambda') def visit_lambda(self, node): """check whether or not the lambda is suspicious """ @@ -446,11 +492,11 @@ def visit_lambda(self, node): # of the lambda. return call = node.body - if not isinstance(call, astng.CallFunc): + if not isinstance(call, astroid.CallFunc): # The body of the lambda must be a function call expression # for the lambda to be unnecessary. return - # XXX are lambda still different with astng >= 0.18 ? + # XXX are lambda still different with astroid >= 0.18 ? # *args and **kwargs need to be treated specially, since they # are structured differently between the lambda and the function # call (in the lambda they appear in the args.args list and are @@ -460,14 +506,14 @@ def visit_lambda(self, node): ordinary_args = list(node.args.args) if node.args.kwarg: if (not call.kwargs - or not isinstance(call.kwargs, astng.Name) + or not isinstance(call.kwargs, astroid.Name) or node.args.kwarg != call.kwargs.name): return elif call.kwargs: return if node.args.vararg: if (not call.starargs - or not isinstance(call.starargs, astng.Name) + or not isinstance(call.starargs, astroid.Name) or node.args.vararg != call.starargs.name): return elif call.starargs: @@ -477,12 +523,13 @@ def visit_lambda(self, node): if len(ordinary_args) != len(call.args): return for i in xrange(len(ordinary_args)): - if not isinstance(call.args[i], astng.Name): + if not isinstance(call.args[i], astroid.Name): return if node.args.args[i].name != call.args[i].name: return - self.add_message('W0108', line=node.fromlineno, node=node) + self.add_message('unnecessary-lambda', line=node.fromlineno, node=node) + @check_messages('dangerous-default-value') def visit_function(self, node): """check function name, docstring, arguments, redefinition, variable names, max locals @@ -492,19 +539,20 @@ def visit_function(self, node): for default in node.args.defaults: try: value = default.infer().next() - except astng.InferenceError: + except astroid.InferenceError: continue - if (isinstance(value, astng.Instance) and - value.qname() in ('__builtin__.set', '__builtin__.dict', '__builtin__.list')): + builtins = bases.BUILTINS + if (isinstance(value, astroid.Instance) and + value.qname() in ['.'.join([builtins, x]) for x in ('set', 'dict', 'list')]): if value is default: msg = default.as_string() - elif type(value) is astng.Instance: + elif type(value) is astroid.Instance: msg = '%s (%s)' % (default.as_string(), value.qname()) else: msg = '%s (%s)' % (default.as_string(), value.as_string()) - self.add_message('W0102', node=node, args=(msg,)) + self.add_message('dangerous-default-value', node=node, args=(msg,)) - @check_messages('W0101', 'W0150') + @check_messages('unreachable', 'lost-exception') def visit_return(self, node): """1 - check is the node has a right sibling (if so, that's some unreachable code) @@ -513,16 +561,16 @@ def visit_return(self, node): """ self._check_unreachable(node) # Is it inside final body of a try...finally bloc ? - self._check_not_in_finally(node, 'return', (astng.Function,)) + self._check_not_in_finally(node, 'return', (astroid.Function,)) - @check_messages('W0101') + @check_messages('unreachable') def visit_continue(self, node): """check is the node has a right sibling (if so, that's some unreachable code) """ self._check_unreachable(node) - @check_messages('W0101', 'W0150') + @check_messages('unreachable', 'lost-exception') def visit_break(self, node): """1 - check is the node has a right sibling (if so, that's some unreachable code) @@ -532,36 +580,42 @@ def visit_break(self, node): # 1 - Is it right sibling ? self._check_unreachable(node) # 2 - Is it inside final body of a try...finally bloc ? - self._check_not_in_finally(node, 'break', (astng.For, astng.While,)) + self._check_not_in_finally(node, 'break', (astroid.For, astroid.While,)) - @check_messages('W0101') + @check_messages('unreachable', 'old-raise-syntax') def visit_raise(self, node): - """check is the node has a right sibling (if so, that's some unreachable + """check if the node has a right sibling (if so, that's some unreachable code) """ self._check_unreachable(node) + if sys.version_info >= (3, 0): + return + if node.exc is not None and node.inst is not None and node.tback is None: + self.add_message('old-raise-syntax', node=node) - @check_messages('W0122') + @check_messages('exec-used') def visit_exec(self, node): """just print a warning on exec statements""" - self.add_message('W0122', node=node) + self.add_message('exec-used', node=node) - @check_messages('W0141', 'W0142') + @check_messages('bad-builtin', 'star-args', 'exec-used') def visit_callfunc(self, node): """visit a CallFunc node -> check if this is not a blacklisted builtin call and check for * or ** use """ - if isinstance(node.func, astng.Name): + if isinstance(node.func, astroid.Name): name = node.func.name # ignore the name if it's not a builtin (i.e. not defined in the # locals nor globals scope) if not (name in node.frame() or name in node.root()): + if name == 'exec': + self.add_message('exec-used', node=node) if name in self.config.bad_functions: - self.add_message('W0141', node=node, args=name) + self.add_message('bad-builtin', node=node, args=name) if node.starargs or node.kwargs: scope = node.scope() - if isinstance(scope, astng.Function): + if isinstance(scope, astroid.Function): toprocess = [(n, vn) for (n, vn) in ((node.starargs, scope.args.vararg), (node.kwargs, scope.args.kwarg)) if n] if toprocess: @@ -569,25 +623,25 @@ def visit_callfunc(self, node): if getattr(cfnode, 'name', None) == fargname: toprocess.remove((cfnode, fargname)) if not toprocess: - return # W0142 can be skipped - self.add_message('W0142', node=node.func) + return # star-args can be skipped + self.add_message('star-args', node=node.func) - @check_messages('W0199') + @check_messages('assert-on-tuple') def visit_assert(self, node): """check the use of an assert statement on a tuple.""" - if node.fail is None and isinstance(node.test, astng.Tuple) and \ + if node.fail is None and isinstance(node.test, astroid.Tuple) and \ len(node.test.elts) == 2: - self.add_message('W0199', node=node) + self.add_message('assert-on-tuple', node=node) - @check_messages('W0109') + @check_messages('duplicate-key') def visit_dict(self, node): """check duplicate key in dictionary""" keys = set() for k, _ in node.items: - if isinstance(k, astng.Const): + if isinstance(k, astroid.Const): key = k.value if key in keys: - self.add_message('W0109', node=node, args=key) + self.add_message('duplicate-key', node=node, args=key) keys.add(key) def visit_tryfinally(self, node): @@ -602,7 +656,7 @@ def _check_unreachable(self, node): """check unreachable code""" unreach_stmt = node.next_sibling() if unreach_stmt is not None: - self.add_message('W0101', node=unreach_stmt) + self.add_message('unreachable', node=unreach_stmt) def _check_not_in_finally(self, node, node_name, breaker_classes=()): """check that a node is not inside a finally clause of a @@ -617,25 +671,24 @@ def _check_not_in_finally(self, node, node_name, breaker_classes=()): _node = node while _parent and not isinstance(_parent, breaker_classes): if hasattr(_parent, 'finalbody') and _node in _parent.finalbody: - self.add_message('W0150', node=node, args=node_name) + self.add_message('lost-exception', node=node, args=node_name) return _node = _parent _parent = _node.parent - class NameChecker(_BasicChecker): msgs = { 'C0102': ('Black listed name "%s"', 'blacklisted-name', 'Used when the name is listed in the black list (unauthorized \ names).'), - 'C0103': ('Invalid name "%s" for type %s (should match %s)', + 'C0103': ('Invalid %s name "%s"', 'invalid-name', 'Used when the name doesn\'t match the regular expression \ associated to its type (constant, variable, class...).'), - } + options = (('module-rgx', {'default' : MOD_NAME_RGX, 'type' :'regexp', 'metavar' : '', @@ -683,6 +736,12 @@ class NameChecker(_BasicChecker): 'help' : 'Regular expression which should only match correct ' 'variable names'} ), + ('class-attribute-rgx', + {'default' : CLASS_ATTRIBUTE_RGX, + 'type' :'regexp', 'metavar' : '', + 'help' : 'Regular expression which should only match correct ' + 'attribute names in class bodies'} + ), ('inlinevar-rgx', {'default' : COMP_VAR_RGX, 'type' :'regexp', 'metavar' : '', @@ -712,48 +771,65 @@ def open(self): badname_const=0, badname_variable=0, badname_inlinevar=0, - badname_argument=0) + badname_argument=0, + badname_class_attribute=0) - @check_messages('C0102', 'C0103') + @check_messages('blacklisted-name', 'invalid-name') def visit_module(self, node): self._check_name('module', node.name.split('.')[-1], node) - @check_messages('C0102', 'C0103') + @check_messages('blacklisted-name', 'invalid-name') def visit_class(self, node): self._check_name('class', node.name, node) for attr, anodes in node.instance_attrs.iteritems(): - self._check_name('attr', attr, anodes[0]) + if not list(node.instance_attr_ancestors(attr)): + self._check_name('attr', attr, anodes[0]) - @check_messages('C0102', 'C0103') + @check_messages('blacklisted-name', 'invalid-name') def visit_function(self, node): - self._check_name(node.is_method() and 'method' or 'function', + # Do not emit any warnings if the method is just an implementation + # of a base class method. + if node.is_method() and overrides_a_method(node.parent.frame(), node.name): + return + self._check_name(_determine_function_name_type(node), node.name, node) - # check arguments name + # Check argument names args = node.args.args if args is not None: self._recursive_check_names(args, node) - @check_messages('C0102', 'C0103') + @check_messages('blacklisted-name', 'invalid-name') + def visit_global(self, node): + for name in node.names: + self._check_name('const', name, node) + + @check_messages('blacklisted-name', 'invalid-name') def visit_assname(self, node): """check module level assigned names""" frame = node.frame() ass_type = node.ass_type() - if isinstance(ass_type, (astng.Comprehension, astng.Comprehension)): + if isinstance(ass_type, astroid.Comprehension): self._check_name('inlinevar', node.name, node) - elif isinstance(frame, astng.Module): - if isinstance(ass_type, astng.Assign) and not in_loop(ass_type): - self._check_name('const', node.name, node) - elif isinstance(ass_type, astng.ExceptHandler): + elif isinstance(frame, astroid.Module): + if isinstance(ass_type, astroid.Assign) and not in_loop(ass_type): + if isinstance(safe_infer(ass_type.value), astroid.Class): + self._check_name('class', node.name, node) + else: + self._check_name('const', node.name, node) + elif isinstance(ass_type, astroid.ExceptHandler): self._check_name('variable', node.name, node) - elif isinstance(frame, astng.Function): + elif isinstance(frame, astroid.Function): # global introduced variable aren't in the function locals - if node.name in frame: + if node.name in frame and node.name not in frame.argnames(): self._check_name('variable', node.name, node) + elif isinstance(frame, astroid.Class): + if not list(frame.local_attr_ancestors(node.name)): + self._check_name('class_attribute', node.name, node) def _recursive_check_names(self, args, node): """check names in a possibly recursive list """ for arg in args: - if isinstance(arg, astng.AssName): + if isinstance(arg, astroid.AssName): self._check_name('argument', arg.name, node) else: self._recursive_check_names(arg.elts, node) @@ -768,26 +844,27 @@ def _check_name(self, node_type, name, node): return if name in self.config.bad_names: self.stats['badname_' + node_type] += 1 - self.add_message('C0102', node=node, args=name) + self.add_message('blacklisted-name', node=node, args=name) return regexp = getattr(self.config, node_type + '_rgx') if regexp.match(name) is None: type_label = {'inlinedvar': 'inlined variable', 'const': 'constant', 'attr': 'attribute', + 'class_attribute': 'class attribute' }.get(node_type, node_type) - self.add_message('C0103', node=node, args=(name, type_label, regexp.pattern)) + self.add_message('invalid-name', node=node, args=(type_label, name)) self.stats['badname_' + node_type] += 1 class DocStringChecker(_BasicChecker): msgs = { - 'C0111': ('Missing docstring', # W0131 + 'C0111': ('Missing %s docstring', # W0131 'missing-docstring', 'Used when a module, function, class or method has no docstring.\ Some special methods like __init__ doesn\'t necessary require a \ docstring.'), - 'C0112': ('Empty docstring', # W0132 + 'C0112': ('Empty %s docstring', # W0132 'empty-docstring', 'Used when a module, function, class or method has an empty \ docstring (it would be too easy ;).'), @@ -796,33 +873,41 @@ class DocStringChecker(_BasicChecker): {'default' : NO_REQUIRED_DOC_RGX, 'type' : 'regexp', 'metavar' : '', 'help' : 'Regular expression which should only match ' - 'functions or classes name which do not require a ' - 'docstring'} + 'function or class names that do not require a ' + 'docstring.'} + ), + ('docstring-min-length', + {'default' : -1, + 'type' : 'int', 'metavar' : '', + 'help': ('Minimum line length for functions/classes that' + ' require docstrings, shorter ones are exempt.')} ), ) + def open(self): self.stats = self.linter.add_stats(undocumented_module=0, undocumented_function=0, undocumented_method=0, undocumented_class=0) - + @check_messages('missing-docstring', 'empty-docstring') def visit_module(self, node): self._check_docstring('module', node) + @check_messages('missing-docstring', 'empty-docstring') def visit_class(self, node): if self.config.no_docstring_rgx.match(node.name) is None: self._check_docstring('class', node) - + @check_messages('missing-docstring', 'empty-docstring') def visit_function(self, node): if self.config.no_docstring_rgx.match(node.name) is None: ftype = node.is_method() and 'method' or 'function' - if isinstance(node.parent.frame(), astng.Class): + if isinstance(node.parent.frame(), astroid.Class): overridden = False # check if node is from a method overridden by its ancestor for ancestor in node.parent.frame().ancestors(): if node.name in ancestor and \ - isinstance(ancestor[node.name], astng.Function): + isinstance(ancestor[node.name], astroid.Function): overridden = True break if not overridden: @@ -834,24 +919,32 @@ def _check_docstring(self, node_type, node): """check the node has a non empty docstring""" docstring = node.doc if docstring is None: + if node.body: + lines = node.body[-1].lineno - node.body[0].lineno + 1 + else: + lines = 0 + max_lines = self.config.docstring_min_length + + if node_type != 'module' and max_lines > -1 and lines < max_lines: + return self.stats['undocumented_'+node_type] += 1 - self.add_message('C0111', node=node) + self.add_message('missing-docstring', node=node, args=(node_type,)) elif not docstring.strip(): self.stats['undocumented_'+node_type] += 1 - self.add_message('C0112', node=node) + self.add_message('empty-docstring', node=node, args=(node_type,)) class PassChecker(_BasicChecker): - """check is the pass statement is really necessary""" + """check if the pass statement is really necessary""" msgs = {'W0107': ('Unnecessary pass statement', 'unnecessary-pass', 'Used when a "pass" statement that can be avoided is ' 'encountered.'), } - + @check_messages('unnecessary-pass') def visit_pass(self, node): if len(node.parent.child_sequence(node)) > 1: - self.add_message('W0107', node=node) + self.add_message('unnecessary-pass', node=node) class LambdaForComprehensionChecker(_BasicChecker): @@ -865,23 +958,23 @@ class LambdaForComprehensionChecker(_BasicChecker): 'deprecated-lambda', 'Used when a lambda is the first argument to "map" or ' '"filter". It could be clearer as a list ' - 'comprehension or generator expression.'), + 'comprehension or generator expression.', + {'maxversion': (3, 0)}), } - @check_messages('W0110') + @check_messages('deprecated-lambda') def visit_callfunc(self, node): """visit a CallFunc node, check if map or filter are called with a lambda """ if not node.args: return - if not isinstance(node.args[0], astng.Lambda): + if not isinstance(node.args[0], astroid.Lambda): return infered = safe_infer(node.func) - if (infered - and infered.parent.name == '__builtin__' + if (is_builtin_object(infered) and infered.name in ['map', 'filter']): - self.add_message('W0110', node=node) + self.add_message('deprecated-lambda', node=node) def register(linter): diff --git a/pylibs/pylama/checkers/pylint/checkers/classes.py b/pylibs/pylama/checkers/pylint/checkers/classes.py index 8e3b836d..ecacecea 100644 --- a/pylibs/pylama/checkers/pylint/checkers/classes.py +++ b/pylibs/pylama/checkers/pylint/checkers/classes.py @@ -17,12 +17,12 @@ """ from __future__ import generators -from ..logilab import astng -from ..logilab.astng import YES, Instance, are_exclusive, AssAttr +from .. import astroid +from ..astroid import YES, Instance, are_exclusive, AssAttr -from ..interfaces import IASTNGChecker -from ..checkers import BaseChecker -from ..checkers.utils import (PYMETHODS, overrides_a_method, +from ..interfaces import IAstroidChecker +from . import BaseChecker +from .utils import (PYMETHODS, overrides_a_method, check_messages, is_attr_private, is_attr_protected, node_frame_class) def class_is_abstract(node): @@ -156,7 +156,7 @@ class ClassChecker(BaseChecker): * unreachable code """ - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) # configuration section name name = 'classes' @@ -222,7 +222,7 @@ def visit_class(self, node): if node.type == 'class': try: node.local_attr('__init__') - except astng.NotFoundError: + except astroid.NotFoundError: self.add_message('W0232', args=node, node=node) @check_messages('E0203', 'W0201') @@ -241,7 +241,7 @@ def leave_class(self, cnode): defining_methods = self.config.defining_attr_methods for attr, nodes in cnode.instance_attrs.iteritems(): nodes = [n for n in nodes if not - isinstance(n.statement(), (astng.Delete, astng.AugAssign))] + isinstance(n.statement(), (astroid.Delete, astroid.AugAssign))] if not nodes: continue # error detected by typechecking attr_defined = False @@ -264,7 +264,7 @@ def leave_class(self, cnode): # check attribute is defined as a class attribute try: cnode.local_attr(attr) - except astng.NotFoundError: + except astroid.NotFoundError: self.add_message('W0201', args=attr, node=node) def visit_function(self, node): @@ -281,25 +281,25 @@ def visit_function(self, node): return # check signature if the method overloads inherited method for overridden in klass.local_attr_ancestors(node.name): - # get astng for the searched method + # get astroid for the searched method try: meth_node = overridden[node.name] except KeyError: # we have found the method but it's not in the local # dictionary. - # This may happen with astng build from living objects + # This may happen with astroid build from living objects continue - if not isinstance(meth_node, astng.Function): + if not isinstance(meth_node, astroid.Function): continue self._check_signature(node, meth_node, 'overridden') break if node.decorators: for decorator in node.decorators.nodes: - if isinstance(decorator, astng.Getattr) and \ + if isinstance(decorator, astroid.Getattr) and \ decorator.attrname in ('getter', 'setter', 'deleter'): # attribute affectation will call this method, not hiding it return - if isinstance(decorator, astng.Name) and decorator.name == 'property': + if isinstance(decorator, astroid.Name) and decorator.name == 'property': # attribute affectation will either call a setter or raise # an attribute error, anyway not hiding the function return @@ -308,7 +308,7 @@ def visit_function(self, node): overridden = klass.instance_attr(node.name)[0] # XXX args = (overridden.root().name, overridden.fromlineno) self.add_message('E0202', args=args, node=node) - except astng.NotFoundError: + except astroid.NotFoundError: pass def leave_function(self, node): @@ -387,8 +387,8 @@ def _check_protected_attribute_access(self, node): return # If the expression begins with a call to super, that's ok. - if isinstance(node.expr, astng.CallFunc) and \ - isinstance(node.expr.func, astng.Name) and \ + if isinstance(node.expr, astroid.CallFunc) and \ + isinstance(node.expr.func, astroid.Name) and \ node.expr.func.name == 'super': return @@ -416,7 +416,7 @@ def _check_accessed_members(self, node, accessed): node.local_attr(attr) # yes, stop here continue - except astng.NotFoundError: + except astroid.NotFoundError: pass # is it an instance attribute of a parent class ? try: @@ -428,7 +428,7 @@ def _check_accessed_members(self, node, accessed): # is it an instance attribute ? try: defstmts = node.instance_attr(attr) - except astng.NotFoundError: + except astroid.NotFoundError: pass else: if len(defstmts) == 1: @@ -530,7 +530,7 @@ def _check_interfaces(self, node): e0221_hack = [False] def iface_handler(obj): """filter interface objects, it should be classes""" - if not isinstance(obj, astng.Class): + if not isinstance(obj, astroid.Class): e0221_hack[0] = True self.add_message('E0221', node=node, args=(obj.as_string(),)) @@ -545,10 +545,10 @@ def iface_handler(obj): # don't check method beginning with an underscore, # usually belonging to the interface implementation continue - # get class method astng + # get class method astroid try: method = node_method(node, name) - except astng.NotFoundError: + except astroid.NotFoundError: self.add_message('E0222', args=(name, iface.name), node=node) continue @@ -558,12 +558,12 @@ def iface_handler(obj): # check signature self._check_signature(method, imethod, '%s interface' % iface.name) - except astng.InferenceError: + except astroid.InferenceError: if e0221_hack[0]: return implements = Instance(node).getattr('__implements__')[0] assignment = implements.parent - assert isinstance(assignment, astng.Assign) + assert isinstance(assignment, astroid.Assign) # assignment.expr can be a Name or a Tuple or whatever. # Use as_string() for the message # FIXME: in case of multiple interfaces, find which one could not @@ -580,14 +580,14 @@ def _check_init(self, node): klass_node = node.parent.frame() to_call = _ancestors_to_call(klass_node) not_called_yet = dict(to_call) - for stmt in node.nodes_of_class(astng.CallFunc): + for stmt in node.nodes_of_class(astroid.CallFunc): expr = stmt.func - if not isinstance(expr, astng.Getattr) \ + if not isinstance(expr, astroid.Getattr) \ or expr.attrname != '__init__': continue # skip the test if using super - if isinstance(expr.expr, astng.CallFunc) and \ - isinstance(expr.expr.func, astng.Name) and \ + if isinstance(expr.expr, astroid.CallFunc) and \ + isinstance(expr.expr.func, astroid.Name) and \ expr.expr.func.name == 'super': return try: @@ -599,7 +599,7 @@ def _check_init(self, node): except KeyError: if klass not in to_call: self.add_message('W0233', node=expr, args=klass.name) - except astng.InferenceError: + except astroid.InferenceError: continue for klass, method in not_called_yet.iteritems(): if klass.name == 'object' or method.parent.name == 'object': @@ -611,8 +611,8 @@ def _check_signature(self, method1, refmethod, class_type): class_type is in 'class', 'interface' """ - if not (isinstance(method1, astng.Function) - and isinstance(refmethod, astng.Function)): + if not (isinstance(method1, astroid.Function) + and isinstance(refmethod, astroid.Function)): self.add_message('F0202', args=(method1, refmethod), node=method1) return # don't care about functions with unknown argument (builtins) @@ -632,7 +632,7 @@ def is_first_attr(self, node): """Check that attribute lookup name use first attribute variable name (self for method, cls for classmethod and mcs for metaclass). """ - return self._first_attrs and isinstance(node.expr, astng.Name) and \ + return self._first_attrs and isinstance(node.expr, astroid.Name) and \ node.expr.name == self._first_attrs[-1] def _ancestors_to_call(klass_node, method='__init__'): @@ -643,19 +643,19 @@ def _ancestors_to_call(klass_node, method='__init__'): for base_node in klass_node.ancestors(recurs=False): try: to_call[base_node] = base_node.igetattr(method).next() - except astng.InferenceError: + except astroid.InferenceError: continue return to_call def node_method(node, method_name): - """get astng for on the given class node, ensuring it + """get astroid for on the given class node, ensuring it is a Function node """ for n in node.local_attr(method_name): - if isinstance(n, astng.Function): + if isinstance(n, astroid.Function): return n - raise astng.NotFoundError(method_name) + raise astroid.NotFoundError(method_name) def register(linter): """required method to auto register this checker """ diff --git a/pylibs/pylama/checkers/pylint/checkers/design_analysis.py b/pylibs/pylama/checkers/pylint/checkers/design_analysis.py index 789cf88f..f81efc37 100644 --- a/pylibs/pylama/checkers/pylint/checkers/design_analysis.py +++ b/pylibs/pylama/checkers/pylint/checkers/design_analysis.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -13,17 +13,13 @@ # 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., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -"""check for signs of poor design +"""check for signs of poor design""" +from ..astroid import Function, If, InferenceError - see http://intranet.logilab.fr/jpl/view?rql=Any%20X%20where%20X%20eid%201243 - FIXME: missing 13, 15, 16 -""" - -from ..logilab.astng import Function, If, InferenceError - -from ..interfaces import IASTNGChecker -from ..checkers import BaseChecker +from ..interfaces import IAstroidChecker +from . import BaseChecker +from .utils import check_messages import re @@ -33,10 +29,9 @@ SPECIAL_METHODS = [('Context manager', set(('__enter__', '__exit__',))), ('Container', set(('__len__', - '__getitem__', - '__setitem__', - '__delitem__',))), - ('Callable', set(('__call__',))), + '__getitem__',))), + ('Mutable container', set(('__setitem__', + '__delitem__',))), ] class SpecialMethodChecker(object): @@ -60,8 +55,8 @@ def __call__(self, methods_required, protocol): required_methods_found = methods_required & self.methods_found if required_methods_found == methods_required: return True - if required_methods_found != set(): - required_methods_missing = methods_required - self.methods_found + if required_methods_found: + required_methods_missing = methods_required - self.methods_found self.on_error((protocol, ', '.join(sorted(required_methods_found)), ', '.join(sorted(required_methods_missing)))) @@ -83,11 +78,11 @@ def class_is_abstract(klass): 'R0901': ('Too many ancestors (%s/%s)', 'too-many-ancestors', 'Used when class has too many parent classes, try to reduce \ - this to get a more simple (and so easier to use) class.'), + this to get a simpler (and so easier to use) class.'), 'R0902': ('Too many instance attributes (%s/%s)', 'too-many-instance-attributes', 'Used when class has too many instance attributes, try to reduce \ - this to get a more simple (and so easier to use) class.'), + this to get a simpler (and so easier to use) class.'), 'R0903': ('Too few public methods (%s/%s)', 'too-few-public-methods', 'Used when class has too few public methods, so be sure it\'s \ @@ -95,7 +90,7 @@ def class_is_abstract(klass): 'R0904': ('Too many public methods (%s/%s)', 'too-many-public-methods', 'Used when class has too many public methods, try to reduce \ - this to get a more simple (and so easier to use) class.'), + this to get a simpler (and so easier to use) class.'), 'R0911': ('Too many return statements (%s/%s)', 'too-many-return-statements', @@ -139,7 +134,7 @@ class MisdesignChecker(BaseChecker): * size, complexity of functions, methods """ - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) # configuration section name name = 'design' @@ -166,7 +161,7 @@ class MisdesignChecker(BaseChecker): 'help': 'Maximum number of return / yield for function / ' 'method body'} ), - ('max-branchs', + ('max-branches', {'default' : 12, 'type' : 'int', 'metavar' : '', 'help': 'Maximum number of branch for function / method body'} ), @@ -208,7 +203,7 @@ def __init__(self, linter=None): BaseChecker.__init__(self, linter) self.stats = None self._returns = None - self._branchs = None + self._branches = None self._used_abstracts = None self._used_ifaces = None self._abstracts = None @@ -219,12 +214,13 @@ def open(self): """initialize visit variables""" self.stats = self.linter.add_stats() self._returns = [] - self._branchs = [] + self._branches = [] self._used_abstracts = {} self._used_ifaces = {} self._abstracts = [] self._ifaces = [] + # Check 'R0921', 'R0922', 'R0923' def close(self): """check that abstract/interface classes are used""" for abstract in self._abstracts: @@ -237,6 +233,7 @@ def close(self): if not iface in self._used_ifaces: self.add_message('R0923', node=iface) + @check_messages('R0901', 'R0902', 'R0903', 'R0904', 'R0921', 'R0922', 'R0923') def visit_class(self, node): """check size of inheritance hierarchy and number of instance attributes """ @@ -274,6 +271,7 @@ def visit_class(self, node): except KeyError: self._used_abstracts[parent] = 1 + @check_messages('R0901', 'R0902', 'R0903', 'R0904', 'R0921', 'R0922', 'R0923') def leave_class(self, node): """check number of public methods""" nb_public_methods = 0 @@ -304,6 +302,7 @@ def leave_class(self, node): args=(nb_public_methods, self.config.min_public_methods)) + @check_messages('R0911', 'R0912', 'R0913', 'R0914', 'R0915') def visit_function(self, node): """check function name, docstring, arguments, redefinition, variable names, max locals @@ -311,7 +310,7 @@ def visit_function(self, node): self._inc_branch() # init branch and returns counters self._returns.append(0) - self._branchs.append(0) + self._branches.append(0) # check number of arguments args = node.args.args if args is not None: @@ -332,6 +331,7 @@ def visit_function(self, node): # init statements counter self._stmts = 1 + @check_messages('R0911', 'R0912', 'R0913', 'R0914', 'R0915') def leave_function(self, node): """most of the work is done here on close: checks for max returns, branch, return in __init__ @@ -340,10 +340,10 @@ def leave_function(self, node): if returns > self.config.max_returns: self.add_message('R0911', node=node, args=(returns, self.config.max_returns)) - branchs = self._branchs.pop() - if branchs > self.config.max_branchs: + branches = self._branches.pop() + if branches > self.config.max_branches: self.add_message('R0912', node=node, - args=(branchs, self.config.max_branchs)) + args=(branches, self.config.max_branches)) # check number of statements if self._stmts > self.config.max_statements: self.add_message('R0915', node=node, @@ -363,42 +363,42 @@ def visit_default(self, node): self._stmts += 1 def visit_tryexcept(self, node): - """increments the branchs counter""" - branchs = len(node.handlers) + """increments the branches counter""" + branches = len(node.handlers) if node.orelse: - branchs += 1 - self._inc_branch(branchs) - self._stmts += branchs + branches += 1 + self._inc_branch(branches) + self._stmts += branches def visit_tryfinally(self, _): - """increments the branchs counter""" + """increments the branches counter""" self._inc_branch(2) self._stmts += 2 def visit_if(self, node): - """increments the branchs counter""" - branchs = 1 + """increments the branches counter""" + branches = 1 # don't double count If nodes coming from some 'elif' if node.orelse and (len(node.orelse)>1 or not isinstance(node.orelse[0], If)): - branchs += 1 - self._inc_branch(branchs) - self._stmts += branchs + branches += 1 + self._inc_branch(branches) + self._stmts += branches def visit_while(self, node): - """increments the branchs counter""" - branchs = 1 + """increments the branches counter""" + branches = 1 if node.orelse: - branchs += 1 - self._inc_branch(branchs) + branches += 1 + self._inc_branch(branches) visit_for = visit_while - def _inc_branch(self, branchsnum=1): - """increments the branchs counter""" - branchs = self._branchs - for i in xrange(len(branchs)): - branchs[i] += branchsnum + def _inc_branch(self, branchesnum=1): + """increments the branches counter""" + branches = self._branches + for i in xrange(len(branches)): + branches[i] += branchesnum # FIXME: make a nice report... diff --git a/pylibs/pylama/checkers/pylint/checkers/exceptions.py b/pylibs/pylama/checkers/pylint/checkers/exceptions.py index b2006cef..3031075f 100644 --- a/pylibs/pylama/checkers/pylint/checkers/exceptions.py +++ b/pylibs/pylama/checkers/pylint/checkers/exceptions.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2007 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # 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 @@ -18,12 +18,12 @@ from ..logilab.common.compat import builtins BUILTINS_NAME = builtins.__name__ -from ..logilab import astng -from ..logilab.astng import YES, Instance, unpack_infer +from .. import astroid +from ..astroid import YES, Instance, unpack_infer -from ..checkers import BaseChecker -from ..checkers.utils import is_empty, is_raising -from ..interfaces import IASTNGChecker +from . import BaseChecker +from .utils import is_empty, is_raising, check_messages +from ..interfaces import IAstroidChecker OVERGENERAL_EXCEPTIONS = ('Exception',) @@ -71,6 +71,12 @@ 'Used when the exception to catch is of the form \ "except A or B:". If intending to catch multiple, \ rewrite as "except (A, B):"'), + 'W0712': ('Implicit unpacking of exceptions is not supported in Python 3', + 'unpacking-in-except', + 'Python3 will not allow implicit unpacking of exceptions in except ' + 'clauses. ' + 'See http://www.python.org/dev/peps/pep-3110/', + {'maxversion': (3, 0)}), } @@ -85,7 +91,7 @@ class ExceptionsChecker(BaseChecker): * type of raise argument : string, Exceptions, other values """ - __implements__ = IASTNGChecker + __implements__ = IAstroidChecker name = 'exceptions' msgs = MSGS @@ -99,6 +105,7 @@ class ExceptionsChecker(BaseChecker): ), ) + @check_messages('W0701', 'W0710', 'E0702', 'E0710', 'E0711') def visit_raise(self, node): """visit raise possibly inferring value""" # ignore empty raise @@ -110,7 +117,7 @@ def visit_raise(self, node): else: try: value = unpack_infer(expr).next() - except astng.InferenceError: + except astroid.InferenceError: return self._check_raise_value(node, value) @@ -118,29 +125,29 @@ def _check_raise_value(self, node, expr): """check for bad values, string exception and class inheritance """ value_found = True - if isinstance(expr, astng.Const): + if isinstance(expr, astroid.Const): value = expr.value if isinstance(value, str): self.add_message('W0701', node=node) else: self.add_message('E0702', node=node, args=value.__class__.__name__) - elif (isinstance(expr, astng.Name) and \ + elif (isinstance(expr, astroid.Name) and \ expr.name in ('None', 'True', 'False')) or \ - isinstance(expr, (astng.List, astng.Dict, astng.Tuple, - astng.Module, astng.Function)): + isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, + astroid.Module, astroid.Function)): self.add_message('E0702', node=node, args=expr.name) - elif ( (isinstance(expr, astng.Name) and expr.name == 'NotImplemented') - or (isinstance(expr, astng.CallFunc) and - isinstance(expr.func, astng.Name) and + elif ( (isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') + or (isinstance(expr, astroid.CallFunc) and + isinstance(expr.func, astroid.Name) and expr.func.name == 'NotImplemented') ): self.add_message('E0711', node=node) - elif isinstance(expr, astng.BinOp) and expr.op == '%': + elif isinstance(expr, astroid.BinOp) and expr.op == '%': self.add_message('W0701', node=node) - elif isinstance(expr, (Instance, astng.Class)): + elif isinstance(expr, (Instance, astroid.Class)): if isinstance(expr, Instance): expr = expr._proxied - if (isinstance(expr, astng.Class) and + if (isinstance(expr, astroid.Class) and not inherit_from_std_ex(expr) and expr.root().name != BUILTINS_NAME): if expr.newstyle: @@ -154,6 +161,12 @@ def _check_raise_value(self, node, expr): return value_found + @check_messages('W0712') + def visit_excepthandler(self, node): + """Visit an except handler block and check for exception unpacking.""" + if isinstance(node.name, (astroid.Tuple, astroid.List)): + self.add_message('W0712', node=node) + @check_messages('W0702', 'W0703', 'W0704', 'W0711', 'E0701') def visit_tryexcept(self, node): """check for empty except""" exceptions_classes = [] @@ -171,19 +184,19 @@ def visit_tryexcept(self, node): msg = 'empty except clause should always appear last' self.add_message('E0701', node=node, args=msg) - elif isinstance(handler.type, astng.BoolOp): + elif isinstance(handler.type, astroid.BoolOp): self.add_message('W0711', node=handler, args=handler.type.op) else: try: excs = list(unpack_infer(handler.type)) - except astng.InferenceError: + except astroid.InferenceError: continue for exc in excs: # XXX skip other non class nodes - if exc is YES or not isinstance(exc, astng.Class): + if exc is YES or not isinstance(exc, astroid.Class): continue exc_ancestors = [anc for anc in exc.ancestors() - if isinstance(anc, astng.Class)] + if isinstance(anc, astroid.Class)] for previous_exc in exceptions_classes: if previous_exc in exc_ancestors: msg = '%s is an ancestor class of %s' % ( diff --git a/pylibs/pylama/checkers/pylint/checkers/format.py b/pylibs/pylama/checkers/pylint/checkers/format.py index 83da2613..bf53234a 100644 --- a/pylibs/pylama/checkers/pylint/checkers/format.py +++ b/pylibs/pylama/checkers/pylint/checkers/format.py @@ -27,12 +27,12 @@ raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") from ..logilab.common.textutils import pretty_match -from ..logilab.astng import nodes +from ..astroid import nodes -from ..interfaces import IRawChecker, IASTNGChecker -from ..checkers import BaseRawChecker -from ..checkers.utils import check_messages -from ..utils import WarningScope +from ..interfaces import ITokenChecker, IAstroidChecker +from . import BaseTokenChecker +from .utils import check_messages +from ..utils import WarningScope, OPTION_RGX MSGS = { 'C0301': ('Line too long (%s/%s)', @@ -42,7 +42,13 @@ 'too-many-lines', 'Used when a module has too much lines, reducing its readability.' ), - + 'C0303': ('Trailing whitespace', + 'trailing-whitespace', + 'Used when there is whitespace between the end of a line and the ' + 'newline.'), + 'C0304': ('Final newline missing', + 'missing-final-newline', + 'Used when the last line in a file is missing a newline.'), 'W0311': ('Bad indentation. Found %s %s, expected %s', 'bad-indentation', 'Used when an unexpected number of indentation\'s tabulations or ' @@ -163,7 +169,7 @@ def check_line(line): return msg_id, pretty_match(match, line.rstrip()) -class FormatChecker(BaseRawChecker): +class FormatChecker(BaseTokenChecker): """checks for : * unauthorized constructions * strict indentation @@ -171,7 +177,7 @@ class FormatChecker(BaseRawChecker): * use of <> instead of != """ - __implements__ = (IRawChecker, IASTNGChecker) + __implements__ = (ITokenChecker, IAstroidChecker) # configuration section name name = 'format' @@ -182,6 +188,11 @@ class FormatChecker(BaseRawChecker): options = (('max-line-length', {'default' : 80, 'type' : "int", 'metavar' : '', 'help' : 'Maximum number of characters on a single line.'}), + ('ignore-long-lines', + {'type': 'regexp', 'metavar': '', + 'default': r'^\s*(# )??$', + 'help': ('Regexp for a line that is allowed to be longer than ' + 'the limit.')}), ('max-module-lines', {'default' : 1000, 'type' : 'int', 'metavar' : '', 'help': 'Maximum number of lines in a module'} @@ -192,22 +203,10 @@ class FormatChecker(BaseRawChecker): " " (4 spaces) or "\\t" (1 tab).'}), ) def __init__(self, linter=None): - BaseRawChecker.__init__(self, linter) + BaseTokenChecker.__init__(self, linter) self._lines = None self._visited_lines = None - def process_module(self, node): - """extracts encoding from the stream and decodes each line, so that - international text's length is properly calculated. - """ - stream = node.file_stream - stream.seek(0) # XXX may be removed with astng > 0.23 - readline = stream.readline - if sys.version_info < (3, 0): - if node.file_encoding is not None: - readline = lambda: stream.readline().decode(node.file_encoding, 'replace') - self.process_tokens(tokenize.generate_tokens(readline)) - def new_line(self, tok_type, line, line_num, junk): """a new line has been encountered, process it if necessary""" if not tok_type in junk: @@ -233,13 +232,24 @@ def process_tokens(self, tokens): previous = None self._lines = {} self._visited_lines = {} + new_line_delay = False for (tok_type, token, start, _, line) in tokens: + if new_line_delay: + new_line_delay = False + self.new_line(tok_type, line, line_num, junk) if start[0] != line_num: if previous is not None and previous[0] == tokenize.OP and previous[1] == ';': self.add_message('W0301', line=previous[2]) previous = None line_num = start[0] - self.new_line(tok_type, line, line_num, junk) + # A tokenizer oddity: if an indented line contains a multi-line + # docstring, the line member of the INDENT token does not contain + # the full line; therefore we delay checking the new line until + # the next token. + if tok_type == tokenize.INDENT: + new_line_delay = True + else: + self.new_line(tok_type, line, line_num, junk) if tok_type not in (indent, dedent, newline) + junk: previous = tok_type, token, start[0] @@ -338,8 +348,22 @@ def check_lines(self, lines, i): """check lines have less than a maximum number of characters """ max_chars = self.config.max_line_length - for line in lines.splitlines(): - if len(line) > max_chars: + ignore_long_line = self.config.ignore_long_lines + + for line in lines.splitlines(True): + if not line.endswith('\n'): + self.add_message('C0304', line=i) + else: + stripped_line = line.rstrip() + if line != stripped_line + '\n': + self.add_message('C0303', line=i) + # Don't count excess whitespace in the line length. + line = stripped_line + mobj = OPTION_RGX.search(line) + if mobj and mobj.group(1).split('=', 1)[0].strip() == 'disable': + line = line.split('#')[0].rstrip() + + if len(line) > max_chars and not ignore_long_line.search(line): self.add_message('C0301', line=i, args=(len(line), max_chars)) i += 1 diff --git a/pylibs/pylama/checkers/pylint/checkers/imports.py b/pylibs/pylama/checkers/pylint/checkers/imports.py index e243ff6b..3c321bbb 100644 --- a/pylibs/pylama/checkers/pylint/checkers/imports.py +++ b/pylibs/pylama/checkers/pylint/checkers/imports.py @@ -1,4 +1,4 @@ -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -19,11 +19,13 @@ from ..logilab.common.modutils import is_standard_module from ..logilab.common.ureports import VerbatimText, Paragraph -from ..logilab import astng -from ..logilab.astng import are_exclusive +from .. import astroid +from ..astroid import are_exclusive -from ..interfaces import IASTNGChecker -from ..checkers import BaseChecker, EmptyReport +from ..interfaces import IAstroidChecker +from ..utils import EmptyReport +from . import BaseChecker +from .utils import check_messages def get_first_import(node, context, name, base, level): @@ -38,11 +40,11 @@ def get_first_import(node, context, name, base, level): continue if first.scope() is node.scope() and first.fromlineno > node.fromlineno: continue - if isinstance(first, astng.Import): + if isinstance(first, astroid.Import): if any(fullname == iname[0] for iname in first.names): found = True break - elif isinstance(first, astng.From): + elif isinstance(first, astroid.From): if level == first.level and any( fullname == '%s.%s' % (first.modname, iname[0]) for iname in first.names): found = True @@ -157,15 +159,14 @@ class ImportsChecker(BaseChecker): * uses of deprecated modules """ - __implements__ = IASTNGChecker + __implements__ = IAstroidChecker name = 'imports' msgs = MSGS priority = -2 options = (('deprecated-modules', - {'default' : ('regsub', 'string', 'TERMIOS', - 'Bastion', 'rexec'), + {'default' : ('regsub', 'TERMIOS', 'Bastion', 'rexec'), 'type' : 'csv', 'metavar' : '', 'help' : 'Deprecated modules which should not be used, \ @@ -232,7 +233,9 @@ def visit_import(self, node): self._check_deprecated_module(node, name) self._check_reimport(node, name) - + # TODO This appears to be the list of all messages of the checker... + # @check_messages('W0410', 'W0401', 'W0403', 'W0402', 'W0404', 'W0406', 'F0401') + @check_messages(*(MSGS.keys())) def visit_from(self, node): """triggered when a from statement is seen""" basename = node.modname @@ -241,7 +244,7 @@ def visit_from(self, node): prev = node.previous_sibling() if prev: # consecutive future statements are possible - if not (isinstance(prev, astng.From) + if not (isinstance(prev, astroid.From) and prev.modname == '__future__'): self.add_message('W0410', node=node) return @@ -261,7 +264,7 @@ def visit_from(self, node): def get_imported_module(self, modnode, importnode, modname): try: return importnode.do_import_module(modname) - except astng.InferenceError, ex: + except astroid.InferenceError, ex: if str(ex) != modname: args = '%r (%s)' % (modname, ex) else: diff --git a/pylibs/pylama/checkers/pylint/checkers/logging.py b/pylibs/pylama/checkers/pylint/checkers/logging.py index bb95a539..5f2381ca 100644 --- a/pylibs/pylama/checkers/pylint/checkers/logging.py +++ b/pylibs/pylama/checkers/pylint/checkers/logging.py @@ -14,11 +14,9 @@ """checker for use of Python logging """ -from ..logilab import astng -from .. import checkers +from .. import astroid +from . import BaseChecker, utils from .. import interfaces -from ..checkers import utils - MSGS = { 'W1201': ('Specify string format arguments as logging function parameters', @@ -54,10 +52,10 @@ 'warning']) -class LoggingChecker(checkers.BaseChecker): +class LoggingChecker(BaseChecker): """Checks use of the logging module.""" - __implements__ = interfaces.IASTNGChecker + __implements__ = interfaces.IAstroidChecker name = 'logging' msgs = MSGS @@ -77,18 +75,19 @@ def visit_import(self, node): else: self._logging_name = 'logging' + @utils.check_messages(*(MSGS.keys())) def visit_callfunc(self, node): """Checks calls to (simple forms of) logging methods.""" - if (not isinstance(node.func, astng.Getattr) - or not isinstance(node.func.expr, astng.Name)): + if (not isinstance(node.func, astroid.Getattr) + or not isinstance(node.func.expr, astroid.Name)): return try: logger_class = [inferred for inferred in node.func.expr.infer() if ( - isinstance(inferred, astng.Instance) + isinstance(inferred, astroid.Instance) and any(ancestor for ancestor in inferred._proxied.ancestors() if ( ancestor.name == 'Logger' and ancestor.parent.name == 'logging')))] - except astng.exceptions.InferenceError: + except astroid.exceptions.InferenceError: return if (node.func.expr.name != self._logging_name and not logger_class): return @@ -103,9 +102,9 @@ def _check_convenience_methods(self, node): # Either no args, star args, or double-star args. Beyond the # scope of this checker. return - if isinstance(node.args[0], astng.BinOp) and node.args[0].op == '%': + if isinstance(node.args[0], astroid.BinOp) and node.args[0].op == '%': self.add_message('W1201', node=node) - elif isinstance(node.args[0], astng.Const): + elif isinstance(node.args[0], astroid.Const): self._check_format_string(node, 0) def _check_log_methods(self, node): @@ -116,9 +115,9 @@ def _check_log_methods(self, node): # Either a malformed call, star args, or double-star args. Beyond # the scope of this checker. return - if isinstance(node.args[1], astng.BinOp) and node.args[1].op == '%': + if isinstance(node.args[1], astroid.BinOp) and node.args[1].op == '%': self.add_message('W1201', node=node) - elif isinstance(node.args[1], astng.Const): + elif isinstance(node.args[1], astroid.Const): self._check_format_string(node, 1) def _check_format_string(self, node, format_arg): @@ -171,7 +170,7 @@ def _count_supplied_tokens(self, args): Returns: Number of AST nodes that aren't keywords. """ - return sum(1 for arg in args if not isinstance(arg, astng.Keyword)) + return sum(1 for arg in args if not isinstance(arg, astroid.Keyword)) def register(linter): diff --git a/pylibs/pylama/checkers/pylint/checkers/misc.py b/pylibs/pylama/checkers/pylint/checkers/misc.py index 8830b8fd..dee53e62 100644 --- a/pylibs/pylama/checkers/pylint/checkers/misc.py +++ b/pylibs/pylama/checkers/pylint/checkers/misc.py @@ -20,19 +20,25 @@ import re from ..interfaces import IRawChecker -from ..checkers import BaseChecker +from . import BaseChecker MSGS = { 'W0511': ('%s', 'fixme', 'Used when a warning note as FIXME or XXX is detected.'), + 'W0512': ('Cannot decode using encoding "%s", unexpected byte at position %d', + 'invalid-encoded-data', + 'Used when a source line cannot be decoded using the specified ' + 'source file encoding.', + {'maxversion': (3, 0)}), } + class EncodingChecker(BaseChecker): """checks for: * warning notes in the code like FIXME, XXX - * PEP 263: source code with non ascii character but no encoding declaration + * encoding issues. """ __implements__ = IRawChecker @@ -48,30 +54,36 @@ class EncodingChecker(BaseChecker): }), ) - def __init__(self, linter=None): - BaseChecker.__init__(self, linter) + def _check_note(self, notes, lineno, line): + match = notes.search(line) + if match: + self.add_message('W0511', args=line[match.start():-1], line=lineno) + + def _check_encoding(self, lineno, line, file_encoding): + try: + return unicode(line, file_encoding) + except UnicodeDecodeError, ex: + self.add_message('W0512', line=lineno, + args=(file_encoding, ex.args[2])) - def process_module(self, node): - """inspect the source file to found encoding problem or fixmes like + def process_module(self, module): + """inspect the source file to find encoding problem or fixmes like notes """ - stream = node.file_stream - stream.seek(0) # XXX may be removed with astng > 0.23 - # warning notes in the code - notes = [] - for note in self.config.notes: - notes.append(re.compile(note)) - linenum = 1 - for line in stream.readlines(): - for note in notes: - match = note.search(line) - if match: - self.add_message('W0511', args=line[match.start():-1], - line=linenum) - break - linenum += 1 - - + stream = module.file_stream + stream.seek(0) # XXX may be removed with astroid > 0.23 + if self.config.notes: + notes = re.compile('|'.join(self.config.notes)) + else: + notes = None + if module.file_encoding: + encoding = module.file_encoding + else: + encoding = 'ascii' + for lineno, line in enumerate(stream): + line = self._check_encoding(lineno+1, line, encoding) + if line is not None and notes: + self._check_note(notes, lineno+1, line) def register(linter): """required method to auto register this checker""" diff --git a/pylibs/pylama/checkers/pylint/checkers/newstyle.py b/pylibs/pylama/checkers/pylint/checkers/newstyle.py index c72f8d0d..715282a9 100644 --- a/pylibs/pylama/checkers/pylint/checkers/newstyle.py +++ b/pylibs/pylama/checkers/pylint/checkers/newstyle.py @@ -15,12 +15,13 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """check for new / old style related problems """ +import sys -from ..logilab import astng +from .. import astroid -from ..interfaces import IASTNGChecker -from ..checkers import BaseChecker -from ..checkers.utils import check_messages +from ..interfaces import IAstroidChecker +from . import BaseChecker +from .utils import check_messages MSGS = { 'E1001': ('Use of __slots__ on an old style class', @@ -29,15 +30,23 @@ 'E1002': ('Use of super on an old style class', 'super-on-old-class', 'Used when an old style class uses the super builtin.'), - 'E1003': ('Bad first argument %r given to super class', + 'E1003': ('Bad first argument %r given to super()', 'bad-super-call', 'Used when another argument than the current class is given as \ first argument of the super builtin.'), + 'E1004': ('Missing argument to super()', + 'missing-super-argument', + 'Used when the super builtin didn\'t receive an \ + argument on Python 2'), 'W1001': ('Use of "property" on an old style class', 'property-on-old-class', 'Used when PyLint detect the use of the builtin "property" \ on an old style class while this is relying on new style \ classes features'), + 'C1001': ('Old-style class defined.', + 'old-style-class', + 'Used when a class is defined that does not inherit from another' + 'class and does not inherit explicitly from "object".') } @@ -48,7 +57,7 @@ class NewStyleConflictChecker(BaseChecker): * "super" usage """ - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) # configuration section name name = 'newstyle' @@ -58,53 +67,67 @@ class NewStyleConflictChecker(BaseChecker): # configuration options options = () - @check_messages('E1001') + @check_messages('E1001', 'C1001') def visit_class(self, node): """check __slots__ usage """ if '__slots__' in node and not node.newstyle: self.add_message('E1001', node=node) + # The node type could be class, exception, metaclass, or + # interface. Presumably, the non-class-type nodes would always + # have an explicit base class anyway. + if not node.bases and node.type == 'class': + self.add_message('C1001', node=node) @check_messages('W1001') def visit_callfunc(self, node): """check property usage""" parent = node.parent.frame() - if (isinstance(parent, astng.Class) and + if (isinstance(parent, astroid.Class) and not parent.newstyle and - isinstance(node.func, astng.Name)): + isinstance(node.func, astroid.Name)): name = node.func.name if name == 'property': self.add_message('W1001', node=node) - @check_messages('E1002', 'E1003') + @check_messages('E1002', 'E1003', 'E1004') def visit_function(self, node): """check use of super""" # ignore actual functions or method within a new style class if not node.is_method(): return klass = node.parent.frame() - for stmt in node.nodes_of_class(astng.CallFunc): + for stmt in node.nodes_of_class(astroid.CallFunc): expr = stmt.func - if not isinstance(expr, astng.Getattr): + if not isinstance(expr, astroid.Getattr): continue call = expr.expr # skip the test if using super - if isinstance(call, astng.CallFunc) and \ - isinstance(call.func, astng.Name) and \ + if isinstance(call, astroid.CallFunc) and \ + isinstance(call.func, astroid.Name) and \ call.func.name == 'super': if not klass.newstyle: # super should not be used on an old style class self.add_message('E1002', node=node) else: # super first arg should be the class + if not call.args and sys.version_info[0] == 3: + # unless Python 3 + continue + try: supcls = (call.args and call.args[0].infer().next() or None) - except astng.InferenceError: + except astroid.InferenceError: continue + + if supcls is None and sys.version_info[0] == 2: + self.add_message('missing-super-argument', node=call) + continue + if klass is not supcls: supcls = getattr(supcls, 'name', supcls) - self.add_message('E1003', node=node, args=supcls) + self.add_message('E1003', node=call, args=(supcls, )) def register(linter): diff --git a/pylibs/pylama/checkers/pylint/checkers/raw_metrics.py b/pylibs/pylama/checkers/pylint/checkers/raw_metrics.py index 9f270357..4d450da1 100644 --- a/pylibs/pylama/checkers/pylint/checkers/raw_metrics.py +++ b/pylibs/pylama/checkers/pylint/checkers/raw_metrics.py @@ -1,3 +1,6 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# # 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 @@ -24,8 +27,9 @@ from ..logilab.common.ureports import Table -from ..interfaces import IRawChecker -from ..checkers import BaseRawChecker, EmptyReport +from ..interfaces import ITokenChecker +from ..utils import EmptyReport +from ..checkers import BaseTokenChecker from ..reporters import diff_string def report_raw_stats(sect, stats, old_stats): @@ -50,7 +54,7 @@ def report_raw_stats(sect, stats, old_stats): sect.append(Table(children=lines, cols=5, rheaders=1)) -class RawMetricsChecker(BaseRawChecker): +class RawMetricsChecker(BaseTokenChecker): """does not check anything but gives some raw metrics : * total number of lines * total number of code lines @@ -59,7 +63,7 @@ class RawMetricsChecker(BaseRawChecker): * total number of empty lines """ - __implements__ = (IRawChecker,) + __implements__ = (ITokenChecker,) # configuration section name name = 'metrics' @@ -71,7 +75,7 @@ class RawMetricsChecker(BaseRawChecker): reports = ( ('RP0701', 'Raw metrics', report_raw_stats), ) def __init__(self, linter): - BaseRawChecker.__init__(self, linter) + BaseTokenChecker.__init__(self, linter) self.stats = None def open(self): diff --git a/pylibs/pylama/checkers/pylint/checkers/similar.py b/pylibs/pylama/checkers/pylint/checkers/similar.py index 40d8a123..f22116e9 100644 --- a/pylibs/pylama/checkers/pylint/checkers/similar.py +++ b/pylibs/pylama/checkers/pylint/checkers/similar.py @@ -1,5 +1,5 @@ # pylint: disable=W0622 -# Copyright (c) 2004-2012 LOGILAB S.A. (Paris, FRANCE). +# Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE). # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This program is free software; you can redistribute it and/or modify it under @@ -22,10 +22,10 @@ from ..logilab.common.ureports import Table from ..interfaces import IRawChecker -from ..checkers import BaseChecker, table_lines_from_stats +from . import BaseChecker, table_lines_from_stats -class Similar: +class Similar(object): """finds copy-pasted lines of code in a project""" def __init__(self, min_lines=4, ignore_comments=False, @@ -36,14 +36,21 @@ def __init__(self, min_lines=4, ignore_comments=False, self.ignore_imports = ignore_imports self.linesets = [] - def append_stream(self, streamid, stream): + def append_stream(self, streamid, stream, encoding=None): """append a file to search for similarities""" - stream.seek(0) # XXX may be removed with astng > 0.23 - self.linesets.append(LineSet(streamid, - stream.readlines(), - self.ignore_comments, - self.ignore_docstrings, - self.ignore_imports)) + stream.seek(0) # XXX may be removed with astroid > 0.23 + if encoding is None: + readlines = stream.readlines + else: + readlines = lambda: [line.decode(encoding) for line in stream] + try: + self.linesets.append(LineSet(streamid, + readlines(), + self.ignore_comments, + self.ignore_docstrings, + self.ignore_imports)) + except UnicodeDecodeError: + pass def run(self): """start looking for similarities and display results on stdout""" @@ -80,7 +87,7 @@ def _display_sims(self, sims): print "==%s:%s" % (lineset.name, idx) # pylint: disable=W0631 for line in lineset._real_lines[idx:idx+num]: - print " ", line, + print " ", line.rstrip() nb_lignes_dupliquees += num * (len(couples)-1) nb_total_lignes = sum([len(lineset) for lineset in self.linesets]) print "TOTAL lines=%s duplicates=%s percent=%.2f" \ @@ -153,7 +160,8 @@ def stripped_lines(lines, ignore_comments, ignore_docstrings, ignore_imports): strippedlines.append(line) return strippedlines -class LineSet: + +class LineSet(object): """Holds and indexes all the lines of a single source file""" def __init__(self, name, lines, ignore_comments=False, ignore_docstrings=False, ignore_imports=False): @@ -288,7 +296,7 @@ def process_module(self, node): stream must implement the readlines method """ - self.append_stream(self.linter.current_name, node.file_stream) + self.append_stream(self.linter.current_name, node.file_stream, node.file_encoding) def close(self): """compute and display similarities on closing (i.e. end of parsing)""" diff --git a/pylibs/pylama/checkers/pylint/checkers/stdlib.py b/pylibs/pylama/checkers/pylint/checkers/stdlib.py new file mode 100644 index 00000000..51450983 --- /dev/null +++ b/pylibs/pylama/checkers/pylint/checkers/stdlib.py @@ -0,0 +1,68 @@ +# Copyright 2012 Google Inc. +# +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""Checkers for various standard library functions.""" + +import re +import sys + +from .. import astroid + +from ..interfaces import IAstroidChecker +from . import BaseChecker, BaseTokenChecker, utils + +_VALID_OPEN_MODE_REGEX = r'^(r?U|[rwa]\+?b?)$' + +if sys.version_info >= (3, 0): + OPEN_MODULE = '_io' +else: + OPEN_MODULE = '__builtin__' + +class OpenModeChecker(BaseChecker): + __implements__ = (IAstroidChecker,) + name = 'open_mode' + + msgs = { + 'W1501': ('"%s" is not a valid mode for open.', + 'bad-open-mode', + 'Python supports: r, w, a modes with b, +, and U options. ' + 'See http://docs.python.org/2/library/functions.html#open'), + } + + @utils.check_messages('W1501') + def visit_callfunc(self, node): + """Visit a CallFunc node.""" + if hasattr(node, 'func'): + infer = utils.safe_infer(node.func) + if infer and infer.root().name == OPEN_MODULE: + if getattr(node.func, 'name', None) in ('open', 'file'): + self._check_open_mode(node) + + def _check_open_mode(self, node): + """Check that the mode argument of an open or file call is valid.""" + try: + mode_arg = utils.get_argument_from_call(node, position=1, keyword='mode') + if mode_arg: + mode_arg = utils.safe_infer(mode_arg) + if (isinstance(mode_arg, astroid.Const) + and not re.match(_VALID_OPEN_MODE_REGEX, mode_arg.value)): + self.add_message('W1501', node=node, args=(mode_arg.value)) + except (utils.NoSuchArgumentError, TypeError): + pass + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(OpenModeChecker(linter)) + diff --git a/pylibs/pylama/checkers/pylint/checkers/strings.py b/pylibs/pylama/checkers/pylint/checkers/strings.py index 3c755dd7..3d5e8051 100644 --- a/pylibs/pylama/checkers/pylint/checkers/strings.py +++ b/pylibs/pylama/checkers/pylint/checkers/strings.py @@ -21,11 +21,10 @@ import sys import tokenize -from ..logilab import astng +from .. import astroid -from ..interfaces import IRawChecker, IASTNGChecker -from ..checkers import BaseChecker, BaseRawChecker -from ..checkers import utils +from ..interfaces import ITokenChecker, IAstroidChecker +from . import BaseChecker, BaseTokenChecker, utils _PY3K = sys.version_info >= (3, 0) @@ -72,26 +71,27 @@ specifiers is given too many arguments"), } -OTHER_NODES = (astng.Const, astng.List, astng.Backquote, - astng.Lambda, astng.Function, - astng.ListComp, astng.SetComp, astng.GenExpr) +OTHER_NODES = (astroid.Const, astroid.List, astroid.Backquote, + astroid.Lambda, astroid.Function, + astroid.ListComp, astroid.SetComp, astroid.GenExpr) class StringFormatChecker(BaseChecker): """Checks string formatting operations to ensure that the format string is valid and the arguments match the format string. """ - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) name = 'string' msgs = MSGS + @utils.check_messages(*(MSGS.keys())) def visit_binop(self, node): if node.op != '%': return left = node.left args = node.right - if not (isinstance(left, astng.Const) + if not (isinstance(left, astroid.Const) and isinstance(left.value, basestring)): return format_string = left.value @@ -114,11 +114,11 @@ def visit_binop(self, node): # Check that the RHS of the % operator is a mapping object # that contains precisely the set of keys required by the # format string. - if isinstance(args, astng.Dict): + if isinstance(args, astroid.Dict): keys = set() unknown_keys = False for k, _ in args.items: - if isinstance(k, astng.Const): + if isinstance(k, astroid.Const): key = k.value if isinstance(key, basestring): keys.add(key) @@ -137,7 +137,7 @@ def visit_binop(self, node): for key in keys: if key not in required_keys: self.add_message('W1301', node=node, args=key) - elif isinstance(args, OTHER_NODES + (astng.Tuple,)): + elif isinstance(args, OTHER_NODES + (astroid.Tuple,)): type_name = type(args).__name__ self.add_message('E1303', node=node, args=type_name) # else: @@ -149,9 +149,9 @@ def visit_binop(self, node): # Check that the number of arguments passed to the RHS of # the % operator matches the number required by the format # string. - if isinstance(args, astng.Tuple): + if isinstance(args, astroid.Tuple): num_args = len(args.elts) - elif isinstance(args, OTHER_NODES + (astng.Dict, astng.DictComp)): + elif isinstance(args, OTHER_NODES + (astroid.Dict, astroid.DictComp)): num_args = 1 else: # The RHS of the format specifier is a name or @@ -166,7 +166,7 @@ def visit_binop(self, node): class StringMethodsChecker(BaseChecker): - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) name = 'string' msgs = { 'E1310': ("Suspicious argument in %s.%s call", @@ -175,24 +175,25 @@ class StringMethodsChecker(BaseChecker): " duplicate character, "), } + @utils.check_messages(*(MSGS.keys())) def visit_callfunc(self, node): func = utils.safe_infer(node.func) - if (isinstance(func, astng.BoundMethod) - and isinstance(func.bound, astng.Instance) + if (isinstance(func, astroid.BoundMethod) + and isinstance(func.bound, astroid.Instance) and func.bound.name in ('str', 'unicode', 'bytes') and func.name in ('strip', 'lstrip', 'rstrip') and node.args): arg = utils.safe_infer(node.args[0]) - if not isinstance(arg, astng.Const): + if not isinstance(arg, astroid.Const): return if len(arg.value) != len(set(arg.value)): self.add_message('E1310', node=node, args=(func.bound.name, func.name)) -class StringConstantChecker(BaseRawChecker): +class StringConstantChecker(BaseTokenChecker): """Check string literals""" - __implements__ = (IRawChecker, IASTNGChecker) + __implements__ = (ITokenChecker,) name = 'string_constant' msgs = { 'W1401': ('Anomalous backslash in string: \'%s\'. ' diff --git a/pylibs/pylama/checkers/pylint/checkers/typecheck.py b/pylibs/pylama/checkers/pylint/checkers/typecheck.py index 4cdc6006..e8938827 100644 --- a/pylibs/pylama/checkers/pylint/checkers/typecheck.py +++ b/pylibs/pylama/checkers/pylint/checkers/typecheck.py @@ -13,18 +13,18 @@ # 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., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -"""try to find more bugs in the code using astng inference capabilities +"""try to find more bugs in the code using astroid inference capabilities """ import re import shlex -from ..logilab import astng -from ..logilab.astng import InferenceError, NotFoundError, YES, Instance +from .. import astroid +from ..astroid import InferenceError, NotFoundError, YES, Instance -from ..interfaces import IASTNGChecker -from ..checkers import BaseChecker -from ..checkers.utils import safe_infer, is_super, check_messages +from ..interfaces import IAstroidChecker +from . import BaseChecker +from .utils import safe_infer, is_super, check_messages MSGS = { 'E1101': ('%s %r has no %r member', @@ -37,7 +37,7 @@ 'E1103': ('%s %r has no %r member (but some types could not be inferred)', 'maybe-no-member', 'Used when a variable is accessed for an unexistent member, but \ - astng was not able to interpret all possible types of this \ + astroid was not able to interpret all possible types of this \ variable.'), 'E1111': ('Assigning to function call which doesn\'t return', 'assignment-from-no-return', @@ -58,7 +58,8 @@ 'E1122': ('Duplicate keyword argument %r in function call', 'duplicate-keyword-arg', 'Used when a function call passes the same keyword argument \ - multiple times.'), + multiple times.', + {'maxversion': (2, 6)}), 'E1123': ('Passing unexpected keyword argument %r in function call', 'unexpected-keyword-arg', 'Used when a function call passes a keyword argument that \ @@ -68,13 +69,18 @@ 'Used when a function call would result in assigning multiple \ values to a function parameter, one value from a positional \ argument and one from a keyword argument.'), + 'E1125': ('Missing mandatory keyword argument %r', + 'missing-kwoa', + 'Used when a function call doesn\'t pass a mandatory \ + keyword-only argument.', + {'minversion': (3, 0)}), } class TypeChecker(BaseChecker): """try to find bugs in the code using type inference """ - __implements__ = (IASTNGChecker,) + __implements__ = (IAstroidChecker,) # configuration section name name = 'typecheck' @@ -120,7 +126,7 @@ def open(self): self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent')) def visit_assattr(self, node): - if isinstance(node.ass_type(), astng.AugAssign): + if isinstance(node.ass_type(), astroid.AugAssign): self.visit_getattr(node) def visit_delattr(self, node): @@ -162,7 +168,7 @@ def visit_getattr(self, node): inference_failure = True continue # skip None anyway - if isinstance(owner, astng.Const) and owner.value is None: + if isinstance(owner, astroid.Const) and owner.value is None: continue # XXX "super" / metaclass call if is_super(owner) or getattr(owner, 'type', None) == 'metaclass': @@ -174,14 +180,14 @@ def visit_getattr(self, node): continue try: if not [n for n in owner.getattr(node.attrname) - if not isinstance(n.statement(), astng.AugAssign)]: + if not isinstance(n.statement(), astroid.AugAssign)]: missingattr.add((owner, name)) continue except AttributeError: # XXX method / function continue except NotFoundError: - if isinstance(owner, astng.Function) and owner.decorators: + if isinstance(owner, astroid.Function) and owner.decorators: continue if isinstance(owner, Instance) and owner.has_dynamic_getattr(): continue @@ -212,33 +218,34 @@ def visit_getattr(self, node): args=(owner.display_type(), name, node.attrname)) - + @check_messages('E1111', 'W1111') def visit_assign(self, node): """check that if assigning to a function call, the function is possibly returning something valuable """ - if not isinstance(node.value, astng.CallFunc): + if not isinstance(node.value, astroid.CallFunc): return function_node = safe_infer(node.value.func) # skip class, generator and incomplete function definition - if not (isinstance(function_node, astng.Function) and + if not (isinstance(function_node, astroid.Function) and function_node.root().fully_defined()): return if function_node.is_generator() \ or function_node.is_abstract(pass_is_abstract=False): return - returns = list(function_node.nodes_of_class(astng.Return, - skip_klass=astng.Function)) + returns = list(function_node.nodes_of_class(astroid.Return, + skip_klass=astroid.Function)) if len(returns) == 0: self.add_message('E1111', node=node) else: for rnode in returns: - if not (isinstance(rnode.value, astng.Const) + if not (isinstance(rnode.value, astroid.Const) and rnode.value.value is None): break else: self.add_message('W1111', node=node) + @check_messages(*(MSGS.keys())) def visit_callfunc(self, node): """check that called functions/methods are inferred to callable objects, and that the arguments passed to the function match the parameters in @@ -250,7 +257,7 @@ def visit_callfunc(self, node): keyword_args = set() num_positional_args = 0 for arg in node.args: - if isinstance(arg, astng.Keyword): + if isinstance(arg, astroid.Keyword): keyword = arg.arg if keyword in keyword_args: self.add_message('E1122', node=node, args=keyword) @@ -265,18 +272,18 @@ def visit_callfunc(self, node): # Note that BoundMethod is a subclass of UnboundMethod (huh?), so must # come first in this 'if..else'. - if isinstance(called, astng.BoundMethod): + if isinstance(called, astroid.BoundMethod): # Bound methods have an extra implicit 'self' argument. num_positional_args += 1 - elif isinstance(called, astng.UnboundMethod): + elif isinstance(called, astroid.UnboundMethod): if called.decorators is not None: for d in called.decorators.nodes: - if isinstance(d, astng.Name) and (d.name == 'classmethod'): + if isinstance(d, astroid.Name) and (d.name == 'classmethod'): # Class methods have an extra implicit 'cls' argument. num_positional_args += 1 break - elif (isinstance(called, astng.Function) or - isinstance(called, astng.Lambda)): + elif (isinstance(called, astroid.Function) or + isinstance(called, astroid.Lambda)): pass else: return @@ -295,15 +302,15 @@ def visit_callfunc(self, node): parameters = [] parameter_name_to_index = {} for i, arg in enumerate(called.args.args): - if isinstance(arg, astng.Tuple): + if isinstance(arg, astroid.Tuple): name = None # Don't store any parameter names within the tuple, since those # are not assignable from keyword arguments. else: - if isinstance(arg, astng.Keyword): + if isinstance(arg, astroid.Keyword): name = arg.arg else: - assert isinstance(arg, astng.AssName) + assert isinstance(arg, astroid.AssName) # This occurs with: # def f( (a), (b) ): pass name = arg.name @@ -314,6 +321,15 @@ def visit_callfunc(self, node): defval = None parameters.append([(name, defval), False]) + kwparams = {} + for i, arg in enumerate(called.args.kwonlyargs): + if isinstance(arg, astroid.Keyword): + name = arg.arg + else: + assert isinstance(arg, astroid.AssName) + name = arg.name + kwparams[name] = [called.args.kw_defaults[i], False] + # Match the supplied arguments against the function parameters. # 1. Match the positional arguments. @@ -338,6 +354,12 @@ def visit_callfunc(self, node): self.add_message('E1124', node=node, args=keyword) else: parameters[i][1] = True + elif keyword in kwparams: + if kwparams[keyword][1]: # XXX is that even possible? + # Duplicate definition of function parameter. + self.add_message('E1124', node=node, args=keyword) + else: + kwparams[keyword][1] = True elif called.args.kwarg is not None: # The keyword argument gets assigned to the **kwargs parameter. pass @@ -382,6 +404,12 @@ def visit_callfunc(self, node): display_name = repr(name) self.add_message('E1120', node=node, args=display_name) + for name in kwparams: + defval, assigned = kwparams[name] + if defval is None and not assigned: + self.add_message('E1125', node=node, args=name) + + def register(linter): """required method to auto register this checker """ linter.register_checker(TypeChecker(linter)) diff --git a/pylibs/pylama/checkers/pylint/checkers/utils.py b/pylibs/pylama/checkers/pylint/checkers/utils.py index 34d335bb..1a7dca9e 100644 --- a/pylibs/pylama/checkers/pylint/checkers/utils.py +++ b/pylibs/pylama/checkers/pylint/checkers/utils.py @@ -21,19 +21,23 @@ import re import string -from ..logilab import astng -from ..logilab.astng import scoped_nodes +from .. import astroid + +from ..astroid import scoped_nodes from ..logilab.common.compat import builtins BUILTINS_NAME = builtins.__name__ -COMP_NODE_TYPES = astng.ListComp, astng.SetComp, astng.DictComp, astng.GenExpr +COMP_NODE_TYPES = astroid.ListComp, astroid.SetComp, astroid.DictComp, astroid.GenExpr + +class NoSuchArgumentError(Exception): + pass def is_inside_except(node): """Returns true if node is inside the name of an except handler.""" current = node - while current and not isinstance(current.parent, astng.ExceptHandler): + while current and not isinstance(current.parent, astroid.ExceptHandler): current = current.parent return current and current is current.parent.name @@ -41,7 +45,7 @@ def is_inside_except(node): def get_all_elements(node): """Recursively returns all atoms in nested lists and tuples.""" - if isinstance(node, (astng.Tuple, astng.List)): + if isinstance(node, (astroid.Tuple, astroid.List)): for child in node.elts: for e in get_all_elements(child): yield e @@ -56,9 +60,9 @@ def clobber_in_except(node): Returns (True, args for W0623) if assignment clobbers an existing variable, (False, None) otherwise. """ - if isinstance(node, astng.AssAttr): + if isinstance(node, astroid.AssAttr): return (True, (node.attrname, 'object %r' % (node.expr.name,))) - elif isinstance(node, astng.AssName): + elif isinstance(node, astroid.AssName): name = node.name if is_builtin(name): return (True, (name, 'builtins')) @@ -66,7 +70,7 @@ def clobber_in_except(node): scope, stmts = node.lookup(name) if (stmts and not isinstance(stmts[0].ass_type(), - (astng.Assign, astng.AugAssign, astng.ExceptHandler))): + (astroid.Assign, astroid.AugAssign, astroid.ExceptHandler))): return (True, (name, 'outer scope (line %s)' % (stmts[0].fromlineno,))) return (False, None) @@ -79,12 +83,12 @@ def safe_infer(node): try: inferit = node.infer() value = inferit.next() - except astng.InferenceError: + except astroid.InferenceError: return try: inferit.next() return # None if there is ambiguity on the inferred node - except astng.InferenceError: + except astroid.InferenceError: return # there is some kind of ambiguity except StopIteration: return value @@ -100,24 +104,28 @@ def is_super(node): def is_error(node): """return true if the function does nothing but raising an exception""" for child_node in node.get_children(): - if isinstance(child_node, astng.Raise): + if isinstance(child_node, astroid.Raise): return True return False def is_raising(body): """return true if the given statement node raise an exception""" for node in body: - if isinstance(node, astng.Raise): + if isinstance(node, astroid.Raise): return True return False def is_empty(body): """return true if the given node does nothing but 'pass'""" - return len(body) == 1 and isinstance(body[0], astng.Pass) + return len(body) == 1 and isinstance(body[0], astroid.Pass) builtins = builtins.__dict__.copy() SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') +def is_builtin_object(node): + """Returns True if the given node is an object from the __builtin__ module.""" + return node and node.root().name == '__builtin__' + def is_builtin(name): # was is_native_builtin """return true if could be considered as a builtin defined by python """ @@ -136,20 +144,20 @@ def is_defined_before(var_node): _node = var_node.parent while _node: if isinstance(_node, COMP_NODE_TYPES): - for ass_node in _node.nodes_of_class(astng.AssName): + for ass_node in _node.nodes_of_class(astroid.AssName): if ass_node.name == varname: return True - elif isinstance(_node, astng.For): - for ass_node in _node.target.nodes_of_class(astng.AssName): + elif isinstance(_node, astroid.For): + for ass_node in _node.target.nodes_of_class(astroid.AssName): if ass_node.name == varname: return True - elif isinstance(_node, astng.With): - if _node.vars is None: - # quickfix : case in which 'with' is used without 'as' - return False - if _node.vars.name == varname: - return True - elif isinstance(_node, (astng.Lambda, astng.Function)): + elif isinstance(_node, astroid.With): + for expr, vars in _node.items: + if expr.parent_of(var_node): + break + if vars and vars.name == varname: + return True + elif isinstance(_node, (astroid.Lambda, astroid.Function)): if _node.args.is_argument(varname): return True if getattr(_node, 'name', None) == varname: @@ -161,10 +169,10 @@ def is_defined_before(var_node): _node = stmt.previous_sibling() lineno = stmt.fromlineno while _node and _node.fromlineno == lineno: - for ass_node in _node.nodes_of_class(astng.AssName): + for ass_node in _node.nodes_of_class(astroid.AssName): if ass_node.name == varname: return True - for imp_node in _node.nodes_of_class( (astng.From, astng.Import)): + for imp_node in _node.nodes_of_class( (astroid.From, astroid.Import)): if varname in [name[1] or name[0] for name in imp_node.names]: return True _node = _node.previous_sibling() @@ -175,9 +183,9 @@ def is_func_default(node): value """ parent = node.scope() - if isinstance(parent, astng.Function): + if isinstance(parent, astroid.Function): for default_node in parent.args.defaults: - for default_name_node in default_node.nodes_of_class(astng.Name): + for default_name_node in default_node.nodes_of_class(astroid.Name): if default_name_node is node: return True return False @@ -186,10 +194,10 @@ def is_func_decorator(node): """return true if the name is used in function decorator""" parent = node.parent while parent is not None: - if isinstance(parent, astng.Decorators): + if isinstance(parent, astroid.Decorators): return True if (parent.is_statement or - isinstance(parent, astng.Lambda) or + isinstance(parent, astroid.Lambda) or isinstance(parent, (scoped_nodes.ComprehensionScope, scoped_nodes.ListComp))): break @@ -197,7 +205,7 @@ def is_func_decorator(node): return False def is_ancestor_name(frame, node): - """return True if `frame` is a astng.Class node with `node` in the + """return True if `frame` is a astroid.Class node with `node` in the subtree of its bases attribute """ try: @@ -205,23 +213,23 @@ def is_ancestor_name(frame, node): except AttributeError: return False for base in bases: - if node in base.nodes_of_class(astng.Name): + if node in base.nodes_of_class(astroid.Name): return True return False def assign_parent(node): """return the higher parent which is not an AssName, Tuple or List node """ - while node and isinstance(node, (astng.AssName, - astng.Tuple, - astng.List)): + while node and isinstance(node, (astroid.AssName, + astroid.Tuple, + astroid.List)): node = node.parent return node def overrides_an_abstract_method(class_node, name): """return True if pnode is a parent of node""" for ancestor in class_node.ancestors(): - if name in ancestor and isinstance(ancestor[name], astng.Function) and \ + if name in ancestor and isinstance(ancestor[name], astroid.Function) and \ ancestor[name].is_abstract(pass_is_abstract=False): return True return False @@ -229,7 +237,7 @@ def overrides_an_abstract_method(class_node, name): def overrides_a_method(class_node, name): """return True if is a method overridden from an ancestor""" for ancestor in class_node.ancestors(): - if name in ancestor and isinstance(ancestor[name], astng.Function): + if name in ancestor and isinstance(ancestor[name], astroid.Function): return True return False @@ -352,7 +360,7 @@ def node_frame_class(node): """ klass = node.frame() - while klass is not None and not isinstance(klass, astng.Class): + while klass is not None and not isinstance(klass, astroid.Class): if klass.parent is None: klass = None else: @@ -364,8 +372,8 @@ def is_super_call(expr): """return True if expression node is a function call and if function name is super. Check before that you're in a method. """ - return (isinstance(expr, astng.CallFunc) and - isinstance(expr.func, astng.Name) and + return (isinstance(expr, astroid.CallFunc) and + isinstance(expr.func, astroid.Name) and expr.func.name == 'super') def is_attr_private(attrname): @@ -374,3 +382,28 @@ def is_attr_private(attrname): """ regex = re.compile('^_{2,}.*[^_]+_?$') return regex.match(attrname) + +def get_argument_from_call(callfunc_node, position=None, keyword=None): + """Returns the specified argument from a function call. + + :param callfunc_node: Node representing a function call to check. + :param int position: position of the argument. + :param str keyword: the keyword of the argument. + + :returns: The node representing the argument, None if the argument is not found. + :raises ValueError: if both position and keyword are None. + :raises NoSuchArgumentError: if no argument at the provided position or with + the provided keyword. + """ + if not position and not keyword: + raise ValueError('Must specify at least one of: position or keyword.') + try: + if position and not isinstance(callfunc_node.args[position], astroid.Keyword): + return callfunc_node.args[position] + except IndexError as error: + raise NoSuchArgumentError(error) + if keyword: + for arg in callfunc_node.args: + if isinstance(arg, astroid.Keyword) and arg.arg == keyword: + return arg.value + raise NoSuchArgumentError diff --git a/pylibs/pylama/checkers/pylint/checkers/variables.py b/pylibs/pylama/checkers/pylint/checkers/variables.py index 88bd2a51..afe0f945 100644 --- a/pylibs/pylama/checkers/pylint/checkers/variables.py +++ b/pylibs/pylama/checkers/pylint/checkers/variables.py @@ -19,12 +19,12 @@ import sys from copy import copy -from ..logilab import astng -from ..logilab.astng import are_exclusive, builtin_lookup, ASTNGBuildingException +from .. import astroid +from ..astroid import are_exclusive, builtin_lookup, AstroidBuildingException -from ..interfaces import IASTNGChecker -from ..checkers import BaseChecker -from ..checkers.utils import (PYMETHODS, is_ancestor_name, is_builtin, +from ..interfaces import IAstroidChecker +from . import BaseChecker +from .utils import (PYMETHODS, is_ancestor_name, is_builtin, is_defined_before, is_error, is_func_default, is_func_decorator, assign_parent, check_messages, is_inside_except, clobber_in_except, get_all_elements) @@ -32,7 +32,7 @@ def in_for_else_branch(parent, stmt): """Returns True if stmt in inside the else branch for a parent For stmt.""" - return (isinstance(parent, astng.For) and + return (isinstance(parent, astroid.For) and any(else_stmt.parent_of(stmt) for else_stmt in parent.orelse)) def overridden_method(klass, name): @@ -45,9 +45,9 @@ def overridden_method(klass, name): meth_node = parent[name] except KeyError: # We have found an ancestor defining but it's not in the local - # dictionary. This may happen with astng built from living objects. + # dictionary. This may happen with astroid built from living objects. return None - if isinstance(meth_node, astng.Function): + if isinstance(meth_node, astroid.Function): return meth_node return None @@ -118,6 +118,12 @@ def overridden_method(klass, name): 'Used when an loop variable (i.e. defined by a for loop or \ a list comprehension or a generator expression) is used outside \ the loop.'), + + 'W0632': ('Possible unbalanced tuple unpacking: ' + 'left side has %d label(s), right side has %d value(s)', + 'unbalanced-tuple-unpacking', + 'Used when there is an unbalanced tuple unpacking in assignment'), + } class VariablesChecker(BaseChecker): @@ -129,7 +135,7 @@ class VariablesChecker(BaseChecker): * __all__ consistency """ - __implements__ = IASTNGChecker + __implements__ = IAstroidChecker name = 'variables' msgs = MSGS @@ -140,7 +146,7 @@ class VariablesChecker(BaseChecker): 'help' : 'Tells whether we should check for unused import in \ __init__ files.'}), ("dummy-variables-rgx", - {'default': ('_|dummy'), + {'default': ('_$|dummy'), 'type' :'regexp', 'metavar' : '', 'help' : 'A regular expression matching the beginning of \ the name of dummy variables (i.e. not used).'}), @@ -166,7 +172,7 @@ def visit_module(self, node): # do not print Redefining builtin for additional builtins self.add_message('W0622', args=name, node=stmts[0]) - @check_messages('W0611', 'W0614') + @check_messages('W0611', 'W0614', 'W0622', 'E0603', 'E0604') def leave_module(self, node): """leave module: check globals """ @@ -175,14 +181,14 @@ def leave_module(self, node): # attempt to check for __all__ if defined if '__all__' in node.locals: assigned = node.igetattr('__all__').next() - if assigned is not astng.YES: + if assigned is not astroid.YES: for elt in getattr(assigned, 'elts', ()): try: elt_name = elt.infer().next() - except astng.InferenceError: + except astroid.InferenceError: continue - if not isinstance(elt_name, astng.Const) or not isinstance(elt_name.value, basestring): + if not isinstance(elt_name, astroid.Const) or not isinstance(elt_name.value, basestring): self.add_message('E0604', args=elt.as_string(), node=elt) continue elt_name = elt.value @@ -197,9 +203,9 @@ def leave_module(self, node): return for name, stmts in not_consumed.iteritems(): stmt = stmts[0] - if isinstance(stmt, astng.Import): + if isinstance(stmt, astroid.Import): self.add_message('W0611', args=name, node=stmt) - elif isinstance(stmt, astng.From) and stmt.modname != '__future__': + elif isinstance(stmt, astroid.From) and stmt.modname != '__future__': if stmt.names[0][0] == '*': self.add_message('W0614', args=name, node=stmt) else: @@ -271,9 +277,11 @@ def visit_function(self, node): for name, stmt in node.items(): if is_inside_except(stmt): continue - if name in globs and not isinstance(stmt, astng.Global): + if name in globs and not isinstance(stmt, astroid.Global): line = globs[name][0].fromlineno - self.add_message('W0621', args=(name, line), node=stmt) + dummy_rgx = self.config.dummy_variables_rgx + if not dummy_rgx.match(name): + self.add_message('W0621', args=(name, line), node=stmt) elif is_builtin(name): # do not print Redefining builtin for additional builtins self.add_message('W0622', args=name, node=stmt) @@ -301,7 +309,7 @@ def leave_function(self, node): # ignore names imported by the global statement # FIXME: should only ignore them if it's assigned latter stmt = stmts[0] - if isinstance(stmt, astng.Global): + if isinstance(stmt, astroid.Global): continue # care about functions with unknown argument (builtins) if name in argnames: @@ -328,7 +336,7 @@ def leave_function(self, node): def visit_global(self, node): """check names imported exists in the global scope""" frame = node.frame() - if isinstance(frame, astng.Module): + if isinstance(frame, astroid.Module): self.add_message('W0604', node=node) return module = frame.root() @@ -336,7 +344,7 @@ def visit_global(self, node): for name in node.names: try: assign_nodes = module.getattr(name) - except astng.NotFoundError: + except astroid.NotFoundError: # unassigned global, skip assign_nodes = [] for anode in assign_nodes: @@ -399,10 +407,11 @@ def _loopvar_name(self, node, name): astmts = _astmts if len(astmts) == 1: ass = astmts[0].ass_type() - if isinstance(ass, (astng.For, astng.Comprehension, astng.GenExpr)) \ + if isinstance(ass, (astroid.For, astroid.Comprehension, astroid.GenExpr)) \ and not ass.statement() is node.statement(): self.add_message('W0631', args=name, node=node) + @check_messages('W0623') def visit_excepthandler(self, node): for name in get_all_elements(node.name): clobbering, args = clobber_in_except(name) @@ -410,19 +419,20 @@ def visit_excepthandler(self, node): self.add_message('W0623', args=args, node=name) def visit_assname(self, node): - if isinstance(node.ass_type(), astng.AugAssign): + if isinstance(node.ass_type(), astroid.AugAssign): self.visit_name(node) def visit_delname(self, node): self.visit_name(node) + @check_messages(*(MSGS.keys())) def visit_name(self, node): """check that a name is defined if the current scope and doesn't redefine a built-in """ stmt = node.statement() if stmt.fromlineno is None: - # name node from a astng built from live code, skip + # name node from a astroid built from live code, skip assert not stmt.root().file.endswith('.py') return name = node.name @@ -481,13 +491,13 @@ def visit_name(self, node): and stmt.fromlineno <= defstmt.fromlineno and not is_defined_before(node) and not are_exclusive(stmt, defstmt, ('NameError', 'Exception', 'BaseException'))): - if defstmt is stmt and isinstance(node, (astng.DelName, - astng.AssName)): + if defstmt is stmt and isinstance(node, (astroid.DelName, + astroid.AssName)): self.add_message('E0602', args=name, node=node) elif self._to_consume[-1][-1] != 'lambda': # E0601 may *not* occurs in lambda scope self.add_message('E0601', args=name, node=node) - if not isinstance(node, astng.AssName): # Aug AssName + if not isinstance(node, astroid.AssName): # Aug AssName del to_consume[name] else: del consumed[name] @@ -497,7 +507,7 @@ def visit_name(self, node): else: # we have not found the name, if it isn't a builtin, that's an # undefined name ! - if not (name in astng.Module.scope_attrs or is_builtin(name) + if not (name in astroid.Module.scope_attrs or is_builtin(name) or name in self.config.additional_builtins): self.add_message('E0602', args=name, node=node) @@ -508,7 +518,7 @@ def visit_import(self, node): parts = name.split('.') try: module = node.infer_name_module(parts[0]).next() - except astng.ResolveError: + except astroid.ResolveError: continue self._check_module_attrs(node, module, parts[1:]) @@ -519,7 +529,7 @@ def visit_from(self, node): level = getattr(node, 'level', None) try: module = node.root().import_module(name_parts[0], level=level) - except ASTNGBuildingException: + except AstroidBuildingException: return except Exception, exc: print 'Unhandled exception in VariablesChecker:', exc @@ -532,12 +542,33 @@ def visit_from(self, node): continue self._check_module_attrs(node, module, name.split('.')) + @check_messages('unbalanced-tuple-unpacking') + def visit_assign(self, node): + """Check unbalanced tuple unpacking for assignments""" + if not isinstance(node.targets[0], (astroid.Tuple, astroid.List)): + return + try: + infered = node.value.infer().next() + except astroid.InferenceError: + return + if not isinstance(infered, (astroid.Tuple, astroid.List)): + return + targets = node.targets[0].itered() + values = infered.itered() + if any(not isinstance(target_node, astroid.AssName) + for target_node in targets): + return + if len(targets) != len(values): + self.add_message('unbalanced-tuple-unpacking', + node=node, + args=(len(targets), len(values))) + def _check_module_attrs(self, node, module, module_names): """check that module_names (list of string) are accessible through the given module if the latest access name corresponds to a module, return it """ - assert isinstance(module, astng.Module), module + assert isinstance(module, astroid.Module), module while module_names: name = module_names.pop(0) if name == '__dict__': @@ -545,12 +576,12 @@ def _check_module_attrs(self, node, module, module_names): break try: module = module.getattr(name)[0].infer().next() - if module is astng.YES: + if module is astroid.YES: return None - except astng.NotFoundError: + except astroid.NotFoundError: self.add_message('E0611', args=(name, module.name), node=node) return None - except astng.InferenceError: + except astroid.InferenceError: return None if module_names: # FIXME: other message if name is not the latest part of @@ -559,7 +590,7 @@ def _check_module_attrs(self, node, module, module_names): self.add_message('E0611', node=node, args=('.'.join(module_names), modname)) return None - if isinstance(module, astng.Module): + if isinstance(module, astroid.Module): return module return None diff --git a/pylibs/pylama/checkers/pylint/config.py b/pylibs/pylama/checkers/pylint/config.py index 94d44b8f..192f2548 100644 --- a/pylibs/pylama/checkers/pylint/config.py +++ b/pylibs/pylama/checkers/pylint/config.py @@ -11,7 +11,7 @@ # 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., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -"""utilities for PyLint configuration : +"""utilities for Pylint configuration : * pylintrc * pylint.d (PYLINTHOME) @@ -105,13 +105,13 @@ def find_pylintrc(): PYLINTRC = find_pylintrc() ENV_HELP = ''' -The following environment variables are used: - * PYLINTHOME - path to the directory where data of persistent run will be stored. If not -found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working -directory). - * PYLINTRC - path to the configuration file. If not found, it will use the first +The following environment variables are used: + * PYLINTHOME + path to the directory where data of persistent run will be stored. If not +found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working +directory). + * PYLINTRC + path to the configuration file. If not found, it will use the first existing file among (~/.pylintrc, /etc/pylintrc). ''' % globals() diff --git a/pylibs/pylama/checkers/pylint/interfaces.py b/pylibs/pylama/checkers/pylint/interfaces.py index 2d2432df..7193c65a 100644 --- a/pylibs/pylama/checkers/pylint/interfaces.py +++ b/pylibs/pylama/checkers/pylint/interfaces.py @@ -10,13 +10,7 @@ # 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., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -""" Copyright (c) 2002-2003 LOGILAB S.A. (Paris, FRANCE). - http://www.logilab.fr/ -- mailto:contact@logilab.fr - -Interfaces for PyLint objects -""" - -__revision__ = "$Id: interfaces.py,v 1.9 2004-04-24 12:14:53 syt Exp $" +"""Interfaces for PyLint objects""" from .logilab.common.interface import Interface @@ -32,51 +26,31 @@ def open(self): def close(self): """called after visiting project (i.e set of modules)""" -## def open_module(self): -## """called before visiting a module""" - -## def close_module(self): -## """called after visiting a module""" - class IRawChecker(IChecker): """interface for checker which need to parse the raw file """ - def process_module(self, astng): + def process_module(self, astroid): """ process a module - the module's content is accessible via astng.file_stream + the module's content is accessible via astroid.file_stream """ -class IASTNGChecker(IChecker): - """ interface for checker which prefers receive events according to - statement type - """ - - -class ILinter(Interface): - """interface for the linter class +class ITokenChecker(IChecker): + """Interface for checkers that need access to the token list.""" + def process_tokens(self, tokens): + """Process a module. - the linter class will generate events to its registered checkers. - Each checker may interact with the linter instance using this API - """ - - def register_checker(self, checker): - """register a new checker class - - checker is a class implementing IrawChecker or / and IASTNGChecker + tokens is a list of all source code tokens in the file. """ - def add_message(self, msg_id, line=None, node=None, args=None): - """add the message corresponding to the given id. - - If provided, msg is expanded using args - astng checkers should provide the node argument, - raw checkers should provide the line argument. - """ +class IAstroidChecker(IChecker): + """ interface for checker which prefers receive events according to + statement type + """ class IReporter(Interface): @@ -95,4 +69,4 @@ def display_results(self, layout): """ -__all__ = ('IRawChecker', 'ILinter', 'IReporter') +__all__ = ('IRawChecker', 'IAstroidChecker', 'ITokenChecker', 'IReporter') diff --git a/pylibs/pylama/checkers/pylint/lint.py b/pylibs/pylama/checkers/pylint/lint.py index 60a9784b..25c5377b 100644 --- a/pylibs/pylama/checkers/pylint/lint.py +++ b/pylibs/pylama/checkers/pylint/lint.py @@ -31,7 +31,6 @@ import sys import os -import re import tokenize from warnings import warn @@ -43,30 +42,24 @@ from .logilab.common.ureports import Table, Text, Section from .logilab.common.__pkginfo__ import version as common_version -from .logilab.astng import MANAGER, nodes, ASTNGBuildingException -from .logilab.astng.__pkginfo__ import version as astng_version - -from .utils import (PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, - ReportsHandlerMixIn, MSG_TYPES, expand_modules, - WarningScope) -from .interfaces import ILinter, IRawChecker, IASTNGChecker -from .checkers import (BaseRawChecker, EmptyReport, - table_lines_from_stats) -from .reporters.text import (TextReporter, ParseableTextReporter, - VSTextReporter, ColorizedTextReporter) -from .reporters.html import HTMLReporter +from .astroid import MANAGER, nodes, AstroidBuildingException +from .astroid.__pkginfo__ import version as astroid_version + +from .utils import ( + MSG_TYPES, OPTION_RGX, + PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, ReportsHandlerMixIn, + EmptyReport, WarningScope, + expand_modules, tokenize_module) +from .interfaces import IRawChecker, ITokenChecker, IAstroidChecker +from .checkers import (BaseTokenChecker, + table_lines_from_stats, + initialize as checkers_initialize) +from .reporters import initialize as reporters_initialize from . import config from .__pkginfo__ import version -OPTION_RGX = re.compile(r'\s*#.*\bpylint:(.*)') -REPORTER_OPT_MAP = {'text': TextReporter, - 'parseable': ParseableTextReporter, - 'msvs': VSTextReporter, - 'colorized': ColorizedTextReporter, - 'html': HTMLReporter,} - def _get_python_path(filepath): dirname = os.path.dirname(os.path.realpath( @@ -88,8 +81,8 @@ def _get_python_path(filepath): 'Used when an error occurred preventing the analysis of a \ module (unable to find it for instance).'), 'F0002': ('%s: %s', - 'astng-error', - 'Used when an unexpected error occurred while building the ASTNG \ + 'astroid-error', + 'Used when an unexpected error occurred while building the Astroid \ representation. This is usually accompanied by a traceback. \ Please report such errors !'), 'F0003': ('ignored builtin module %s', @@ -102,8 +95,8 @@ def _get_python_path(filepath): inferred.'), 'F0010': ('error while code parsing: %s', 'parse-error', - 'Used when an exception occured while building the ASTNG \ - representation which could be handled by astng.'), + 'Used when an exception occured while building the Astroid \ + representation which could be handled by astroid.'), 'I0001': ('Unable to run raw checkers on built-in module %s', 'raw-checker-failed', @@ -157,21 +150,21 @@ def _get_python_path(filepath): class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, - BaseRawChecker): + BaseTokenChecker): """lint Python modules using external checkers. This is the main checker controlling the other ones and the reports - generation. It is itself both a raw checker and an astng checker in order + generation. It is itself both a raw checker and an astroid checker in order to: * handle message activation / deactivation at the module level * handle some basic but necessary stats'data (number of classes, methods...) IDE plugins developpers: you may have to call - `logilab.astng.builder.MANAGER.astng_cache.clear()` accross run if you want + `astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want to ensure the latest code version is actually checked. """ - __implements__ = (ILinter, IRawChecker) + __implements__ = (ITokenChecker,) name = 'master' priority = 0 @@ -206,18 +199,6 @@ def make_options(): 'can also give a reporter class, eg mypackage.mymodule.' 'MyReporterClass.'}), - ('include-ids', - {'type' : 'yn', 'metavar' : '', 'default' : 0, - 'short': 'i', - 'group': 'Reports', - 'help' : 'Include message\'s id in output'}), - - ('symbols', - {'type' : 'yn', 'metavar' : '', 'default' : 0, - 'short': 's', - 'group': 'Reports', - 'help' : 'Include symbolic ids of messages in output'}), - ('files-output', {'default': 0, 'type' : 'yn', 'metavar' : '', 'group': 'Reports', 'level': 1, @@ -274,6 +255,16 @@ def make_options(): 'If you want to run only the classes checker, but have no ' 'Warning level messages displayed, use' '"--disable=all --enable=classes --disable=W"'}), + + ('msg-template', + {'type' : 'string', 'metavar': '