1"""Script to generate doxygen documentation. 2""" 3from __future__ import print_function 4from __future__ import unicode_literals 5from devtools import tarball 6from contextlib import contextmanager 7import subprocess 8import traceback 9import re 10import os 11import sys 12import shutil 13 14@contextmanager 15def cd(newdir): 16 """ 17 http://stackoverflow.com/questions/431684/how-do-i-cd-in-python 18 """ 19 prevdir = os.getcwd() 20 os.chdir(newdir) 21 try: 22 yield 23 finally: 24 os.chdir(prevdir) 25 26def find_program(*filenames): 27 """find a program in folders path_lst, and sets env[var] 28 @param filenames: a list of possible names of the program to search for 29 @return: the full path of the filename if found, or '' if filename could not be found 30""" 31 paths = os.environ.get('PATH', '').split(os.pathsep) 32 suffixes = ('win32' in sys.platform) and '.exe .com .bat .cmd' or '' 33 for filename in filenames: 34 for name in [filename+ext for ext in suffixes.split(' ')]: 35 for directory in paths: 36 full_path = os.path.join(directory, name) 37 if os.path.isfile(full_path): 38 return full_path 39 return '' 40 41def do_subst_in_file(targetfile, sourcefile, dict): 42 """Replace all instances of the keys of dict with their values. 43 For example, if dict is {'%VERSION%': '1.2345', '%BASE%': 'MyProg'}, 44 then all instances of %VERSION% in the file will be replaced with 1.2345 etc. 45 """ 46 with open(sourcefile, 'r') as f: 47 contents = f.read() 48 for (k,v) in list(dict.items()): 49 v = v.replace('\\','\\\\') 50 contents = re.sub(k, v, contents) 51 with open(targetfile, 'w') as f: 52 f.write(contents) 53 54def getstatusoutput(cmd): 55 """cmd is a list. 56 """ 57 try: 58 process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 59 output, _ = process.communicate() 60 status = process.returncode 61 except: 62 status = -1 63 output = traceback.format_exc() 64 return status, output 65 66def run_cmd(cmd, silent=False): 67 """Raise exception on failure. 68 """ 69 info = 'Running: %r in %r' %(' '.join(cmd), os.getcwd()) 70 print(info) 71 sys.stdout.flush() 72 if silent: 73 status, output = getstatusoutput(cmd) 74 else: 75 status, output = subprocess.call(cmd), '' 76 if status: 77 msg = 'Error while %s ...\n\terror=%d, output="""%s"""' %(info, status, output) 78 raise Exception(msg) 79 80def assert_is_exe(path): 81 if not path: 82 raise Exception('path is empty.') 83 if not os.path.isfile(path): 84 raise Exception('%r is not a file.' %path) 85 if not os.access(path, os.X_OK): 86 raise Exception('%r is not executable by this user.' %path) 87 88def run_doxygen(doxygen_path, config_file, working_dir, is_silent): 89 assert_is_exe(doxygen_path) 90 config_file = os.path.abspath(config_file) 91 with cd(working_dir): 92 cmd = [doxygen_path, config_file] 93 run_cmd(cmd, is_silent) 94 95def build_doc(options, make_release=False): 96 if make_release: 97 options.make_tarball = True 98 options.with_dot = True 99 options.with_html_help = True 100 options.with_uml_look = True 101 options.open = False 102 options.silent = True 103 104 version = open('version', 'rt').read().strip() 105 output_dir = 'dist/doxygen' # relative to doc/doxyfile location. 106 if not os.path.isdir(output_dir): 107 os.makedirs(output_dir) 108 top_dir = os.path.abspath('.') 109 html_output_dirname = 'jsoncpp-api-html-' + version 110 tarball_path = os.path.join('dist', html_output_dirname + '.tar.gz') 111 warning_log_path = os.path.join(output_dir, '../jsoncpp-doxygen-warning.log') 112 html_output_path = os.path.join(output_dir, html_output_dirname) 113 def yesno(bool): 114 return bool and 'YES' or 'NO' 115 subst_keys = { 116 '%JSONCPP_VERSION%': version, 117 '%DOC_TOPDIR%': '', 118 '%TOPDIR%': top_dir, 119 '%HTML_OUTPUT%': os.path.join('..', output_dir, html_output_dirname), 120 '%HAVE_DOT%': yesno(options.with_dot), 121 '%DOT_PATH%': os.path.split(options.dot_path)[0], 122 '%HTML_HELP%': yesno(options.with_html_help), 123 '%UML_LOOK%': yesno(options.with_uml_look), 124 '%WARNING_LOG_PATH%': os.path.join('..', warning_log_path) 125 } 126 127 if os.path.isdir(output_dir): 128 print('Deleting directory:', output_dir) 129 shutil.rmtree(output_dir) 130 if not os.path.isdir(output_dir): 131 os.makedirs(output_dir) 132 133 do_subst_in_file('doc/doxyfile', options.doxyfile_input_path, subst_keys) 134 run_doxygen(options.doxygen_path, 'doc/doxyfile', 'doc', is_silent=options.silent) 135 if not options.silent: 136 print(open(warning_log_path, 'r').read()) 137 index_path = os.path.abspath(os.path.join('doc', subst_keys['%HTML_OUTPUT%'], 'index.html')) 138 print('Generated documentation can be found in:') 139 print(index_path) 140 if options.open: 141 import webbrowser 142 webbrowser.open('file://' + index_path) 143 if options.make_tarball: 144 print('Generating doc tarball to', tarball_path) 145 tarball_sources = [ 146 output_dir, 147 'README.md', 148 'LICENSE', 149 'NEWS.txt', 150 'version' 151 ] 152 tarball_basedir = os.path.join(output_dir, html_output_dirname) 153 tarball.make_tarball(tarball_path, tarball_sources, tarball_basedir, html_output_dirname) 154 return tarball_path, html_output_dirname 155 156def main(): 157 usage = """%prog 158 Generates doxygen documentation in build/doxygen. 159 Optionally makes a tarball of the documentation to dist/. 160 161 Must be started in the project top directory. 162 """ 163 from optparse import OptionParser 164 parser = OptionParser(usage=usage) 165 parser.allow_interspersed_args = False 166 parser.add_option('--with-dot', dest="with_dot", action='store_true', default=False, 167 help="""Enable usage of DOT to generate collaboration diagram""") 168 parser.add_option('--dot', dest="dot_path", action='store', default=find_program('dot'), 169 help="""Path to GraphViz dot tool. Must be full qualified path. [Default: %default]""") 170 parser.add_option('--doxygen', dest="doxygen_path", action='store', default=find_program('doxygen'), 171 help="""Path to Doxygen tool. [Default: %default]""") 172 parser.add_option('--in', dest="doxyfile_input_path", action='store', default='doc/doxyfile.in', 173 help="""Path to doxygen inputs. [Default: %default]""") 174 parser.add_option('--with-html-help', dest="with_html_help", action='store_true', default=False, 175 help="""Enable generation of Microsoft HTML HELP""") 176 parser.add_option('--no-uml-look', dest="with_uml_look", action='store_false', default=True, 177 help="""Generates DOT graph without UML look [Default: False]""") 178 parser.add_option('--open', dest="open", action='store_true', default=False, 179 help="""Open the HTML index in the web browser after generation""") 180 parser.add_option('--tarball', dest="make_tarball", action='store_true', default=False, 181 help="""Generates a tarball of the documentation in dist/ directory""") 182 parser.add_option('-s', '--silent', dest="silent", action='store_true', default=False, 183 help="""Hides doxygen output""") 184 parser.enable_interspersed_args() 185 options, args = parser.parse_args() 186 build_doc(options) 187 188if __name__ == '__main__': 189 main() 190