summaryrefslogtreecommitdiff
path: root/taurus/doc/auto_rst4api.py
blob: a21dbcbb93351d3a725a00102d1ab37467744dd1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#############################################################################
##
## This file is part of Taurus
## 
## http://taurus-scada.org
##
## Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
## 
## Taurus is free software: you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## 
## Taurus is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU Lesser General Public License for more details.
## 
## You should have received a copy of the GNU Lesser General Public License
## along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
##
###########################################################################

''' Creates a tree of dirs and restructured text stub files for documenting 
the API of a python module with sphinx'''

import sys, os
import imp
from jinja2 import Environment, FileSystemLoader

def taurusabspath(*path):
    """A method to determine absolute path for a given relative path to the
    directory where the setup.py script is located"""
    this_dir = os.path.dirname(os.path.abspath(__file__))
    setup_dir = os.path.abspath(os.path.join(this_dir, os.pardir))
    return os.path.join(setup_dir, *path)

#import moduleexplorer from the sources, and without importing taurus
__name = "moduleexplorer"
__path = taurusabspath('lib', 'taurus', 'test', 'moduleexplorer.py')
ModuleExplorer = imp.load_source(__name, __path).ModuleExplorer


class Auto_rst4API_Creator(object):
    AUTOGEN_SIGNATURE = '.. AUTO_RST4API'
    AUTOGEN_MESSAGE ='.. This file was generated by auto_rst4api.py. Changes may be lost'
    
    def __init__(self, templatespath='./', moduletemplate='api_module.rst', classtemplate='api_class.rst',
                 classindextemplate='api_AllClasses.rst', exclude_patterns=(), verbose=True, overwrite_old=False):
        '''
        :param templates: (str) path to dir where template files are located
        :param moduletemplate: (str) name of the template to be used for module pages
        :param classtemplate: (str) name of the template to be used for class pages
        :param classindextemplate: (str) name of the template to be used for class index page
        :param verbose: (bool) If True (default) status messages will be printed to stdout
        '''
        
        self.verbose = verbose
        self.exclude_patterns = exclude_patterns
        
        self.env = Environment(loader=FileSystemLoader(templatespath))
        self.moduletemplate = self.env.get_template(moduletemplate)
        self.classtemplate = self.env.get_template(classtemplate)
        self.classindextemplate = self.env.get_template(classindextemplate)
        self.overwrite_old = overwrite_old
    
    def _isautogeneratedfile(self, fname):
        ret = False
        f = open(fname,'r')
        lines = f.readlines()
        for l in lines:
            if l.startswith(self.AUTOGEN_SIGNATURE):
                ret = True
                break
        f.close()
        
        return ret
    
    def cleanAutogenerated(self, apipath):
        '''Removes any previously autogenerated rst file in the given path 
        or its subdirectories
        
        :param apipath: (str) directory to clean
        '''
        for dirpath,dirnames,filenames in os.walk(apipath):
            for f in filenames:
                if f.endswith('.rst'):
                    fullname = os.path.join(dirpath,f)
                    try :
                        if self._isautogeneratedfile(fullname):
                            print "Removing %s"%fullname
                            os.remove(fullname)
                    except Exception, e:
                        print 'Error accessing %s:%s'%(fullname,repr(e))
        
    def createClassIndex(self, info, ofname ):
        '''
        Creates a class index page using the classindextemplate.
        
        :param info: (dict) dictionary containing the information about the
                     items to document for this module (as generated by 
                     :meth:`exploreModule`
        :param ofname: (str) output file name       
        '''
        classes = ModuleExplorer.getAll(info, 'localclassnames') #this is a list of tuples of (modulename,class)
        classes = sorted(classes, key=lambda item: item[1]) #sort it by class name
        classes = ['.'.join((m,c)) for m,c in classes] # make a full classname 
        if self.verbose: print 'creating "%s" ...'%ofname, 
        if not os.path.exists(ofname) or (self.overwrite_old and self._isautogeneratedfile(ofname)):
            text = self.classindextemplate.render(info=info, classes=classes)
            f = open(ofname, "w")
            f.write('\n'.join((self.AUTOGEN_SIGNATURE, self.AUTOGEN_MESSAGE, text)))
            f.close()
            if self.verbose: print ' ok.'
        else:
            if self.verbose: print ' skipping (file already exists)'
    
    def createStubs(self, info, docparentpath):
        '''creates rst stub files for modules and classes according to the
        information contained in info.

        :param info: (dict) dictionary containing the information about the
                     items to document for this module (as generated by 
                     :meth:`exploreModule`)
        :docparentpath: (str) path to the directory in which the documentation 
                        files will be written   
        '''
        #create the module doc dir if it didn't exist
        absdocpath = os.path.join(docparentpath, info['basemodulename'])   
        if not os.path.exists(absdocpath):
            os.makedirs(absdocpath, mode=0755)
        #create module index stub in doc parent dir
        ofname = os.path.join(docparentpath,"%s.rst"%info['basemodulename'])
        if self.verbose: print 'creating "%s" ...'%ofname, 
        if not os.path.exists(ofname) or (self.overwrite_old and self._isautogeneratedfile(ofname)):
            text = self.moduletemplate.render(info=info)
            f = open(ofname, "w")
            f.write('\n'.join((self.AUTOGEN_SIGNATURE, self.AUTOGEN_MESSAGE, text)))
            f.close()
            if self.verbose: print ' ok.'
        else:
            if self.verbose: print ' skipping (file already exists)'
        #create class stubs
        for name in info['localclassnames']:
            ofname = os.path.join(absdocpath,"_%s.rst"%name)
            if self.verbose: print 'creating "%s" ...'%ofname, 
            if not os.path.exists(ofname) or (self.overwrite_old and self._isautogeneratedfile(ofname)):
                text = self.classtemplate.render(info=info, classname=name)
                f = open(ofname, "w")
                f.write('\n'.join((self.AUTOGEN_SIGNATURE, self.AUTOGEN_MESSAGE, text)))
                f.close()
                if self.verbose: print ' ok.'
            else:
                if self.verbose: print ' skipping (file already exists)'
        #recurse for submodules
        for sminfo in info['submodules'].itervalues():
            self.createStubs(sminfo, absdocpath)
    
    def documentModule(self, modulename, docparentpath, exclude_patterns=None):
        '''
        recursive function that walks on the module structure and generates
        documentation files for the given module and its submodules. It also
        creates a class index for the root module
        
        :param modulename: (str) name of the module to document
        :docparentpath: (str) path to the directory in which the documentation 
                        files will be written
        :param exclude_patterns: (seq<str>) sequence of strings containing regexp
                 patterns. Each candidate to be documented will be 
                 matched against these patterns and will be excluded
                 if it matches any of them.
                        
        :return: (list<str>) list of warning messages 
        '''
        if self.verbose: print "\nDocumenting %s..."%modulename
        if exclude_patterns is None:
            exclude_patterns = self.exclude_patterns
        moduleinfo, w = ModuleExplorer.explore(modulename, 
                                               exclude_patterns=exclude_patterns, 
                                               verbose=self.verbose)
        self.createStubs(moduleinfo, docparentpath)
        self.createClassIndex(moduleinfo, os.path.join(docparentpath,"%s_AllClasses.rst"%modulename))
        if len (w) == 0: return []
        else: return zip(*w)[1]
              
        
def main():    
    import sys
    if len(sys.argv) != 3:
        print 'Usage:\n\t%s modulename docpreffix\n\n'%sys.argv[0]
        sys.exit(1)            
    modulename, docparentpath = sys.argv[1:]
    creator = Auto_rst4API_Creator(verbose=True)
    r = creator.documentModule(modulename, docparentpath, exclude_patterns = ['.*/ui'])
    print '\n\n'+'*'*50
    print "Auto Creation of API docs for %s Finished with %i warnings:"%(modulename,len(r))
    print '\n'.join(r)
    print '*'*50+'\n'
    
if __name__ == "__main__":    
    main()