src/eric7/DocumentationTools/QtHelpGenerator.py

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

eric ide

mercurial