KApiDox

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

KDE's Doxygen guidelines are available online.