1#!/usr/bin/env python 2""" 3Script to generate documentation for command line utilities 4""" 5import os 6from os.path import join as pjoin 7import re 8from subprocess import Popen, PIPE, CalledProcessError 9import sys 10import importlib 11import inspect 12 13# version comparison 14from distutils.version import LooseVersion as V 15 16# List of workflows to ignore 17SKIP_WORKFLOWS_LIST = ('Workflow', 'CombinedWorkflow') 18 19 20def sh3(cmd): 21 """ 22 Execute command in a subshell, return stdout, stderr 23 If anything appears in stderr, print it out to sys.stderr 24 25 https://github.com/scikit-image/scikit-image/blob/master/doc/gh-pages.py 26 27 Copyright (C) 2011, the scikit-image team All rights reserved. 28 29 Redistribution and use in source and binary forms, with or without 30 modification, are permitted provided that the following conditions are met: 31 32 Redistributions of source code must retain the above copyright notice, 33 this list of conditions and the following disclaimer. 34 Redistributions in binary form must reproduce the above copyright notice, 35 this list of conditions and the following disclaimer in the documentation 36 and/or other materials provided with the distribution. 37 Neither the name of skimage nor the names of its contributors may be used 38 to endorse or promote products derived from this software without specific 39 prior written permission. 40 41 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 42 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 43 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 44 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 45 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 46 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 47 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 48 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 49 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 50 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 51 """ 52 p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True) 53 out, err = p.communicate() 54 retcode = p.returncode 55 if retcode: 56 raise CalledProcessError(retcode, cmd) 57 else: 58 return out.rstrip(), err.rstrip() 59 60 61def abort(error): 62 print('*WARNING* Command line API documentation not generated: %s' % error) 63 exit() 64 65 66def get_help_string(class_obj): 67 # return inspect.getdoc(class_obj.run) 68 try: 69 ia_module = importlib.import_module("dipy.workflows.base") 70 parser = ia_module.IntrospectiveArgumentParser() 71 parser.add_workflow(class_obj()) 72 except Exception as e: 73 abort("Error on {0}: {1}".format(class_obj.__name__, e)) 74 75 return parser.format_help() 76 77 78def format_title(text): 79 text = text.title() 80 line = '-' * len(text) 81 return '{0}\n{1}\n\n'.format(text, line) 82 83 84if __name__ == '__main__': 85 # package name: Eg: dipy 86 package = sys.argv[1] 87 # directory in which the generated rst files will be saved 88 outdir = sys.argv[2] 89 90 try: 91 __import__(package) 92 except ImportError: 93 abort("Cannot import " + package) 94 95 module = sys.modules[package] 96 97 # Check that the source version is equal to the installed 98 # version. If the versions mismatch the API documentation sources 99 # are not (re)generated. This avoids automatic generation of documentation 100 # for older or newer versions if such versions are installed on the system. 101 102 installed_version = V(module.__version__) 103 104 info_file = pjoin('..', package, 'info.py') 105 info_lines = open(info_file).readlines() 106 source_version = '.'.join( 107 [v.split('=')[1].strip(" '\n.") 108 for v in info_lines 109 if re.match('^_version_(major|minor|micro|extra)', v)]) 110 print('***', source_version) 111 112 if source_version != installed_version: 113 abort("Installed version does not match source version") 114 115 # generate docs 116 command_list = [] 117 118 workflows_folder = pjoin('..', 'bin') 119 workflow_module = importlib.import_module("dipy.workflows.workflow") 120 121 workflow_flist = [os.path.abspath(pjoin(workflows_folder, f)) 122 for f in os.listdir(workflows_folder) 123 if os.path.isfile(pjoin(workflows_folder, f)) and 124 f.lower().startswith("dipy_")] 125 126 workflow_flist = sorted(workflow_flist) 127 workflow_desc = {} 128 # We get all workflows class obj in a dictionary 129 for path_file in os.listdir(pjoin('..', 'dipy', 'workflows')): 130 module_name = inspect.getmodulename(path_file) 131 if module_name is None: 132 continue 133 134 module = importlib.import_module("dipy.workflows." + module_name) 135 members = inspect.getmembers(module) 136 d_wkflw = {name: {"module": obj, "help": get_help_string(obj)} 137 for name, obj in members 138 if inspect.isclass(obj) and 139 issubclass(obj, workflow_module.Workflow) and 140 name not in SKIP_WORKFLOWS_LIST 141 } 142 143 workflow_desc.update(d_wkflw) 144 145 cmd_list = [] 146 for fpath in workflow_flist: 147 fname = os.path.basename(fpath) 148 with open(fpath) as file_object: 149 flow_name = set(re.findall(r"[A-Z]\w+Flow", file_object.read(), 150 re.X | re.M)) 151 152 if not flow_name or len(flow_name) != 1: 153 continue 154 155 flow_name = list(flow_name)[-1] 156 print("Generating docs for: {0} ({1})".format(fname, flow_name)) 157 out_fname = fname + ".rst" 158 with open(pjoin(outdir, out_fname), "w") as fp: 159 dashes = "=" * len(fname) 160 fp.write("\n{0}\n{1}\n{0}\n\n".format(dashes, fname)) 161 # Trick to avoid docgen_cmd.py as cmd line 162 help_txt = workflow_desc[flow_name]["help"] 163 help_txt = help_txt.replace("docgen_cmd.py", fname) 164 help_txt = help_txt.replace("usage:", format_title('usage')) 165 help_txt = help_txt.replace("positional arguments:", 166 format_title("positional arguments")) 167 help_txt = help_txt.replace("optional arguments:", 168 format_title("optional arguments")) 169 help_txt = help_txt.replace( 170 "output arguments(optional):", 171 format_title("output arguments(optional)")) 172 help_txt = help_txt.replace("References:", 173 format_title("References")) 174 help_txt = help_txt.rstrip() 175 fp.write(help_txt) 176 177 cmd_list.append(out_fname) 178 print("Done") 179 180 # generate index.rst 181 print("Generating index.rst") 182 with open(pjoin(outdir, "index.rst"), "w") as index: 183 index.write(".. _workflows_reference:\n\n") 184 index.write("Command Line Utilities Reference\n") 185 index.write("================================\n\n") 186 index.write(".. toctree::\n\n") 187 for cmd in cmd_list: 188 index.write(" " + cmd) 189 index.write("\n") 190 print("Done") 191