KApiDox

generator.py
1 # -*- coding: utf-8 -*-
2 #
3 # SPDX-FileCopyrightText: 2014 Alex Merry <[email protected]>
4 # SPDX-FileCopyrightText: 2014 Aurélien Gâteau <[email protected]>
5 # SPDX-FileCopyrightText: 2014 Alex Turbov <[email protected]>
6 # SPDX-FileCopyrightText: 2016 Olivier Churlaud <[email protected]>
7 #
8 # SPDX-License-Identifier: BSD-2-Clause
9 
10 import codecs
11 from distutils.spawn import find_executable
12 import datetime
13 import os
14 import logging
15 import shutil
16 import subprocess
17 import tempfile
18 import sys
19 import xml.etree.ElementTree as ET
20 
21 import jinja2
22 
23 from urllib.parse import urljoin
24 
25 import xml.etree.ElementTree as xmlET
26 import json
27 
28 from kapidox import utils
29 try:
30  from kapidox import depdiagram
31  DEPDIAGRAM_AVAILABLE = True
32 except ImportError:
33  DEPDIAGRAM_AVAILABLE = False
34 
35 from .doxyfilewriter import DoxyfileWriter
36 
37 
38 ## @package kapidox.generator
39 #
40 # The generator
41 
42 __all__ = (
43  "Context",
44  "generate_apidocs",
45  "search_for_tagfiles",
46  "WARN_LOGFILE",
47  "build_classmap",
48  "postprocess",
49  "create_dirs",
50  "create_jinja_environment",
51  )
52 
53 WARN_LOGFILE = 'doxygen-warnings.log'
54 
55 HTML_SUBDIR = 'html'
56 
57 
58 class Context(object):
59  """
60  Holds parameters used by the various functions of the generator
61  """
62  __slots__ = (
63  # Names
64  'modulename',
65  'fancyname',
66  'title',
67  'fwinfo',
68  # KApidox files
69  'doxdatadir',
70  'resourcedir',
71  # Input
72  'srcdir',
73  'tagfiles',
74  'dependency_diagram',
75  'copyright',
76  # Output
77  'outputdir',
78  'htmldir',
79  'tagfile',
80  # Output options
81  'man_pages',
82  'qhp',
83  # Binaries
84  'doxygen',
85  'qhelpgenerator',
86  )
87 
88  def __init__(self, args, **kwargs):
89  # Names
90  self.title = args.title
91  # KApidox files
92  self.doxdatadir = args.doxdatadir
93  # Output options
94  self.man_pages = args.man_pages
95  self.qhp = args.qhp
96  # Binaries
97  self.doxygen = args.doxygen
98  self.qhelpgenerator = args.qhelpgenerator
99 
100  for key in self.__slots__:
101  if not hasattr(self, key):
102  setattr(self, key, kwargs.get(key))
103 
104 
105 def create_jinja_environment(doxdatadir):
106  loader = jinja2.FileSystemLoader(os.path.join(doxdatadir, 'templates'))
107  return jinja2.Environment(loader=loader)
108 
109 
110 def process_toplevel_html_file(outputfile, doxdatadir, products, title, qch_enabled=False):
111 
112  products.sort(key=lambda x: x.fancyname.lower())
113  mapping = {
114  'resources': './resources',
115  # steal the doxygen css from one of the frameworks
116  # this means that all the doxygen-provided images etc. will be found
117  'title': title,
118  'qch': qch_enabled,
119  'breadcrumbs': {
120  'entries': [
121  {
122  'href': './index.html',
123  'text': 'KDE API Reference'
124  }
125  ]
126  },
127  'product_list': products,
128  }
129  tmpl = create_jinja_environment(doxdatadir).get_template('frontpage.html')
130  with codecs.open(outputfile, 'w', 'utf-8') as outf:
131  outf.write(tmpl.render(mapping))
132 
133  tmpl2 = create_jinja_environment(doxdatadir).get_template('search.html')
134  search_output = "search.html"
135  with codecs.open(search_output, 'w', 'utf-8') as outf:
136  outf.write(tmpl2.render(mapping))
137 
138 
139 def process_subgroup_html_files(outputfile, doxdatadir, groups, available_platforms, title, qch_enabled=False):
140 
141  for group in groups:
142  mapping = {
143  'resources': '../resources',
144  'title': title,
145  'qch': qch_enabled,
146  'breadcrumbs': {
147  'entries': [
148  {
149  'href': '../index.html',
150  'text': 'KDE API Reference'
151  },
152  {
153  'href': './index.html',
154  'text': group.fancyname
155  }
156  ]
157  },
158  'group': group,
159  'available_platforms': sorted(available_platforms),
160  }
161 
162  if not os.path.isdir(group.name):
163  os.mkdir(group.name)
164  outputfile = group.name + '/index.html'
165  tmpl = create_jinja_environment(doxdatadir).get_template('subgroup.html')
166  with codecs.open(outputfile, 'w', 'utf-8') as outf:
167  outf.write(tmpl.render(mapping))
168 
169  tmpl2 = create_jinja_environment(doxdatadir).get_template('search.html')
170  search_output = group.name + "/search.html"
171  with codecs.open(search_output, 'w', 'utf-8') as outf:
172  outf.write(tmpl2.render(mapping))
173 
174 
175 def create_dirs(ctx):
176  ctx.htmldir = os.path.join(ctx.outputdir, HTML_SUBDIR)
177  ctx.tagfile = os.path.join(ctx.htmldir, ctx.fwinfo.fancyname + '.tags')
178 
179  if not os.path.exists(ctx.outputdir):
180  os.makedirs(ctx.outputdir)
181 
182  if os.path.exists(ctx.htmldir):
183  # If we have files left there from a previous run but which are no
184  # longer generated (for example because a C++ class has been removed)
185  # then postprocess will fail because the left-over file has already been
186  # processed. To avoid that, we delete the html dir.
187  shutil.rmtree(ctx.htmldir)
188  os.makedirs(ctx.htmldir)
189 
190 
191 def load_template(path):
192  # Set errors to 'ignore' because we don't want weird characters in Doxygen
193  # output (for example source code listing) to cause a failure
194  content = codecs.open(path, encoding='utf-8', errors='ignore').read()
195  try:
196  return jinja2.Template(content)
197  except jinja2.exceptions.TemplateSyntaxError:
198  logging.error('Failed to parse template {}'.format(path))
199  raise
200 
201 
202 def find_tagfiles(docdir, doclink=None, flattenlinks=False, exclude=None, _depth=0):
203  """Find Doxygen-generated tag files in a directory.
204 
205  The tag files must have the extension .tags, and must be in the listed
206  directory, a subdirectory or a subdirectory named html of a subdirectory.
207 
208  Args:
209  docdir: (string) the directory to search.
210  doclink: (string) the path or URL to use when creating the
211  documentation links; if None, this will default to
212  docdir. (optional, default None)
213  flattenlinks: (bool) if True, generated links will assume all the html
214  files are directly under doclink; else the html files are
215  assumed to be at the same relative location to doclink as
216  the tag file is to docdir; ignored if doclink is not set.
217  (optional, default False)
218 
219  Returns:
220  A list of pairs of (tag_file,link_path).
221  """
222 
223  if not os.path.isdir(docdir):
224  return []
225 
226  if doclink is None:
227  doclink = docdir
228  flattenlinks = False
229 
230  def smartjoin(pathorurl1, *args):
231  """Join paths or URLS
232 
233  It figures out which it is from whether the first contains a "://"
234  """
235  if '://' in pathorurl1:
236  if not pathorurl1.endswith('/'):
237  pathorurl1 += '/'
238  return urljoin(pathorurl1, *args)
239  else:
240  return os.path.join(pathorurl1, *args)
241 
242  def nestedlink(subdir):
243  if flattenlinks:
244  return doclink
245  else:
246  return smartjoin(doclink, subdir)
247 
248  tagfiles = []
249 
250  entries = os.listdir(docdir)
251  for e in entries:
252  if e == exclude:
253  continue
254  path = os.path.join(docdir, e)
255  if os.path.isfile(path) and e.endswith('.tags'):
256  tagfiles.append((path, doclink))
257  elif (_depth == 0 or (_depth == 1 and e == 'html')) and os.path.isdir(path):
258  tagfiles += find_tagfiles(path, nestedlink(e),
259  flattenlinks=flattenlinks,
260  _depth=_depth+1,
261  exclude=exclude)
262 
263  return tagfiles
264 
265 
266 def search_for_tagfiles(suggestion=None, doclink=None, flattenlinks=False, searchpaths=[], exclude=None):
267  """Find Doxygen-generated tag files
268 
269  See the find_tagfiles documentation for how the search is carried out in
270  each directory; this just allows a list of directories to be searched.
271 
272  At least one of docdir or searchpaths must be given for it to find anything.
273 
274  Args:
275  suggestion: the first place to look (will complain if there are no
276  documentation tag files there)
277  doclink: the path or URL to use when creating the documentation
278  links; if None, this will default to docdir
279  flattenlinks: if this is True, generated links will assume all the html
280  files are directly under doclink; if False (the default),
281  the html files are assumed to be at the same relative
282  location to doclink as the tag file is to docdir; ignored
283  if doclink is not set
284  searchpaths: other places to look for documentation tag files
285 
286  Returns:
287  A list of pairs of (tag_file,link_path)
288  """
289 
290  if not suggestion is None:
291  if not os.path.isdir(suggestion):
292  logging.warning(suggestion + " is not a directory")
293  else:
294  tagfiles = find_tagfiles(suggestion, doclink, flattenlinks, exclude)
295  if len(tagfiles) == 0:
296  logging.warning(suggestion + " does not contain any tag files")
297  else:
298  return tagfiles
299 
300  for d in searchpaths:
301  tagfiles = find_tagfiles(d, doclink, flattenlinks, exclude)
302  if len(tagfiles) > 0:
303  logging.info("Documentation tag files found at " + d)
304  return tagfiles
305 
306  return []
307 
308 
309 
310 def menu_items(htmldir, modulename):
311  """Menu items for standard Doxygen files
312 
313  Looks for a set of standard Doxygen files (like namespaces.html) and
314  provides menu text for those it finds in htmldir.
315 
316  Args:
317  htmldir: (string) the directory the HTML files are contained in.
318  modulname: (string) the name of the library
319 
320  Returns:
321  A list of maps with 'text' and 'href' keys.
322  """
323  entries = [
324  {'text': 'Main Page', 'href': 'index.html'},
325  {'text': 'Namespace List', 'href': 'namespaces.html'},
326  {'text': 'Namespace Members', 'href': 'namespacemembers.html'},
327  {'text': 'Alphabetical List', 'href': 'classes.html'},
328  {'text': 'Class List', 'href': 'annotated.html'},
329  {'text': 'Class Hierarchy', 'href': 'hierarchy.html'},
330  {'text': 'File List', 'href': 'files.html'},
331  {'text': 'File Members', 'href': 'globals.html'},
332  {'text': 'Modules', 'href': 'modules.html'},
333  {'text': 'Directories', 'href': 'dirs.html'},
334  {'text': 'Dependencies', 'href': modulename + '-dependencies.html'},
335  {'text': 'Related Pages', 'href': 'pages.html'},
336  ]
337  # NOTE In Python 3 filter() builtin returns an iterable, but not a list
338  # type, so explicit conversion is here!
339  return list(filter(
340  lambda e: os.path.isfile(os.path.join(htmldir, e['href'])),
341  entries))
342 
343 
344 def parse_dox_html(stream):
345  """Parse the HTML files produced by Doxygen, extract the key/value block we
346  add through header.html and return a dict ready for the Jinja template.
347 
348  The HTML files produced by Doxygen with our custom header and footer files
349  look like this:
350 
351  @code
352  <!--
353  key1: value1
354  key2: value2
355  ...
356  -->
357  <html>
358  <head>
359  ...
360  </head>
361  <body>
362  ...
363  </body>
364  </html>
365  @endcode
366 
367  The parser fills the dict from the top key/value block, and add the content
368  of the body to the dict using the "content" key.
369 
370  We do not use an XML parser because the HTML file might not be well-formed,
371  for example if the documentation contains raw HTML.
372 
373  The key/value block is kept in a comment so that it does not appear in Qt
374  Compressed Help output, which is not postprocessed by ourself.
375  """
376  dct = {}
377  body = []
378 
379  def parse_key_value_block(line):
380  if line == "<!--":
381  return parse_key_value_block
382  if line == "-->":
383  return skip_head
384  key, value = line.split(': ', 1)
385  dct[key] = value
386  return parse_key_value_block
387 
388  def skip_head(line):
389  if line == "<body>":
390  return extract_body
391  else:
392  return skip_head
393 
394  def extract_body(line):
395  if line == "</body>":
396  return None
397  body.append(line)
398  return extract_body
399 
400  parser = parse_key_value_block
401  while parser is not None:
402  line = stream.readline().rstrip()
403  parser = parser(line)
404 
405  dct['content'] = '\n'.join(body)
406  return dct
407 
408 
409 def postprocess_internal(htmldir, tmpl, mapping):
410  """Substitute text in HTML files
411 
412  Performs text substitutions on each line in each .html file in a directory.
413 
414  Args:
415  htmldir: (string) the directory containing the .html files.
416  mapping: (dict) a dict of mappings.
417 
418  """
419  for name in os.listdir(htmldir):
420  if name.endswith('.html'):
421  path = os.path.join(htmldir, name)
422  newpath = path + '.new'
423 
424  if name != 'classes.html' and name.startswith('class'):
425  mapping['classname'] = name[5:-5].split('_1_1')[-1]
426  mapping['fullname'] = name[5:-5].replace('_1_1', '::')
427  elif name.startswith('namespace') and name != 'namespaces.html' and not name.startswith('namespacemembers'):
428  mapping['classname'] = None
429  mapping['fullname'] = name[9:-5].replace('_1_1', '::')
430  else:
431  mapping['classname'] = None
432  mapping['fullname'] = None
433 
434  with codecs.open(path, 'r', 'utf-8', errors='ignore') as f:
435  mapping['dox'] = parse_dox_html(f)
436 
437  with codecs.open(newpath, 'w', 'utf-8') as outf:
438  try:
439  html = tmpl.render(mapping)
440  except Exception:
441  logging.error('postprocessing {} failed'.format(path))
442  raise
443  outf.write(html)
444  os.remove(path)
445  os.rename(newpath, path)
446 
447 
448 def build_classmap(tagfile):
449  """Parses a tagfile to get a map from classes to files
450 
451  Args:
452  tagfile: the Doxygen-generated tagfile to parse.
453 
454  Returns:
455  A list of maps (keys: classname and filename).
456  """
457  import xml.etree.ElementTree as ET
458  tree = ET.parse(tagfile)
459  tagfile_root = tree.getroot()
460  mapping = []
461  for compound in tagfile_root:
462  kind = compound.get('kind')
463  if kind == 'class' or kind == 'namespace':
464  name_el = compound.find('name')
465  filename_el = compound.find('filename')
466  mapping.append({'classname': name_el.text,
467  'filename': filename_el.text})
468  return mapping
469 
470 
471 def generate_dependencies_page(tmp_dir, doxdatadir, modulename, dependency_diagram):
472  """Create `modulename`-dependencies.md in `tmp_dir`"""
473  template_path = os.path.join(doxdatadir, 'dependencies.md.tmpl')
474  out_path = os.path.join(tmp_dir, modulename + '-dependencies.md')
475  tmpl = load_template(template_path)
476  with codecs.open(out_path, 'w', 'utf-8') as outf:
477  txt = tmpl.render({
478  'modulename': modulename,
479  'diagramname': os.path.basename(dependency_diagram),
480  })
481  outf.write(txt)
482  return out_path
483 
484 
485 def generate_apidocs(ctx, tmp_dir, doxyfile_entries=None, keep_temp_dirs=False):
486  """Generate the API documentation for a single directory"""
487 
488  def find_src_subdir(dirlist, deeper_subd=None):
489  returnlist = []
490  for d in dirlist:
491  pth = os.path.join(ctx.fwinfo.path, d)
492  if deeper_subd is not None:
493  pth = os.path.join(pth, deeper_subd)
494  if os.path.isdir(pth) or os.path.isfile(pth):
495  returnlist.append(pth)
496  else:
497  pass # We drop it
498  return returnlist
499 
500  input_list = []
501  if os.path.isfile(ctx.fwinfo.path + "/Mainpage.dox"):
502  input_list.append(ctx.fwinfo.path + "/Mainpage.dox")
503  elif os.path.isfile(ctx.fwinfo.path + "/README.md"):
504  input_list.append(ctx.fwinfo.path + "/README.md")
505 
506  input_list.extend(find_src_subdir(ctx.fwinfo.srcdirs))
507  input_list.extend(find_src_subdir(ctx.fwinfo.docdir))
508  image_path_list = []
509 
510  if ctx.dependency_diagram:
511  input_list.append(generate_dependencies_page(tmp_dir,
512  ctx.doxdatadir,
513  ctx.modulename,
514  ctx.dependency_diagram))
515  image_path_list.append(ctx.dependency_diagram)
516 
517  doxyfile_path = os.path.join(tmp_dir, 'Doxyfile')
518  with codecs.open(doxyfile_path, 'w', 'utf-8') as doxyfile:
519  # Global defaults
520  with codecs.open(os.path.join(ctx.doxdatadir, 'Doxyfile.global'),
521  'r', 'utf-8') as f:
522  for line in f:
523  doxyfile.write(line)
524 
525  writer = DoxyfileWriter(doxyfile)
526  writer.write_entry('PROJECT_NAME', ctx.fancyname)
527  # FIXME: can we get the project version from CMake? No from GIT TAGS!
528 
529  # Input locations
530  image_path_list.extend(find_src_subdir(ctx.fwinfo.docdir, 'pics'))
531  writer.write_entries(
532  INPUT=input_list,
533  DOTFILE_DIRS=find_src_subdir(ctx.fwinfo.docdir, 'dot'),
534  EXAMPLE_PATH=find_src_subdir(ctx.fwinfo.exampledirs),
535  IMAGE_PATH=image_path_list)
536 
537  # Other input settings
538  writer.write_entry('TAGFILES', [f + '=' + loc for f, loc in ctx.tagfiles])
539 
540  # Output locations
541  writer.write_entries(
542  OUTPUT_DIRECTORY=ctx.outputdir,
543  GENERATE_TAGFILE=ctx.tagfile,
544  HTML_OUTPUT=HTML_SUBDIR,
545  WARN_LOGFILE=os.path.join(ctx.outputdir, WARN_LOGFILE))
546 
547  # Other output settings
548  writer.write_entries(
549  HTML_HEADER=ctx.doxdatadir + '/header.html',
550  HTML_FOOTER=ctx.doxdatadir + '/footer.html'
551  )
552 
553  # Set a layout so that properties are first
554  writer.write_entries(
555  LAYOUT_FILE=ctx.doxdatadir + '/DoxygenLayout.xml'
556  )
557 
558  # Always write these, even if QHP is disabled, in case Doxygen.local
559  # overrides it
560  writer.write_entries(
561  QHP_VIRTUAL_FOLDER=ctx.modulename,
562  QHP_NAMESPACE="org.kde." + ctx.modulename,
563  QHG_LOCATION=ctx.qhelpgenerator)
564 
565  writer.write_entries(
566  GENERATE_MAN=ctx.man_pages,
567  GENERATE_QHP=ctx.qhp)
568 
569  if doxyfile_entries:
570  writer.write_entries(**doxyfile_entries)
571 
572  # Module-specific overrides
573  if find_src_subdir(ctx.fwinfo.docdir):
574  localdoxyfile = os.path.join(find_src_subdir(ctx.fwinfo.docdir)[0], 'Doxyfile.local')
575  if os.path.isfile(localdoxyfile):
576  with codecs.open(localdoxyfile, 'r', 'utf-8') as f:
577  for line in f:
578  doxyfile.write(line)
579 
580  logging.info('Running Doxygen')
581  subprocess.call([ctx.doxygen, doxyfile_path])
582 
583 
584 def postprocess(ctx, classmap, template_mapping=None):
585  mapping = {
586  'doxygencss': 'doxygen.css',
587  'resources': ctx.resourcedir,
588  'title': ctx.title,
589  'fwinfo': ctx.fwinfo,
590  'copyright': ctx.copyright,
591  'doxygen_menu': {'entries': menu_items(ctx.htmldir, ctx.modulename)},
592  'class_map': {'classes': classmap},
593  'kapidox_version': utils.get_kapidox_version(),
594  }
595  if template_mapping:
596  mapping.update(template_mapping)
597  logging.info('Postprocessing')
598 
599  tmpl = create_jinja_environment(ctx.doxdatadir).get_template('doxygen.html')
600  postprocess_internal(ctx.htmldir, tmpl, mapping)
601 
602 
603 def generate_diagram(png_path, fancyname, dot_files, tmp_dir):
604  """Generate a dependency diagram for a framework.
605  """
606  def run_cmd(cmd, **kwargs):
607  try:
608  subprocess.check_call(cmd, **kwargs)
609  except subprocess.CalledProcessError as exc:
610  logging.error('Command {exc.cmd} failed with error code {}.'
611  .format(exc.returncode))
612  return False
613  return True
614 
615  logging.info('Generating dependency diagram')
616  dot_path = os.path.join(tmp_dir, fancyname + '.dot')
617 
618  with open(dot_path, 'w') as f:
619  with_qt = False
620  ok = depdiagram.generate(f, dot_files, framework=fancyname,
621  with_qt=with_qt)
622  if not ok:
623  logging.error('Generating diagram failed')
624  return False
625 
626  logging.info('- Simplifying diagram')
627  simplified_dot_path = os.path.join(tmp_dir, fancyname + '-simplified.dot')
628  with open(simplified_dot_path, 'w') as f:
629  if not run_cmd(['tred', dot_path], stdout=f):
630  return False
631 
632  logging.info('- Generating diagram png')
633  if not run_cmd(['dot', '-Tpng', '-o' + png_path, simplified_dot_path]):
634  return False
635 
636  # These os.unlink() calls are not in a 'finally' block on purpose.
637  # Keeping the dot files around makes it possible to inspect their content
638  # when running with the --keep-temp-dirs option. If generation fails and
639  # --keep-temp-dirs is not set, the files will be removed when the program
640  # ends because they were created in `tmp_dir`.
641  os.unlink(dot_path)
642  os.unlink(simplified_dot_path)
643  return True
644 
645 
646 def create_fw_context(args, lib, tagfiles, copyright=''):
647 
648  # There is one more level for groups
649  if lib.part_of_group:
650  corrected_tagfiles = []
651  for k in range(len(tagfiles)):
652  # tagfiles are tupples like:
653  # ('/usr/share/doc/qt/KF5Completion.tags', '/usr/share/doc/qt')
654  # ('/where/the/tagfile/is/Name.tags', '/where/the/root/folder/is')
655  if tagfiles[k][1].startswith("http://") or tagfiles[k][1].startswith("https://"):
656  corrected_tagfiles.append(tagfiles[k])
657  else:
658  corrected_tagfiles.append((tagfiles[k][0], '../' + tagfiles[k][1]))
659  else:
660  corrected_tagfiles = tagfiles
661 
662  return Context(args,
663  # Names
664  modulename=lib.name,
665  fancyname=lib.fancyname,
666  fwinfo=lib,
667  # KApidox files
668  resourcedir=('../../../resources' if lib.part_of_group
669  else '../../resources'),
670  # Input
671  copyright=copyright,
672  tagfiles=corrected_tagfiles,
673  dependency_diagram=lib.dependency_diagram,
674  # Output
675  outputdir=lib.outputdir,
676  )
677 
678 
679 def gen_fw_apidocs(ctx, tmp_base_dir):
680  create_dirs(ctx)
681  # tmp_dir is deleted when tmp_base_dir is
682  tmp_dir = tempfile.mkdtemp(prefix=ctx.modulename + '-', dir=tmp_base_dir)
683  generate_apidocs(ctx, tmp_dir,
684  doxyfile_entries=dict(WARN_IF_UNDOCUMENTED=True)
685  )
686 
687 def create_fw_tagfile_tuple(lib):
688  tagfile = os.path.abspath(
689  os.path.join(
690  lib.outputdir,
691  'html',
692  lib.fancyname+'.tags'))
693  if lib.part_of_group:
694  prefix = '../../'
695  else:
696  prefix = '../../'
697  return (tagfile, prefix + lib.outputdir + '/html/')
698 
699 
700 def finish_fw_apidocs(ctx, group_menu):
701  classmap = build_classmap(ctx.tagfile)
702 
703  entries = [{
704  'href': '../../index.html',
705  'text': 'KDE API Reference'
706  }]
707  if ctx.fwinfo.part_of_group:
708  entries[0]['href'] = '../' + entries[0]['href']
709  entries.append({
710  'href': '../../index.html',
711  'text': ctx.fwinfo.product.fancyname
712  })
713  entries.append({
714  'href': 'index.html',
715  'text': ctx.fancyname
716  })
717 
718  template_mapping = {
719  'breadcrumbs': {
720  'entries': entries
721  },
722  }
723  copyright = '1996-' + str(datetime.date.today().year) + ' The KDE developers'
724  mapping = {
725  'qch': ctx.qhp,
726  'doxygencss': 'doxygen.css',
727  'resources': ctx.resourcedir,
728  'title': ctx.title,
729  'fwinfo': ctx.fwinfo,
730  'copyright': copyright,
731  'doxygen_menu': {'entries': menu_items(ctx.htmldir, ctx.modulename)},
732  'class_map': {'classes': classmap},
733  'kapidox_version': utils.get_kapidox_version(),
734  }
735  if template_mapping:
736  mapping.update(template_mapping)
737  logging.info('Postprocessing')
738 
739  tmpl = create_jinja_environment(ctx.doxdatadir).get_template('library.html')
740  postprocess_internal(ctx.htmldir, tmpl, mapping)
741 
742  tmpl2 = create_jinja_environment(ctx.doxdatadir).get_template('search.html')
743  search_output = ctx.fwinfo.outputdir + "/html/search.html"
744  with codecs.open(search_output, 'w', 'utf-8') as outf:
745  outf.write(tmpl2.render(mapping))
746 
747 
748 def indexer(lib):
749  """ Create json index from xml
750  <add>
751  <doc>
752  <field name="type">source</field>
753  <field name="name">kcmodule.cpp</field>
754  <field name="url">kcmodule_8cpp_source.html#l00001</field>
755  <field name="keywords"></field>
756  <field name="text"></field>
757  </doc>
758  </add
759  """
760 
761  doclist = []
762  tree = xmlET.parse(lib.outputdir + '/searchdata.xml')
763  for doc_child in tree.getroot():
764  field = {}
765  for child in doc_child:
766  if child.attrib['name'] == "type":
767  if child.text == 'source':
768  field = None
769  break; # We go to next <doc>
770  field['type'] = child.text
771  elif child.attrib['name'] == "name":
772  field['name'] = child.text
773  elif child.attrib['name'] == "url":
774  field['url'] = child.text
775  elif child.attrib['name'] == "keywords":
776  field['keyword'] = child.text
777  elif child.attrib['name'] == "text":
778  field['text'] = "" if child.text is None else child.text
779  if field is not None:
780  doclist.append(field)
781 
782  indexdic = {
783  'name': lib.name,
784  'fancyname': lib.fancyname,
785  'docfields': doclist
786  }
787 
788  with open(lib.outputdir + '/html/searchdata.json', 'w') as f:
789  for chunk in json.JSONEncoder().iterencode(indexdic):
790  f.write(chunk)
791 
792 def create_product_index(product):
793  doclist = []
794  for lib in product.libraries:
795  with open(lib.outputdir+'/html/searchdata.json', 'r') as f:
796  libindex = json.load(f)
797  for item in libindex['docfields']:
798  if lib.part_of_group:
799  item['url'] = lib.name + '/html/' + item['url']
800  else:
801  item['url'] = 'html/' + item['url']
802  doclist.append(libindex)
803 
804  indexdic = {
805  'name': product.name,
806  'fancyname': product.fancyname,
807  'libraries': doclist
808  }
809 
810  with open(product.outputdir + '/searchdata.json', 'w') as f:
811  for chunk in json.JSONEncoder().iterencode(indexdic):
812  f.write(chunk)
813 
814 def create_global_index(products):
815  doclist = []
816  for product in products:
817  with open(product.outputdir+'/searchdata.json', 'r') as f:
818  prodindex = json.load(f)
819  for proditem in prodindex['libraries']:
820  for item in proditem['docfields']:
821  item['url'] = product.name + '/' + item['url']
822  doclist.append(prodindex)
823 
824  indexdic = {
825  'all': doclist
826  }
827  with open('searchdata.json', 'w') as f:
828  for chunk in json.JSONEncoder().iterencode(indexdic):
829  f.write(chunk)
830 
831 def create_qch(products, tagfiles):
832  tag_root = "QtHelpProject"
833  tag_files = "files"
834  tag_filterSection = "filterSection"
835  tag_keywords = "keywords"
836  tag_toc = "toc"
837  for product in products:
838  tree_out = ET.ElementTree(ET.Element(tag_root))
839  root_out = tree_out.getroot()
840  root_out.set("version", "1.0")
841  namespace = ET.SubElement(root_out, "namespace")
842  namespace.text = "org.kde." + product.name
843  virtualFolder = ET.SubElement(root_out, "virtualFolder")
844  virtualFolder.text = product.name
845  filterSection = ET.SubElement(root_out, tag_filterSection)
846  filterAttribute = ET.SubElement(filterSection, "filterAttribute")
847  filterAttribute.text = "doxygen"
848  toc = ET.SubElement(filterSection, "toc")
849  keywords = ET.SubElement(filterSection, tag_keywords)
850  if len(product.libraries) > 0:
851  if product.libraries[0].part_of_group:
852  product_indexSection = ET.SubElement(toc, "section", {'ref': product.name + "/index.html", 'title': product.fancyname})
853  files = ET.SubElement(filterSection, tag_files)
854 
855  for lib in sorted(product.libraries, key=lambda lib: lib.name):
856  tree = ET.parse(lib.outputdir + '/html/index.qhp')
857  root = tree.getroot()
858  for child in root.findall(".//*[@ref]"):
859  if lib.part_of_group:
860  child.attrib['ref'] = lib.name + "/html/" + child.attrib['ref']
861  else:
862  child.attrib['ref'] = "html/" + child.attrib['ref']
863  child.attrib['ref'] = product.name + '/' +child.attrib['ref']
864 
865  for child in root.find(".//"+tag_toc):
866  if lib.part_of_group:
867  product_indexSection.append(child)
868  else:
869  toc.append(child)
870 
871  for child in root.find(".//keywords"):
872  keywords.append(child)
873 
874  resources = [
875  "*.json",
876  product.name + "/*.json",
877  "resources/css/*.css",
878  "resources/3rd-party/bootstrap/css/*.css",
879  "resources/3rd-party/jquery/jquery-3.1.0.min.js",
880  "resources/*.svg",
881  "resources/js/*.js",
882  "resources/icons/*",
883  ]
884  if product.part_of_group:
885  resources.extend([
886  product.name + "/*.html",
887  product.name + "/" + lib.name +"/html/*.html",
888  product.name + "/" + lib.name +"/html/*.png",
889  product.name + "/" + lib.name +"/html/*.css",
890  product.name + "/" + lib.name +"/html/*.js",
891  product.name + "/" + lib.name +"/html/*.json"
892  ])
893 
894  else:
895  resources.extend([
896  product.name + "/html/*.html",
897  product.name + "/html/*.png",
898  product.name + "/html/*.css",
899  product.name + "/html/*.js"
900  ])
901 
902  for resource in resources:
903  file_elem = ET.SubElement(files, "file")
904  file_elem.text = resource
905 
906  if not os.path.isdir('qch'):
907  os.mkdir('qch')
908 
909  name = product.name+".qhp"
910  outname = product.name+".qch"
911  tree_out.write(name, encoding="utf-8", xml_declaration=True)
912 
913  # On many distributions, qhelpgenerator from Qt5 is suffixed with
914  # "-qt5". Look for it first, and fall back to unsuffixed one if
915  # not found.
916  qhelpgenerator = find_executable("qhelpgenerator-qt5")
917 
918  if qhelpgenerator is None:
919  qhelpgenerator = "qhelpgenerator"
920 
921  subprocess.call([qhelpgenerator, name, '-o', 'qch/'+outname])
922  os.remove(name)
def search_for_tagfiles(suggestion=None, doclink=None, flattenlinks=False, searchpaths=[], exclude=None)
Find Doxygen-generated tag files.
Definition: generator.py:288
def generate_apidocs(ctx, tmp_dir, doxyfile_entries=None, keep_temp_dirs=False)
Generate the API documentation for a single directory.
Definition: generator.py:486
def generate_dependencies_page(tmp_dir, doxdatadir, modulename, dependency_diagram)
Create modulename-dependencies.md in tmp_dir
Definition: generator.py:472
def generate_diagram(png_path, fancyname, dot_files, tmp_dir)
Generate a dependency diagram for a framework.
Definition: generator.py:605
def build_classmap(tagfile)
Parses a tagfile to get a map from classes to files.
Definition: generator.py:456
def indexer(lib)
Create json index from xml <add> <doc> <field name="type">source</field> <field name="name">kcmodule...
Definition: generator.py:759
def postprocess_internal(htmldir, tmpl, mapping)
Substitute text in HTML files.
Definition: generator.py:418
def find_tagfiles(docdir, doclink=None, flattenlinks=False, exclude=None, _depth=0)
Find Doxygen-generated tag files in a directory.
Definition: generator.py:221
Holds parameters used by the various functions of the generator.
Definition: generator.py:61
def menu_items(htmldir, modulename)
Menu items for standard Doxygen files.
Definition: generator.py:322
def parse_dox_html(stream)
Parse the HTML files produced by Doxygen, extract the key/value block we add through header...
Definition: generator.py:375
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Nov 27 2020 22:55:34 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.