Source code for linuxdoc.autodoc
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
autodoc
~~~~~~~
Implementation of the ``linuxdoc.autodoc`` command.
:copyright: Copyright (C) 2023 Markus Heiser
:license: AGPL-3.0-or-later; see LICENSE for details.
The command ``linuxdoc.autodoc`` extracts the kernel-doc comments from the
source code and uses them to create documentation of the source code in the reST
markup::
$ linuxdoc.autodoc --help
"""
import sys
import argparse
import multiprocessing
import six
from fspath import FSPath
from . import kernel_doc as kerneldoc
from .kernel_doc import Container
CMD = None
MSG = lambda msg: sys.__stderr__.write("INFO : %s\n" % msg)
ERR = lambda msg: sys.__stderr__.write("ERROR: %s\n" % msg)
FATAL = lambda msg: sys.__stderr__.write("FATAL: %s\n" % msg)
TEMPLATE_INDEX="""\
.. -*- coding: utf-8; mode: rst -*-
================================================================================
%(title)s
================================================================================
.. toctree::
:maxdepth: 1
"""
EPILOG = """This command uses the kernel-doc parser from the linuxdoc Sphinx
extension, for details see: https://return42.github.io/linuxdoc/cmd-line.html"""
DESCRIPTION = """The linuxdoc.autodoc tool can be used to generate documentation
in the reST markup from the kernel-doc markup comments in the source files.
This tool can be used to create an analogous document structure in reST markup
from the folder structure of the source code. """
# ------------------------------------------------------------------------------
[docs]
def main():
# ------------------------------------------------------------------------------
global CMD # pylint: disable=global-statement
cli = get_cli()
CMD = cli.parse_args()
if not CMD.srctree.EXISTS:
ERR("%s does not exists." % CMD.srctree)
sys.exit(42)
if not CMD.srctree.ISDIR:
ERR("%s is not a folder." % CMD.srctree)
sys.exit(42)
if not CMD.force and CMD.doctree.EXISTS:
ERR("%s is in the way, remove it first" % CMD.doctree)
sys.exit(42)
if CMD.markup == "kernel-doc" and CMD.rst_files:
CMD.rst_files = CMD.rst_files.readFile().splitlines()
else:
CMD.rst_files = []
if CMD.threads > 1:
# pylint: disable=consider-using-with
pool = multiprocessing.Pool(CMD.threads)
pool.map(autodoc_file, gather_filenames(CMD))
pool.close()
pool.join()
else:
for fname in gather_filenames(CMD):
autodoc_file(fname)
insert_index_files(CMD.doctree)
[docs]
def get_cli():
cli = argparse.ArgumentParser(
description = DESCRIPTION
, epilog = EPILOG
, formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
cli.add_argument(
"srctree"
, help = "Folder of source code."
, type = lambda x: FSPath(x).ABSPATH
)
cli.add_argument(
"doctree"
, help = "Folder to place reST documentation."
, type = lambda x: FSPath(x).ABSPATH
)
cli.add_argument(
"--sloppy"
, action = "store_true"
, help = "Sloppy comment check, reports only severe errors."
)
cli.add_argument(
"--force"
, action = "store_true"
, help = "Don't stop if doctree exists."
)
cli.add_argument(
"--threads"
, type = int
, default = multiprocessing.cpu_count()
, help = "Use up to n threads."
)
cli.add_argument(
"--markup"
, choices = ["reST", "kernel-doc"]
, default = "reST"
, help = (
"Markup of the comments. Change this option only if you know what"
" you do and make use of --rst-files if you also have some files in"
" your source tree with reST markup. The markup of new comments must"
" be reST!"
)
)
cli.add_argument(
"--rst-files"
, type = lambda x: FSPath(x).ABSPATH
, help = (
"File that list source files, which has comments in reST markup."
" Use linuxdoc.grepdoc command to generate those file."
)
)
return cli
# ------------------------------------------------------------------------------
[docs]
def gather_filenames(cmd):
# ------------------------------------------------------------------------------
"yield .c & .h filenames"
for fname in cmd.srctree.reMatchFind(r"^.*\.[ch]$"):
yield fname
# ------------------------------------------------------------------------------
[docs]
def autodoc_file(fname):
# ------------------------------------------------------------------------------
"generate documentation from fname"
fname = fname.relpath(CMD.srctree)
markup = CMD.markup
if CMD.markup == "kernel-doc" and fname in CMD.rst_files:
markup = "reST"
opts = kerneldoc.ParseOptions(
fname = fname
, src_tree = CMD.srctree
, verbose_warn = not (CMD.sloppy)
, use_all_docs = True
, markup = markup )
parser = kerneldoc.Parser(opts, kerneldoc.NullTranslator())
try:
parser.parse()
except Exception: # pylint: disable=broad-except
FATAL("kernel-doc markup of %s seems buggy / can't parse" % opts.fname)
return
if not parser.ctx.dump_storage:
# no kernel-doc comments found
MSG("parsed: NONE comments: %s" % opts.fname)
return
MSG("parsed: %4d comments: %s" % (len(parser.ctx.dump_storage), opts.fname))
try:
rst = six.StringIO()
translator = kerneldoc.ReSTTranslator()
opts.out = rst
# First try to output reST, this might fail, because the kernel-doc
# parser part is to tollerant ("bad lines", "function name and function
# declaration are different", etc ...).
parser.parse_dump_storage(translator=translator)
out_file = CMD.doctree / fname.replace(".","_") + ".rst"
out_file.DIRNAME.makedirs()
with out_file.openTextFile(mode="w") as out:
out.write(rst.getvalue())
except Exception: # pylint: disable=broad-except
FATAL("kernel-doc markup of %s seems buggy / can't parse" % opts.fname)
return
# ------------------------------------------------------------------------------
[docs]
def insert_index_files(root_folder):
# ------------------------------------------------------------------------------
"From root_folder traverse over subfolders and generate all index.rst files "
for folder, dirnames, filenames in root_folder.walk():
ctx = Container( title = folder.FILENAME )
dirnames.sort()
filenames.sort()
index_file = folder / "index.rst"
MSG("create index: %s" % index_file)
with index_file.openTextFile(mode="w") as index:
index.write(TEMPLATE_INDEX % ctx)
for _d in dirnames:
index.write(" %s/index\n" % _d.FILENAME)
for _f in filenames:
if _f.FILENAME == "index":
continue
index.write(" %s\n" % _f.FILENAME)