Changeset 11
- Timestamp:
- 03/30/06 19:32:51 (3 years ago)
- Files:
-
- trunk/cheesecake/_util.py (modified) (1 diff)
- trunk/cheesecake/cheesecake_index.py (modified) (21 diffs)
- trunk/cheesecake/codeparser.py (modified) (6 diffs)
- trunk/cheesecake/config.py (modified) (2 diffs)
- trunk/cheesecake/model.py (modified) (9 diffs)
- trunk/ez_setup.py (added)
- trunk/tests/test_code_parser.py (modified) (1 diff)
- trunk/tests/test_config.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/cheesecake/_util.py
r8 r11 94 94 def pad_line(char="="): 95 95 """ 96 Printline consisting of 'char' characters96 Return line consisting of 'char' characters 97 97 """ 98 print(char * (PAD_TEXT + PAD_VALUE + 1)) 98 msg = char * (PAD_TEXT + PAD_VALUE + 1) 99 return msg trunk/cheesecake/cheesecake_index.py
r8 r11 28 28 from _util import StdoutRedirector 29 29 import logger 30 importconfig30 from config import get_pkg_config 31 31 from codeparser import CodeParser 32 32 … … 141 141 self.sandbox_install_dir = "" 142 142 143 self.set_defaults()144 self.get_config_values()145 143 self.determine_pkg_name() 146 144 self.configure_logging() 145 self.set_defaults() 146 self.get_config() 147 147 self.init_indexes() 148 148 self.retrieve_pkg() … … 196 196 self.INDEX_DIR_EMPTY = 5 197 197 self.MAX_INDEX_DOCSTRINGS = 100 # max. percentage of modules/classes/methods/functions with docstrings 198 self.MAX_INDEX_UNITTESTS = 100 # max. percentage of methods/functions that are unit tested 198 199 self.MAX_INDEX_PYLINT = 100 # max. pylint score 199 200 self.cheese_files = ["readme", "install", "changelog", … … 207 208 self.critical_cheese_dirs = ["doc", "test"] 208 209 209 def get_config _values(self):210 def get_config(self): 210 211 """ 211 212 Retrieve values from configuration file 212 213 """ 214 self.config = get_pkg_config(self.short_pkg_name) 213 215 for config_var in ["INDEX_PYPI_DOWNLOAD", "INDEX_PYPI_DISTANCE", 214 216 "INDEX_URL_DOWNLOAD", "INDEX_UNPACK", "INDEX_UNPACK_DIR", … … 220 222 "cheese_dirs", "critical_cheese_dirs", 221 223 ]: 222 value = config.get(config_var)224 value = self.config.get(config_var) 223 225 if value: setattr(self, config_var, value) 224 226 … … 226 228 if self.name: 227 229 self.package = self.name 230 self.short_pkg_name = self.name 228 231 elif self.package_path: 229 232 self.package = self.get_package_from_path(self.package_path) … … 231 234 self.package = self.get_package_from_url() 232 235 236 def get_package_from_url(self): 237 """ 238 Use ``urlparse`` to obtain package path from URL 239 """ 240 (scheme,location,path,param,query,fragment_id) = urlparse(self.url) 241 return self.get_package_from_path(path) 242 243 244 def get_package_from_path(self, path): 245 """ 246 Get package name as file portion of path 247 """ 248 dir, file = os.path.split(path) 249 self.short_pkg_name = file 250 for package_type in self.package_types: 251 s = re.search("(.+)\.%s" % package_type, file) 252 if s: 253 self.short_pkg_name = s.group(1) 254 break 255 return file 256 233 257 def configure_logging(self): 234 258 """ … … 240 264 log.warn and log.error go to both logfile and stdout 241 265 """ 242 self.logfile = os.path.join(self.sandbox, self. package + ".log")266 self.logfile = os.path.join(self.sandbox, self.short_pkg_name + ".log") 243 267 244 268 logger.setconsumer('logfile', open(str(self.logfile), 'w', buffering=1)) … … 258 282 self.log.error = logger.MultipleProducer('cheesecake console') 259 283 260 self.log.debug("package = ", self. package)284 self.log.debug("package = ", self.short_pkg_name) 261 285 262 286 def init_indexes(self): … … 276 300 self.INDEX_INSTALL + \ 277 301 self.MAX_INDEX_DOCSTRINGS + \ 278 self.MAX_INDEX_PYLINT 302 self.MAX_INDEX_PYLINT 303 # self.MAX_INDEX_UNITTESTS 279 304 self.max_cheesecake_index_installability = self.INDEX_PYPI_DOWNLOAD + \ 280 305 self.INDEX_UNPACK + \ … … 283 308 self.max_cheesecake_index_documentation = self.INDEX_REQUIRED_FILES + \ 284 309 self.MAX_INDEX_DOCSTRINGS 285 self.max_cheesecake_index_codekwalitee = self.MAX_INDEX_PYLINT 310 self.max_cheesecake_index_codekwalitee = self.MAX_INDEX_PYLINT 311 # self.MAX_INDEX_UNITTESTS 312 286 313 self.index = {} 287 314 for index_type in ["file", "dir"]: … … 289 316 for index_type in ["pypi_download", "url_download", 290 317 "unpack_dir", "unpack", "install", 291 "docstrings", " pylint"]:318 "docstrings", "unittests", "pylint"]: 292 319 self.index[index_type] = Index(index_type) 293 320 … … 321 348 self.pkg_files[type] = [] 322 349 350 self.object_cnt = 0 # Number of modules/functions/classes/methods in .py files found 351 self.docstring_cnt = 0 352 self.functions = [] # List of methods/functions found in .py files 353 323 354 def retrieve_pkg(self): 324 355 if self.name: … … 342 373 """ 343 374 dir, file = os.path.split(path) 375 self.short_pkg_name = file 376 for package_type in self.package_types: 377 s = re.search("(.+)\.%s" % package_type, file) 378 if s: 379 self.short_pkg_name = s.group(1) 380 break 344 381 return file 345 382 … … 586 623 self.pkg_files["py"].append(fullpath) 587 624 self.log.debug("py file found: " + fullpath) 625 pyfile = os.path.join(self.sandbox, fullpath) 626 # Parse the file and count objects (modules/classes/functions) 627 # and their associated docstrings 628 code = CodeParser(pyfile, self.log.debug) 629 self.object_cnt += code.object_count() 630 self.docstring_cnt += code.docstring_count() 631 self.functions += code.functions 632 588 633 if os.path.splitext(file)[1] == ".pyc": 589 634 self.pkg_files["pyc"].append(fullpath) 590 635 self.log.debug("pyc file found: " + fullpath) 636 591 637 if self.is_test_file(file, dirs_in_rootdir): 592 638 self.pkg_files["test"].append(fullpath) 593 639 self.log.debug("test file found: " + fullpath) 640 594 641 len_pyc_list = len(self.pkg_files["pyc"]) 595 642 if len_pyc_list: … … 623 670 Return True is file is in directory rooted at "test" or "tests" 624 671 """ 672 if not file.endswith(".py"): 673 return False 625 674 if file in ["__init__.py"]: 626 675 return False … … 700 749 cwd = os.getcwd() 701 750 os.chdir(os.path.join(self.sandbox, self.package_name)) 702 rc, output = run_cmd("python setup.py install -- home=" + self.sandbox_install_dir)751 rc, output = run_cmd("python setup.py install --root=" + self.sandbox_install_dir) 703 752 if not rc: 704 753 # Install succeeded … … 718 767 Return Index object of type "docstrings" 719 768 """ 720 cnt = 0721 docstring_cnt = 0722 769 index_type = "docstrings" 723 for pyfile in self.pkg_files["py"]: 724 fullpath = os.path.join(self.sandbox, pyfile) 725 code = CodeParser(fullpath, self.log.debug) 726 cnt += code.object_count() 727 docstring_cnt += code.docstring_count() 728 if cnt: 729 percent = float(docstring_cnt)/float(cnt) 770 if self.object_cnt: 771 percent = float(self.docstring_cnt)/float(self.object_cnt) 730 772 else: 731 773 percent = 0 732 774 index_value = int(ceil(percent*100)) 733 details = "found %d/%d=%.2f%% modules/classes/methods/functions with docstrings" % (docstring_cnt, cnt, percent*100) 775 details = "found %d/%d=%.2f%% modules/classes/methods/functions with docstrings" %\ 776 (self.docstring_cnt, self.object_cnt, percent*100) 734 777 self.index[index_type].value = index_value 735 778 self.index[index_type].details = details 736 779 return self.index[index_type] 780 781 def index_unittests(self): 782 """ 783 Compute unittest index as percentage of methods/functions 784 that are exercised in unit tests 785 786 Return Index object of type "unittests" 787 """ 788 unittest_cnt = 0 789 index_type = "unittests" 790 self.functions_tested = {} 791 for testfile in self.pkg_files["test"]: 792 fullpath = os.path.join(self.sandbox, testfile) 793 code = CodeParser(fullpath, self.log.debug) 794 func_called = code.functions_called() 795 self.log.debug("Functions called in unit test:") 796 self.log.debug(func_called) 797 for func in func_called: 798 self.functions_tested[func] = 1 799 self.log.debug("FUNCTIONS TO BE CHECKED WHETHER THEY ARE UNIT TESTED:") 800 self.log.debug(self.functions) 801 self.log.debug("FUNCTIONS THAT ARE UNIT TESTED:") 802 self.log.debug(self.functions_tested.keys()) 803 for funcname in self.functions: 804 if self.is_unit_tested(funcname): 805 unittest_cnt += 1 806 self.log.debug("%s is unit tested" % funcname) 807 cnt = len(self.functions) 808 if cnt: 809 percent = float(unittest_cnt)/float(cnt) 810 else: 811 percent = 0 812 index_value = int(ceil(percent*100)) 813 details = "found %d/%d=%.2f%% unit tested methods/functions" % (unittest_cnt, cnt, percent*100) 814 self.index[index_type].value = index_value 815 self.index[index_type].details = details 816 return self.index[index_type] 817 818 def is_unit_tested(self, funcname): 819 elem = funcname.split(".") 820 n1 = elem[-1] 821 n2 = "" 822 if len(elem) > 1: 823 n2 = elem[-2] + "." + elem[-1] 824 for key in self.functions_tested.keys(): 825 if key.startswith(n1) or (n2 and key.startswith(n2)): 826 return True 827 return False 737 828 738 829 def index_pylint(self): … … 805 896 index_types, self.max_cheesecake_index_documentation) 806 897 807 index_types = ["pylint"] 898 index_types = [ 899 #"unittests", 900 "pylint", 901 ] 808 902 self.cheesecake_index_codekwalitee = self.process_partial_index("CODE KWALITEE",\ 809 903 index_types, self.max_cheesecake_index_codekwalitee) 810 904 811 905 print 812 self.print_separator_line( )906 self.print_separator_line("=") 813 907 print pad_msg("OVERALL CHEESECAKE INDEX (ABSOLUTE)", self.cheesecake_index) 814 908 percentage = (self.cheesecake_index * 100) / self.max_cheesecake_index … … 829 923 partial_index_value += self.process_index(index_type) 830 924 831 self.print_separator_line( "-")925 self.print_separator_line() 832 926 print pad_msg("%s INDEX (ABSOLUTE)" % partial_index_name, partial_index_value) 833 927 percentage = (partial_index_value * 100) / max_value … … 850 944 return index.value 851 945 852 def print_separator_line(self, char=" "):946 def print_separator_line(self, char="-"): 853 947 """ 854 948 Print line of text, unless quiet flag was given trunk/cheesecake/codeparser.py
r8 r11 6 6 Information about the structure of a Python module 7 7 8 * Collects modules, classes, methods /functions and anyassociated docstrings8 * Collects modules, classes, methods, functions and associated docstrings 9 9 * Based on mwh's docextractor.model module 10 10 """ … … 17 17 self.modules = [] 18 18 self.classes = [] 19 self.methods = [] 20 self.method_func = [] 19 21 self.functions = [] 20 22 self.docstrings = {} … … 24 26 self.log("Inspecting file: " + pyfile) 25 27 26 system = System() 27 processModuleAst(parseFile(pyfile), module, system) 28 for obj in system.orderedallobjects: 28 self.system = System() 29 try: 30 processModuleAst(parseFile(pyfile), module, self.system) 31 except: 32 return 33 34 for obj in self.system.orderedallobjects: 29 35 fullname = obj.fullName() 30 36 if isinstance(obj, Module): … … 33 39 self.classes.append(obj.fullName()) 34 40 if isinstance(obj, Function): 35 self. functions.append(fullname)41 self.method_func.append(fullname) 36 42 if obj.docstring: 37 43 self.docstrings[fullname] = 1 38 44 45 for method_or_func in self.method_func: 46 method_found = 0 47 for cls in self.classes: 48 if method_or_func.startswith(cls): 49 self.methods.append(method_or_func) 50 method_found = 1 51 break 52 if not method_found: 53 self.functions.append(method_or_func) 54 39 55 self.log("modules: " + ",".join(self.modules)) 40 56 self.log("classes: " + ",".join(self.classes)) 57 self.log("methods: " + ",".join(self.methods)) 41 58 self.log("functions: " + ",".join(self.functions)) 42 59 … … 47 64 * module 48 65 * classes 49 * methods/functions 66 * methods 67 * functions 50 68 """ 51 69 module_count = len(self.modules) 52 70 cls_count = len(self.classes) 71 method_count = len(self.methods) 53 72 func_count = len(self.functions) 54 return module_count + cls_count + func_count73 return module_count + cls_count + method_count + func_count 55 74 56 75 def docstring_count(self): … … 59 78 """ 60 79 return len(self.docstrings.keys()) 80 81 def functions_called(self): 82 """ 83 Return list of functions called by functions/methods 84 defined in this module 85 """ 86 return self.system.func_called.keys() trunk/cheesecake/config.py
r9 r11 21 21 INDEX_DIR_EMPTY = 5, 22 22 MAX_INDEX_DOCSTRINGS = 100, # max. percentage of modules/classes/methods/functions with docstrings 23 MAX_INDEX_UNITTESTS = 100, # max. percentage of methods/functions that are unit tested 23 24 MAX_INDEX_PYLINT = 100, # max. pylint score 24 25 cheese_files = ["readme", "install", "changelog", … … 28 29 critical_cheese_files = ["readme", "license", "setup.py"], 29 30 cheese_dirs = ["doc", "test", "example", "demo"], 30 critical_cheese_dirs = ["doc", "test"] 31 critical_cheese_dirs = ["doc", "test"], 32 exclude_files = [], 33 exclude_dirs = [], 31 34 ) 32 35 33 # try getting the user's home directory 34 homedir = "~" 35 homedir = os.path.expanduser(homedir) 36 if homedir is "~": 37 #raise Exception("cannot get home directory!") 38 # can't get it...fall back to defaults 39 pass 40 else: 41 # check for .cheesecake dir and create it if it's not there 42 cheesecake_dir = os.path.join(homedir, ".cheesecake") 43 if not os.path.isdir(cheesecake_dir): 44 os.mkdir(cheesecake_dir) 45 # check for config. file and create it if it's not there 46 cfile = os.path.join(cheesecake_dir, "my_config.py") 47 if not os.path.isfile(cfile): 48 try: 49 c = open(cfile, 'w') 50 c.write("my_config = {\n") 51 keys = _config.keys() 52 keys.sort() 53 for key in keys: 54 c.write("\t'%s': %s,\n" % (key, _config[key])) 55 c.write("}\n") 56 c.close() 57 except OSError, e: 58 pass 36 def get_pkg_config(package): 37 # try getting the user's home directory 38 homedir = "~" 39 homedir = os.path.expanduser(homedir) 40 if homedir is "~": 41 # can't get it...fall back to defaults 42 print "Couldn't expand ~ (home directory)" 43 pass 44 else: 45 # check for .cheesecake dir and create it if it's not there 46 cheesecake_dir = os.path.join(homedir, ".cheesecake") 47 if not os.path.isdir(cheesecake_dir): 48 os.mkdir(cheesecake_dir) 49 # check for config. file and create it if it's not there 50 cfile = os.path.join(cheesecake_dir, "%s_config.py" % package) 51 if not os.path.isfile(cfile): 52 try: 53 c = open(cfile, 'w') 54 c.write("my_config = {\n") 55 keys = _config.keys() 56 keys.sort() 57 for key in keys: 58 c.write("\t'%s': %s,\n" % (key, _config[key])) 59 c.write("}\n") 60 c.close() 61 except OSError, e: 62 pass 59 63 60 else: 61 # if we find the file, update _config with contents of the file 62 sys.path.insert(0, cheesecake_dir) 63 try: 64 from my_config import my_config 65 _config.update(my_config) 66 except ImportError, e: 67 pass 64 else: 65 # if we find the file, update _config with contents of the file 66 sys.path.insert(0, cheesecake_dir) 67 try: 68 from my_config import my_config 69 _config.update(my_config) 70 except ImportError, e: 71 pass 72 return _config 68 73 69 74 def get(key, default=None): trunk/cheesecake/model.py
r8 r11 1 1 """ 2 Code borrowed from Michael Hudson's docextractor package with the author's permission. 3 The original code is available at <http://codespeak.net/svn/user/mwh/docextractor/> 4 Minor changes: 5 * do not print warnings to stdout 2 -Code borrowed from Michael Hudson's docextractor package with the author's permission. 3 -The original code is available at <http://codespeak.net/svn/user/mwh/docextractor/> 6 4 """ 5 7 6 from compiler import ast 8 7 import sys … … 12 11 import sets 13 12 13 import compiler 14 14 from compiler.transformer import parse, parseFile 15 15 from compiler.visitor import walk … … 88 88 # for import * statements 89 89 self.importstargraph = {} 90 self.func_called = {} 90 91 91 92 def _push(self, cls, name, docstring): … … 140 141 self.current = self.current.parent 141 142 142 def push(self, obj ):143 def push(self, obj, node=None): 143 144 self._stack.append(self.current) 144 145 self.current = obj … … 158 159 self._pop(self.Module) 159 160 160 def pushFunction(self, name, docstring): 161 def pushFunction(self, name, docstring, func_called): 162 self.func_called.update(func_called) 161 163 return self._push(self.Function, name, docstring) 162 164 def popFunction(self): … … 260 262 assert m.docstring is None 261 263 m.docstring = node.doc 262 self.system.push(m )264 self.system.push(m, node) 263 265 self.default(node) 264 266 self.system.pop(m) … … 327 329 328 330 def visitFunction(self, node): 329 func = self.system.pushFunction(node.name, node.doc) 331 fc = {} 332 get_function_calls(node, fc) 333 #print fc.keys() 334 func = self.system.pushFunction(node.name, node.doc, fc) 330 335 # ast.Function has a pretty lame representation of 331 336 # arguments. Let's convert it to a nice concise … … 357 362 argnames2.append(argname) 358 363 func.argspec = (argnames2, starargname, kwname, tuple(defaults)) 364 #for child in node.getChildren(): 365 # if isinstance(child, compiler.ast.Stmt): 366 # for c in child.getChildren(): 367 # print c.__class__ 368 # print c 359 369 self.postpone(func, node.code) 360 370 self.system.popFunction() 361 371 372 def get_function_calls(node, fc): 373 if not isinstance(node, compiler.ast.Node): 374 return 375 for child in node.getChildren(): 376 #print "child:", child 377 if isinstance(child, compiler.ast.CallFunc): 378 funcname = "" 379 attrname = "" 380 n = child.node 381 #print "n:", n 382 #print n.__class__ 383 if isinstance(n, compiler.ast.Getattr): 384 expr = n.expr 385 if isinstance(expr, compiler.ast.Name): 386 funcname = expr.name 387 attrname = n.attrname 388 func_called = "" 389 if funcname: func_called = funcname + "." 390 func_called += attrname 391 if func_called: 392 fc[func_called] = 1 393 get_function_calls(child, fc) 394 362 395 def processModuleAst(ast, name, system): 363 396 mv = ModuleVistor(system, name) … … 365 398 while mv.morenodes: 366 399 obj, node = mv.morenodes.pop(0) 367 system.push(obj )400 system.push(obj, node) 368 401 mv.visit(node) 369 402 system.pop(obj) trunk/tests/test_code_parser.py
r8 r11 14 14 assert self.code1.classes == ["module1.Class1", "module1.Class2"] 15 15 16 def test_methods(self): 17 assert self.code1.methods== ["module1.Class1.__init__", 18 "module1.Class1.__another_method__", 19 "module1.Class1.method1", 20 "module1.Class1.method2", 21 "module1.Class1.method3", 22 "module1.Class1.method4"] 23 16 24 def test_functions(self): 17 assert self.code1.functions == ["module1.Class1.__init__", "module1.Class1.__another_method__",\ 18 "module1.Class1.method1", "module1.Class1.method2", "module1.Class1.method3", \ 19 "module1.Class1.method4", "module1.func1", "module1.func2", "module1.func3", \ 20 "module1.func4", "module1.__func5__"] 25 assert self.code1.functions == [ 26 "module1.func1", 27 "module1.func2", 28 "module1.func3", 29 "module1.func4", 30 "module1.__func5__"] 31 21 32 def test_count(self): 22 33 assert self.code1.object_count() == 14 trunk/tests/test_config.py
r8 r11 9 9 homedir = os.path.expanduser(homedir) 10 10 self.cheesecake_dir = os.path.join(homedir, ".cheesecake") 11 self.my_config = os.path.join(self.cheesecake_dir, "my_config.py")12 11 13 12 def test_no_custom_config(self): … … 15 14 if os.path.isdir(self.cheesecake_dir): 16 15 shutil.rmtree(self.cheesecake_dir) 17 import cheesecake.config18 # since the module might have been imported by another test,19 # we reload it20 reload(cheesecake.config)16 from cheesecake import config 17 pkg_name = "my" 18 pkg_config_file = os.path.join(self.cheesecake_dir, "my_config.py") 19 config = config.get_pkg_config(pkg_name) 21 20 # the .cheesecke dir and my_config.py file should have been created 22 21 assert os.path.isdir(self.cheesecake_dir) 23 assert os.path.isfile( self.my_config)22 assert os.path.isfile(pkg_config_file) 24 23 # my_config should contain default values 25 24 sys.path.insert(0, self.cheesecake_dir) 26 25 from my_config import my_config 27 26 for key, value in my_config.items(): 28 assert c heesecake.config.get(key) == value27 assert config.get(key) == value
