1 | #!/usr/bin/env python |
---|
2 | |
---|
3 | '''autogenerate documentation of EPICS IOCs''' |
---|
4 | |
---|
5 | |
---|
6 | import CmdReader |
---|
7 | import CommandTable |
---|
8 | import datetime |
---|
9 | import os |
---|
10 | import pprint #@UnusedImport |
---|
11 | import shutil |
---|
12 | import SymbolTable |
---|
13 | import sys |
---|
14 | import traceback #@UnusedImport |
---|
15 | import Trove |
---|
16 | import xmlTools |
---|
17 | import utilities |
---|
18 | |
---|
19 | |
---|
20 | SVN_ID = "$Id: TopDoc.py 1534 2014-04-01 16:52:56Z jemian $" |
---|
21 | |
---|
22 | |
---|
23 | class TopDoc(): |
---|
24 | ''' |
---|
25 | TopDoc documents IOCs in an EPICS top-level directory |
---|
26 | ''' |
---|
27 | config = None |
---|
28 | |
---|
29 | def __init__(self, config, keepAnalyses = True): |
---|
30 | ''' |
---|
31 | Constructor |
---|
32 | ''' |
---|
33 | self.config = config |
---|
34 | self.keepAnalyses = keepAnalyses |
---|
35 | self.project = None |
---|
36 | self.topContent = {} |
---|
37 | self.trove = Trove.Trove() |
---|
38 | |
---|
39 | def learn(self): |
---|
40 | ''' |
---|
41 | Learn about this EPICS top-level directory. |
---|
42 | Store the results in self.topContent |
---|
43 | ''' |
---|
44 | if self.config is None or 'rootDir' not in self.config: |
---|
45 | return None |
---|
46 | original_wd = os.getcwd() |
---|
47 | config_dict = {'config': self.config, 'iocBoot': {}} |
---|
48 | root = self.config['rootDir'] |
---|
49 | for iocConfig in self.config['IOCs']: |
---|
50 | key = os.path.join(iocConfig['iocDir'], iocConfig['cmdFile']) |
---|
51 | bootDir = os.path.join(root, iocConfig['iocDir']) |
---|
52 | if os.path.exists( bootDir ): |
---|
53 | pwd = os.getcwd() |
---|
54 | utilities.chdir( bootDir ) |
---|
55 | self.trove.addFile(iocConfig['cmdFile'], os.path.join(root, key)) |
---|
56 | config_dict['iocBoot'][key] = self.addIOC(iocConfig) |
---|
57 | utilities.chdir( pwd ) |
---|
58 | self.topContent = config_dict |
---|
59 | utilities.chdir( original_wd ) |
---|
60 | |
---|
61 | def addIOC(self, config_dict): |
---|
62 | ''' |
---|
63 | learn about this IOC |
---|
64 | |
---|
65 | :param config_dict: configuration of this IOC |
---|
66 | :return: dictionary of analyses |
---|
67 | ''' |
---|
68 | tables = { |
---|
69 | 'symbols' : SymbolTable.Symbols(), |
---|
70 | 'env' : SymbolTable.Symbols(), |
---|
71 | 'commands' : CommandTable.Commands(), |
---|
72 | 'includeFiles' : SymbolTable.Symbols(), |
---|
73 | 'databases' : SymbolTable.Symbols(), |
---|
74 | 'mounts' : SymbolTable.Symbols(), |
---|
75 | } |
---|
76 | keys = tables.keys() |
---|
77 | tables['analyses'] = {} |
---|
78 | |
---|
79 | path = os.path.join(config_dict['iocDir'], config_dict['cmdFile']) |
---|
80 | utilities.logMessage("processing IOC: %s" % path) |
---|
81 | cr = CmdReader.CmdReader( config_dict['cmdFile'], tables ) |
---|
82 | |
---|
83 | results = {} |
---|
84 | # command files, list avoids redundancy |
---|
85 | results['filenames'] = [(fname, v.absolute_filename) for fname, v in cr.cache_dbFile.items()] |
---|
86 | # databases |
---|
87 | # this code does not capture ALL PV instances, motor for example |
---|
88 | # results are available elsewhere |
---|
89 | # pv_xref = {} |
---|
90 | # for db in tables['databases'].table.values(): |
---|
91 | # for pv_name, pv_dict in db.items(): |
---|
92 | # pv_rtyp = pv_dict['RTYP'] |
---|
93 | # if pv_rtyp not in pv_xref: |
---|
94 | # pv_xref[pv_rtyp] = [] |
---|
95 | # pv_xref[pv_rtyp].append(pv_name) |
---|
96 | # results['pv_rtyp'] = pv_xref |
---|
97 | # templates |
---|
98 | for item in keys: |
---|
99 | results[item] = tables[item].list |
---|
100 | if self.keepAnalyses: |
---|
101 | results['analyses'] = tables['analyses'] |
---|
102 | return results |
---|
103 | |
---|
104 | def remember(self): |
---|
105 | ''' |
---|
106 | try to get the full path to the various component files, amongst other things |
---|
107 | ''' |
---|
108 | utilities.logMessage("remember") |
---|
109 | |
---|
110 | def write(self, targetDirectory): |
---|
111 | ''' |
---|
112 | write what was discovered to file |
---|
113 | |
---|
114 | :param targetDirectory: directory in which to write the results |
---|
115 | :return: (string) name of html file written |
---|
116 | ''' |
---|
117 | # TODO: major refactoring wanted, use ReST instead of XML |
---|
118 | # this implies using proper objects rather than dicts and lists |
---|
119 | # Why? too cumbersome to preserve the analyses and propagate them to documentation via XML |
---|
120 | description = self.config['description'] |
---|
121 | utilities.logMessage("learned about " + description) |
---|
122 | utilities.logMessage("writing results in: %s" % targetDirectory) |
---|
123 | # TODO: do something with other content found in docsDir |
---|
124 | # TODO: should write the analysis into docsDir |
---|
125 | # TODO: make the list of PVs and fields easily identifiable for bean counting |
---|
126 | if self.config['project'] is None: |
---|
127 | return |
---|
128 | xmlFile = os.path.join(targetDirectory, "%s.xml" % self.config['project']) |
---|
129 | writer = xmlTools.XmlWriter("TopDoc") |
---|
130 | writer.root.attrib['file_name'] = xmlFile |
---|
131 | writer.root.attrib['file_time'] = str(datetime.datetime.now()) |
---|
132 | writer.root.attrib['creator'] = SVN_ID |
---|
133 | writer.addStructuredNode(writer.root, "top", {'name': description}, self.topContent) |
---|
134 | xsltFile = 'topdoc.xsl' |
---|
135 | writer.writeFile(xmlFile, xsltFile) |
---|
136 | utilities.logMessage("Wrote file: %s" % xmlFile) |
---|
137 | htmlFile = os.path.splitext(xmlFile)[0] + '.html' |
---|
138 | xmlTools.xsltproc(xmlFile, xsltFile, htmlFile) |
---|
139 | utilities.logMessage("Wrote file: %s" % htmlFile) |
---|
140 | shutil.copy(xsltFile, targetDirectory) |
---|
141 | utilities.logMessage("Wrote file: %s" % os.path.join(targetDirectory, xsltFile)) |
---|
142 | return "%s.html" % self.config['project'] |
---|
143 | |
---|
144 | def strip_analyses_tokens(self): |
---|
145 | '''remove the token analyses from the TopDoc object''' |
---|
146 | # deeply buried content: |
---|
147 | # self.topContent['iocBoot'][*]['analyses'][*][*]['tokens'] |
---|
148 | for st_cmd in self.topContent['iocBoot'].values(): |
---|
149 | for cmd_file in st_cmd['analyses'].values(): |
---|
150 | for analysis in cmd_file.values(): |
---|
151 | del analysis['tokens'] |
---|
152 | |
---|
153 | def processConfigXml(xmlFile): |
---|
154 | ''' |
---|
155 | process the contents of a given config.xml file |
---|
156 | ''' |
---|
157 | utilities.logMessage("processing configuration file: %s" % os.path.abspath( xmlFile )) |
---|
158 | configuration = xmlTools.readConfigurationXML(xmlFile) |
---|
159 | reportDirectory = configuration['reportDirectory'] |
---|
160 | |
---|
161 | html = xmlTools.XmlWriter("html") |
---|
162 | headNode = html.addStructuredNode(html.root, "head") |
---|
163 | html.addStructuredNode(headNode, "title", obj="IOCs") |
---|
164 | bodyNode = html.addStructuredNode(html.root, "body") |
---|
165 | html.addStructuredNode(bodyNode, "h1", obj="Documentation of IOCs") |
---|
166 | listNode = html.addStructuredNode(bodyNode, "ul") |
---|
167 | htmlFile = os.path.join(reportDirectory, 'index.html') |
---|
168 | if not os.path.exists(reportDirectory): |
---|
169 | os.makedirs(reportDirectory) |
---|
170 | |
---|
171 | for config in configuration['list']: |
---|
172 | topLevelDirExists = os.path.exists(config['rootDir']) |
---|
173 | msg = "Looking for: %s (exists=%s)" %(config['rootDir'], str(topLevelDirExists)) |
---|
174 | utilities.logMessage(msg) |
---|
175 | if topLevelDirExists: |
---|
176 | pwd = os.getcwd() |
---|
177 | try: |
---|
178 | td = TopDoc(config, keepAnalyses=True) |
---|
179 | td.learn() |
---|
180 | #td.remember() |
---|
181 | utilities.chdir( pwd ) |
---|
182 | td.strip_analyses_tokens() |
---|
183 | tdFile = td.write(reportDirectory) |
---|
184 | node = html.addStructuredNode(listNode, "li") |
---|
185 | desc = td.config['description'] |
---|
186 | html.addStructuredNode(node, "a", {'href':tdFile}, obj=desc) |
---|
187 | html.writeFile(htmlFile) |
---|
188 | utilities.logMessage("Wrote file: %s" % htmlFile) |
---|
189 | except Exception, exc: |
---|
190 | msg = "problem while handling: %s" % config['rootDir'] |
---|
191 | utilities.detailedExceptionLog(msg) |
---|
192 | traceback.print_exc() |
---|
193 | utilities.chdir( pwd ) |
---|
194 | |
---|
195 | |
---|
196 | def main(): |
---|
197 | import argparse |
---|
198 | parser = argparse.ArgumentParser(prog='topdoc', |
---|
199 | description=__doc__.strip()) |
---|
200 | parser.add_argument('xmlFile', |
---|
201 | action='store', |
---|
202 | #nargs=1, |
---|
203 | help="XML configuration file") |
---|
204 | cmd_args = parser.parse_args() |
---|
205 | |
---|
206 | processConfigXml(cmd_args.xmlFile) |
---|
207 | |
---|
208 | |
---|
209 | if __name__ == '__main__': |
---|
210 | |
---|
211 | # developer use |
---|
212 | #sys.argv.append('-h') |
---|
213 | sys.argv.append('config.xml') |
---|
214 | |
---|
215 | main() |
---|
216 | |
---|
217 | |
---|
218 | ########### SVN repository information ################### |
---|
219 | # $Date: 2014-04-01 16:52:56 +0000 (Tue, 01 Apr 2014) $ |
---|
220 | # $Author: jemian $ |
---|
221 | # $Revision: 1534 $ |
---|
222 | # $URL: topdoc/branches/xml_2014-03-31/src/topdoc/TopDoc.py $ |
---|
223 | # $Id: TopDoc.py 1534 2014-04-01 16:52:56Z jemian $ |
---|
224 | ########### SVN repository information ################### |
---|