|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the QtHelp generator for the builtin documentation |
|
8 generator. |
|
9 """ |
|
10 |
|
11 from __future__ import unicode_literals |
|
12 |
|
13 import sys |
|
14 import os |
|
15 import shutil |
|
16 import subprocess |
|
17 |
|
18 from Utilities import joinext, relpath, html_encode, getQtBinariesPath, \ |
|
19 generateQtToolName, isExecutable |
|
20 |
|
21 HelpCollection = r"""<?xml version="1.0" encoding="utf-8" ?> |
|
22 <QHelpCollectionProject version="1.0"> |
|
23 <docFiles> |
|
24 <register> |
|
25 <file>{helpfile}</file> |
|
26 </register> |
|
27 </docFiles> |
|
28 </QHelpCollectionProject> |
|
29 """ |
|
30 |
|
31 HelpProject = r"""<?xml version="1.0" encoding="UTF-8"?> |
|
32 <QtHelpProject version="1.0"> |
|
33 <namespace>{namespace}</namespace> |
|
34 <virtualFolder>{folder}</virtualFolder> |
|
35 <customFilter name="{filter_name}"> |
|
36 {filter_attributes} |
|
37 </customFilter> |
|
38 <filterSection> |
|
39 {filter_attributes} |
|
40 <toc> |
|
41 {sections} |
|
42 </toc> |
|
43 <keywords> |
|
44 {keywords} |
|
45 </keywords> |
|
46 <files> |
|
47 {files} |
|
48 </files> |
|
49 </filterSection> |
|
50 </QtHelpProject> |
|
51 """ |
|
52 |
|
53 HelpProjectFile = 'source.qhp' |
|
54 HelpHelpFile = 'source.qch' |
|
55 HelpCollectionProjectFile = 'source.qhcp' |
|
56 HelpCollectionFile = 'collection.qhc' |
|
57 |
|
58 |
|
59 class QtHelpGenerator(object): |
|
60 """ |
|
61 Class implementing the QtHelp generator for the builtin documentation |
|
62 generator. |
|
63 """ |
|
64 def __init__(self, htmlDir, |
|
65 outputDir, namespace, virtualFolder, filterName, |
|
66 filterAttributes, title, createCollection): |
|
67 """ |
|
68 Constructor |
|
69 |
|
70 @param htmlDir directory containing the HTML files (string) |
|
71 @param outputDir output directory for the files (string) |
|
72 @param namespace namespace to be used (string) |
|
73 @param virtualFolder virtual folder to be used (string) |
|
74 @param filterName name of the custom filter (string) |
|
75 @param filterAttributes ':' separated list of filter attributes |
|
76 (string) |
|
77 @param title title to be used for the generated help (string) |
|
78 @param createCollection flag indicating the generation of the |
|
79 collection files (boolean) |
|
80 """ |
|
81 self.htmlDir = htmlDir |
|
82 self.outputDir = outputDir |
|
83 self.namespace = namespace |
|
84 self.virtualFolder = virtualFolder |
|
85 self.filterName = filterName |
|
86 self.filterAttributes = \ |
|
87 filterAttributes and filterAttributes.split(':') or [] |
|
88 self.relPath = relpath(self.htmlDir, self.outputDir) |
|
89 self.title = title |
|
90 self.createCollection = createCollection |
|
91 |
|
92 self.packages = { |
|
93 "00index": { |
|
94 "subpackages": {}, |
|
95 "modules": {} |
|
96 } |
|
97 } |
|
98 self.remembered = False |
|
99 self.keywords = [] |
|
100 |
|
101 def remember(self, file, moduleDocument, basename=""): |
|
102 """ |
|
103 Public method to remember a documentation file. |
|
104 |
|
105 @param file The filename to be remembered. (string) |
|
106 @param moduleDocument The ModuleDocument object containing the |
|
107 information for the file. |
|
108 @param basename The basename of the file hierarchy to be documented. |
|
109 The basename is stripped off the filename if it starts with |
|
110 the basename. |
|
111 """ |
|
112 self.remembered = True |
|
113 if basename: |
|
114 file = file.replace(basename, "") |
|
115 |
|
116 if "__init__" in file: |
|
117 dirName = os.path.dirname(file) |
|
118 udir = os.path.dirname(dirName) |
|
119 if udir: |
|
120 upackage = udir.replace(os.sep, ".") |
|
121 try: |
|
122 elt = self.packages[upackage] |
|
123 except KeyError: |
|
124 elt = self.packages["00index"] |
|
125 else: |
|
126 elt = self.packages["00index"] |
|
127 package = dirName.replace(os.sep, ".") |
|
128 elt["subpackages"][package] = moduleDocument.name() |
|
129 |
|
130 self.packages[package] = { |
|
131 "subpackages": {}, |
|
132 "modules": {} |
|
133 } |
|
134 |
|
135 kwEntry = ("{0} (Package)".format(package.split('.')[-1]), |
|
136 joinext("index-{0}".format(package), ".html")) |
|
137 if kwEntry not in self.keywords: |
|
138 self.keywords.append(kwEntry) |
|
139 |
|
140 if moduleDocument.isEmpty(): |
|
141 return |
|
142 |
|
143 package = os.path.dirname(file).replace(os.sep, ".") |
|
144 try: |
|
145 elt = self.packages[package] |
|
146 except KeyError: |
|
147 elt = self.packages["00index"] |
|
148 elt["modules"][moduleDocument.name()] = moduleDocument.name() |
|
149 |
|
150 if "__init__" not in file: |
|
151 kwEntry = ( |
|
152 "{0} (Module)".format(moduleDocument.name().split('.')[-1]), |
|
153 joinext(moduleDocument.name(), ".html")) |
|
154 if kwEntry not in self.keywords: |
|
155 self.keywords.append(kwEntry) |
|
156 for kw in moduleDocument.getQtHelpKeywords(): |
|
157 kwEntry = (kw[0], "{0}{1}".format( |
|
158 joinext(moduleDocument.name(), ".html"), kw[1])) |
|
159 if kwEntry not in self.keywords: |
|
160 self.keywords.append(kwEntry) |
|
161 |
|
162 def __generateSections(self, package, level): |
|
163 """ |
|
164 Private method to generate the sections part. |
|
165 |
|
166 @param package name of the package to process (string) |
|
167 @param level indentation level (integer) |
|
168 @return sections part (string) |
|
169 """ |
|
170 indent = level * ' ' |
|
171 indent1 = indent + ' ' |
|
172 s = indent + '<section title="{0}" ref="{1}">\n'.format( |
|
173 package == "00index" and self.title or package, |
|
174 package == "00index" and |
|
175 joinext("index", ".html") or |
|
176 joinext("index-{0}".format(package), ".html")) |
|
177 for subpack in sorted(self.packages[package]["subpackages"]): |
|
178 s += self.__generateSections(subpack, level + 1) |
|
179 s += '\n' |
|
180 for mod in sorted(self.packages[package]["modules"]): |
|
181 s += indent1 + '<section title="{0}" ref="{1}" />\n'.format( |
|
182 mod, joinext(mod, ".html")) |
|
183 s += indent + '</section>' |
|
184 return s |
|
185 |
|
186 def __convertEol(self, txt, newline): |
|
187 """ |
|
188 Private method to convert the newline characters. |
|
189 |
|
190 @param txt text to be converted (string) |
|
191 @param newline newline character to be used (string) |
|
192 @return converted text (string) |
|
193 """ |
|
194 # step 1: normalize eol to '\n' |
|
195 txt = txt.replace("\r\n", "\n").replace("\r", "\n") |
|
196 |
|
197 # step 2: convert to the target eol |
|
198 if newline is None: |
|
199 return txt.replace("\n", os.linesep) |
|
200 elif newline in ["\r", "\r\n"]: |
|
201 return txt.replace("\n", newline) |
|
202 else: |
|
203 return txt |
|
204 |
|
205 def generateFiles(self, basename="", newline=None): |
|
206 """ |
|
207 Public method to generate all index files. |
|
208 |
|
209 @param basename The basename of the file hierarchy to be documented. |
|
210 The basename is stripped off the filename if it starts with |
|
211 the basename. |
|
212 @param newline newline character to be used (string) |
|
213 """ |
|
214 if not self.remembered: |
|
215 sys.stderr.write("No QtHelp to generate.\n") |
|
216 return |
|
217 |
|
218 if basename: |
|
219 basename = basename.replace(os.sep, ".") |
|
220 if not basename.endswith("."): |
|
221 basename = "{0}.".format(basename) |
|
222 |
|
223 sections = self.__generateSections("00index", 3) |
|
224 filesList = sorted(e for e in os.listdir(self.htmlDir) |
|
225 if e.endswith('.html')) |
|
226 files = "\n".join( |
|
227 [" <file>{0}</file>".format(f) for f in filesList]) |
|
228 filterAttribs = "\n".join( |
|
229 [" <filterAttribute>{0}</filterAttribute>".format(a) |
|
230 for a in sorted(self.filterAttributes)]) |
|
231 keywords = "\n".join( |
|
232 [' <keyword name="{0}" id="{1}" ref="{2}" />'.format( |
|
233 html_encode(kw[0]), html_encode(kw[0]), html_encode(kw[1])) |
|
234 for kw in sorted(self.keywords)]) |
|
235 |
|
236 helpAttribs = { |
|
237 "namespace": self.namespace, |
|
238 "folder": self.virtualFolder, |
|
239 "filter_name": self.filterName, |
|
240 "filter_attributes": filterAttribs, |
|
241 "sections": sections, |
|
242 "keywords": keywords, |
|
243 "files": files, |
|
244 } |
|
245 |
|
246 txt = self.__convertEol(HelpProject.format(**helpAttribs), newline) |
|
247 f = open(os.path.join(self.outputDir, HelpProjectFile), "w", |
|
248 encoding="utf-8", newline=newline) |
|
249 f.write(txt) |
|
250 f.close() |
|
251 |
|
252 if self.createCollection and \ |
|
253 not os.path.exists( |
|
254 os.path.join(self.outputDir, HelpCollectionProjectFile)): |
|
255 collectionAttribs = { |
|
256 "helpfile": HelpHelpFile, |
|
257 } |
|
258 |
|
259 txt = self.__convertEol( |
|
260 HelpCollection.format(**collectionAttribs), newline) |
|
261 f = open(os.path.join(self.outputDir, HelpCollectionProjectFile), |
|
262 "w", encoding="utf-8", newline=newline) |
|
263 f.write(txt) |
|
264 f.close() |
|
265 |
|
266 sys.stdout.write("QtHelp files written.\n") |
|
267 sys.stdout.write("Generating QtHelp documentation...\n") |
|
268 sys.stdout.flush() |
|
269 sys.stderr.flush() |
|
270 |
|
271 cwd = os.getcwd() |
|
272 # generate the compressed files |
|
273 qhelpgeneratorExe = os.path.join( |
|
274 getQtBinariesPath(), generateQtToolName("qhelpgenerator") |
|
275 ) |
|
276 shutil.copy( |
|
277 os.path.join(self.outputDir, HelpProjectFile), self.htmlDir) |
|
278 os.chdir(self.htmlDir) |
|
279 subprocess.call([ |
|
280 qhelpgeneratorExe, |
|
281 HelpProjectFile, "-o", os.path.join(self.outputDir, HelpHelpFile)]) |
|
282 os.remove(HelpProjectFile) |
|
283 |
|
284 if self.createCollection: |
|
285 qcollectiongeneratorExe = os.path.join( |
|
286 getQtBinariesPath(), generateQtToolName("qcollectiongenerator") |
|
287 ) |
|
288 if not isExecutable(qcollectiongeneratorExe): |
|
289 # assume Qt >= 5.12.0 |
|
290 qcollectiongeneratorExe = qhelpgeneratorExe |
|
291 sys.stdout.write("Generating QtHelp collection...\n") |
|
292 sys.stdout.flush() |
|
293 sys.stderr.flush() |
|
294 os.chdir(self.outputDir) |
|
295 subprocess.call([ |
|
296 qcollectiongeneratorExe, |
|
297 HelpCollectionProjectFile, "-o", HelpCollectionFile]) |
|
298 |
|
299 os.chdir(cwd) |