1 # Copyright 2013 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 import logging 5 import optparse 6 import os 7 import pkgutil 8 import pydoc 9 import re 10 import sys 11 12 import telemetry 13 from telemetry.core import util 14 15 telemetry_dir = util.GetTelemetryDir() 16 docs_dir = os.path.join(telemetry_dir, 'docs') 17 18 def RemoveAllDocs(): 19 for dirname, _, filenames in os.walk(docs_dir): 20 for filename in filenames: 21 os.remove(os.path.join(dirname, filename)) 22 23 def GenerateHTMLForModule(module): 24 html = pydoc.html.page(pydoc.describe(module), 25 pydoc.html.document(module, module.__name__)) 26 27 # pydoc writes out html with links in a variety of funky ways. We need 28 # to fix them up. 29 assert not telemetry_dir.endswith(os.sep) 30 links = re.findall('(<a href="(.+?)">(.+?)</a>)', html) 31 for link_match in links: 32 link, href, link_text = link_match 33 if not href.startswith('file:'): 34 continue 35 36 new_href = href.replace('file:', '') 37 new_href = new_href.replace(telemetry_dir, os.pardir) 38 new_href = new_href.replace(os.sep, '/') 39 40 new_link_text = link_text.replace(telemetry_dir + os.sep, '') 41 42 new_link = '<a href="%s">%s</a>' % (new_href, new_link_text) 43 html = html.replace(link, new_link) 44 45 # pydoc writes out html with absolute path file links. This is not suitable 46 # for checked in documentation. So, fix up the HTML after it is generated. 47 #html = re.sub('href="file:%s' % telemetry_dir, 'href="..', html) 48 #html = re.sub(telemetry_dir + os.sep, '', html) 49 return html 50 51 def WriteHTMLForModule(module): 52 page = GenerateHTMLForModule(module) 53 path = os.path.join(docs_dir, '%s.html' % module.__name__) 54 with open(path, 'w') as f: 55 sys.stderr.write('Wrote %s\n' % os.path.relpath(path)) 56 f.write(page) 57 58 def GetAllModulesToDocument(module): 59 modules = [module] 60 for _, modname, _ in pkgutil.walk_packages( 61 module.__path__, module.__name__ + '.'): 62 if modname.endswith('_unittest'): 63 logging.debug("skipping %s due to being a unittest", modname) 64 continue 65 66 module = __import__(modname, fromlist=[""]) 67 name, _ = os.path.splitext(module.__file__) 68 if not os.path.exists(name + '.py'): 69 logging.info("skipping %s due to being an orphan .pyc", module.__file__) 70 continue 71 72 modules.append(module) 73 return modules 74 75 class AlreadyDocumentedModule(object): 76 def __init__(self, filename): 77 self.filename = filename 78 79 @property 80 def name(self): 81 basename = os.path.basename(self.filename) 82 return os.path.splitext(basename)[0] 83 84 @property 85 def contents(self): 86 with open(self.filename, 'r') as f: 87 return f.read() 88 89 def GetAlreadyDocumentedModules(): 90 modules = [] 91 for dirname, _, filenames in os.walk(docs_dir): 92 for filename in filenames: 93 path = os.path.join(dirname, filename) 94 modules.append(AlreadyDocumentedModule(path)) 95 return modules 96 97 98 def IsUpdateDocsNeeded(): 99 already_documented_modules = GetAlreadyDocumentedModules() 100 already_documented_modules_by_name = dict( 101 (module.name, module) for module in already_documented_modules) 102 current_modules = GetAllModulesToDocument(telemetry) 103 104 # Quick check: if the names of modules has changed, we definitely need 105 # an update. 106 already_documented_module_names = set( 107 m.name for m in already_documented_modules) 108 109 current_module_names = set([m.__name__ for m in current_modules]) 110 111 if current_module_names != already_documented_module_names: 112 return True 113 114 # Generate the new docs and compare aganist the old. If changed, then a 115 # an update is needed. 116 for current_module in current_modules: 117 already_documented_module = already_documented_modules_by_name[ 118 current_module.__name__] 119 current_html = GenerateHTMLForModule(current_module) 120 if current_html != already_documented_module.contents: 121 return True 122 123 return False 124 125 def Main(args): 126 parser = optparse.OptionParser() 127 parser.add_option( 128 '-v', '--verbose', action='count', dest='verbosity', 129 help='Increase verbosity level (repeat as needed)') 130 options, args = parser.parse_args(args) 131 if options.verbosity >= 2: 132 logging.getLogger().setLevel(logging.DEBUG) 133 elif options.verbosity: 134 logging.getLogger().setLevel(logging.INFO) 135 else: 136 logging.getLogger().setLevel(logging.WARNING) 137 138 assert os.path.isdir(docs_dir) 139 140 RemoveAllDocs() 141 142 old_cwd = os.getcwd() 143 try: 144 os.chdir(telemetry_dir) 145 for module in GetAllModulesToDocument(telemetry): 146 WriteHTMLForModule(module) 147 finally: 148 os.chdir(old_cwd) 149