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.
4import logging
5import optparse
6import os
7import pkgutil
8import pydoc
9import re
10import sys
11
12import telemetry
13from telemetry.core import util
14
15telemetry_dir = util.GetTelemetryDir()
16docs_dir = os.path.join(telemetry_dir, 'docs', 'pydoc')
17
18def RemoveAllDocs():
19  for dirname, _, filenames in os.walk(docs_dir):
20    for filename in filenames:
21      os.remove(os.path.join(dirname, filename))
22
23def 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, '..')
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
51def 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
58def 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
75class 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
89def 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
98def 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
125def 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), '%s does not exist' % 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