src/eric7/eric7_doc.py

Sat, 26 Apr 2025 12:34:32 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 26 Apr 2025 12:34:32 +0200
branch
eric7
changeset 11240
c48c615c04a3
parent 11148
15e30f0c76a8
permissions
-rw-r--r--

MicroPython
- Added a configuration option to disable the support for the no longer produced Pimoroni Pico Wireless Pack.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright (c) 2003 - 2025 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
eric Documentation Generator.

This is the main Python script of the documentation generator. It is
this script that gets called via the source documentation interface.
This script can be used via the commandline as well.
"""

import argparse
import fnmatch
import glob
import os
import shutil
import sys

from eric7.__version__ import Version
from eric7.DocumentationTools import TemplatesListsStyleCSS
from eric7.DocumentationTools.Config import eric7docDefaultColors
from eric7.DocumentationTools.IndexGenerator import IndexGenerator
from eric7.DocumentationTools.ModuleDocumentor import ModuleDocument
from eric7.DocumentationTools.QtHelpGenerator import QtHelpGenerator
from eric7.SystemUtilities import FileSystemUtilities, OSUtilities
from eric7.Utilities import ModuleParser

# list of supported filename extensions
supportedExtensions = [".py", ".pyw", ".ptl", ".rb"]


def createArgumentParser():
    """
    Function to create an argument parser.

    @return created argument parser object
    @rtype argparse.ArgumentParser
    """
    parser = argparse.ArgumentParser(
        description="Create source code documentation files.  It is part of the eric"
        " tool suite.",
        epilog="Copyright (c) 2003 - 2025 Detlev Offenbach <detlev@die-offenbachs.de>.",
    )

    parser.add_argument(
        "file",
        nargs="+",
        help="'file' can be either python modules, package directories or ordinary"
        " directories. At least one 'file' argument must be given.",
    )
    parser.add_argument(
        "-c",
        "--style-sheet",
        default="",
        help="Specify a CSS style sheet file to be used.",
    )
    parser.add_argument(
        "-e",
        "--no-empty",
        action="store_true",
        help="Don't include empty modules.",
    )
    parser.add_argument(
        "--eol",
        choices=["cr", "lf", "crlf"],
        help="Use the given eol type to terminate lines.",
    )
    parser.add_argument(
        "--exclude-file",
        action="append",
        default=[],
        help="Specify a filename pattern of files to be excluded. This option may be"
        " repeated multiple times.",
    )
    parser.add_argument(
        "-i",
        "--no-index",
        action="store_true",
        help="Don't generate index files.",
    )
    parser.add_argument(
        "-o",
        "--outdir",
        default="doc",
        help="Generate files in the named directory.",
    )
    parser.add_argument(
        "-R",
        "-r",
        "--recursive",
        action="store_true",
        help="Perform a recursive search for source files.",
    )
    parser.add_argument(
        "-s",
        "--startdir",
        default="",
        help="Start the documentation generation in the given directory.",
    )
    parser.add_argument(
        "-t",
        "--extension",
        action="append",
        default=[],
        help="Add the given extension to the list of file extensions. This option may"
        " be given multiple times.",
    )
    parser.add_argument(
        "-V",
        "--version",
        action="version",
        version="%(prog)s {0}".format(Version),
        help="Show version information and exit.",
    )
    parser.add_argument(
        "-x",
        "--exclude",
        action="append",
        default=[],
        help="Specify a directory basename to be excluded. This option may be repeated"
        " multiple times.",
    )

    colorGroup = parser.add_argument_group(
        "Stylesheet Colors", "Parameters to define individual stylesheet colors."
    )
    colorGroup.add_argument(
        "--body-color",
        default=eric7docDefaultColors["BodyColor"],
        help="Specify the text color.",
    )
    colorGroup.add_argument(
        "--body-background-color",
        default=eric7docDefaultColors["BodyBgColor"],
        help="Specify the text background color.",
    )
    colorGroup.add_argument(
        "--l1header-color",
        default=eric7docDefaultColors["Level1HeaderColor"],
        help="Specify the text color of level 1 headers.",
    )
    colorGroup.add_argument(
        "--l1header-background-color",
        default=eric7docDefaultColors["Level1HeaderBgColor"],
        help="Specify the text background color of level 1 headers.",
    )
    colorGroup.add_argument(
        "--l2header-color",
        default=eric7docDefaultColors["Level2HeaderColor"],
        help="Specify the text color of level 2 headers.",
    )
    colorGroup.add_argument(
        "--l2header-background-color",
        default=eric7docDefaultColors["Level2HeaderBgColor"],
        help="Specify the text background color of level 2 headers.",
    )
    colorGroup.add_argument(
        "--cfheader-color",
        default=eric7docDefaultColors["CFColor"],
        help="Specify the text color of class and function headers.",
    )
    colorGroup.add_argument(
        "--cfheader-background-color",
        default=eric7docDefaultColors["CFBgColor"],
        help="Specify the text background color of class and function headers.",
    )
    colorGroup.add_argument(
        "--link-color",
        default=eric7docDefaultColors["LinkColor"],
        help="Specify the text color of hyperlinks.",
    )

    qtGroup = parser.add_argument_group(
        "QtHelp", "Parameters for QtHelp file creation."
    )
    qtGroup.add_argument(
        "--create-qhp",
        action="store_true",
        help="Enable generation of QtHelp files.",
    )
    qtGroup.add_argument(
        "--qhp-outdir",
        default="help",
        help="Store the QtHelp files in the named directory.",
    )
    qtGroup.add_argument(
        "--qhp-namespace",
        default="",
        help="Use the given namespace (required).",
    )
    qtGroup.add_argument(
        "--qhp-virtualfolder",
        default="source",
        help="Use the given virtual folder (mandatory). The virtual folder must not"
        " contain '/'.",
    )
    qtGroup.add_argument(
        "--qhp-filtername",
        default="unknown",
        help="Use the given name for the custom filter.",
    )
    qtGroup.add_argument(
        "--qhp-filterattribs",
        default="",
        help="Add the given attributes to the filter list. Attributes must be"
        " separated by ':'.",
    )
    qtGroup.add_argument(
        "--qhp-title",
        default="",
        help="Use this as the title for the generated help (mandatory).",
    )
    qtGroup.add_argument(
        "--create-qhc",
        action="store_true",
        help="Enable generation of QtHelp Collection files.",
    )

    return parser


def main():
    """
    Main entry point into the application.
    """
    parser = createArgumentParser()
    args = parser.parse_args()

    excludeDirs = [
        ".svn",
        ".hg",
        ".git",
        ".ropeproject",
        ".eric7project",
        ".jedi",
        "dist",
        "build",
        "doc",
        "docs",
        "__pycache__",
    ] + args.exclude
    excludePatterns = args.exclude_file
    startDir = args.startdir
    outputDir = args.outdir
    recursive = args.recursive
    doIndex = not args.no_index
    noempty = args.no_empty
    newline = {
        "cr": "\r",
        "lf": "\n",
        "crlf": "\r\n",
    }.get(args.eol)

    stylesheetFile = args.style_sheet
    colors = eric7docDefaultColors.copy()
    colors = {
        "BodyColor": args.body_color,
        "BodyBgColor": args.body_background_color,
        "Level1HeaderColor": args.l1header_color,
        "Level1HeaderBgColor": args.l1header_background_color,
        "Level2HeaderColor": args.l2header_color,
        "Level2HeaderBgColor": args.l2header_background_color,
        "CFColor": args.cfheader_color,
        "CFBgColor": args.cfheader_background_color,
        "LinkColor": args.link_color,
    }

    qtHelpCreation = args.create_qhp
    qtHelpOutputDir = args.qhp_outdir
    qtHelpNamespace = args.qhp_namespace
    qtHelpFolder = args.qhp_virtualfolder
    qtHelpFilterName = args.qhp_filtername
    qtHelpFilterAttribs = args.qhp_filterattribs
    qtHelpTitle = args.qhp_title
    qtHelpCreateCollection = args.create_qhc

    if qtHelpCreation and (
        qtHelpNamespace == ""
        or qtHelpFolder == ""
        or "/" in qtHelpFolder
        or qtHelpTitle == ""
    ):
        parser.error("Some required QtHelp arguments are missing.")

    basename = ""

    if outputDir:
        if not os.path.isdir(outputDir):
            try:
                os.makedirs(outputDir)
            except OSError:
                sys.stderr.write(
                    "Could not create output directory {0}.".format(outputDir)
                )
                sys.exit(3)
    else:
        outputDir = os.getcwd()
    outputDir = os.path.abspath(outputDir)

    if stylesheetFile:
        try:
            shutil.copy(stylesheetFile, os.path.join(outputDir, "styles.css"))
        except OSError:
            sys.stderr.write(
                "The CSS stylesheet '{0}' does not exist\n".format(stylesheetFile)
            )
            sys.exit(3)
    else:
        try:
            with open(os.path.join(outputDir, "styles.css"), "w") as sf:
                sf.write(TemplatesListsStyleCSS.cssTemplate.format(**colors))
        except OSError:
            sys.stderr.write(
                "The CSS stylesheet '{0}' could not be created\n".format(stylesheetFile)
            )
            sys.exit(3)

    indexGenerator = IndexGenerator(outputDir)

    if qtHelpCreation:
        if qtHelpOutputDir:
            if not os.path.isdir(qtHelpOutputDir):
                try:
                    os.makedirs(qtHelpOutputDir)
                except OSError:
                    sys.stderr.write(
                        "Could not create QtHelp output directory {0}.".format(
                            qtHelpOutputDir
                        )
                    )
                    sys.exit(3)
        else:
            qtHelpOutputDir = os.getcwd()
        qtHelpOutputDir = os.path.abspath(qtHelpOutputDir)

        qtHelpGenerator = QtHelpGenerator(
            outputDir,
            qtHelpOutputDir,
            qtHelpNamespace,
            qtHelpFolder,
            qtHelpFilterName,
            qtHelpFilterAttribs,
            qtHelpTitle,
            qtHelpCreateCollection,
        )

    if startDir:
        os.chdir(os.path.abspath(startDir))

    for argsfile in args.file:
        if os.path.isdir(argsfile):
            if os.path.exists(
                os.path.join(argsfile, FileSystemUtilities.joinext("__init__", ".py"))
            ):
                basename = os.path.dirname(argsfile)
                if argsfile == ".":
                    sys.stderr.write("The directory '.' is a package.\n")
                    sys.stderr.write("Please repeat the call giving its real name.\n")
                    sys.stderr.write("Ignoring the directory.\n")
                    continue
            else:
                basename = argsfile
            if basename:
                basename = "{0}{1}".format(basename, os.sep)

            if recursive and not os.path.islink(argsfile):
                names = [argsfile] + FileSystemUtilities.getDirs(argsfile, excludeDirs)
            else:
                names = [argsfile]
        else:
            basename = ""
            names = [argsfile]

        for filename in names:
            inpackage = False
            if os.path.isdir(filename):
                files = []
                for ext in supportedExtensions:
                    files.extend(
                        glob.glob(
                            os.path.join(
                                filename, FileSystemUtilities.joinext("*", ext)
                            )
                        )
                    )
                    initFile = os.path.join(
                        filename, FileSystemUtilities.joinext("__init__", ext)
                    )
                    if initFile in files:
                        inpackage = True
                        files.remove(initFile)
                        files.insert(0, initFile)
            else:
                if OSUtilities.isWindowsPlatform() and glob.has_magic(filename):
                    files = glob.glob(filename)
                else:
                    files = [filename]

            for file in files:
                skipIt = False
                for pattern in excludePatterns:
                    if fnmatch.fnmatch(os.path.basename(file), pattern):
                        skipIt = True
                        break
                if skipIt:
                    continue

                try:
                    print("Processing", file)
                    module = ModuleParser.readModule(
                        file,
                        basename=basename,
                        inpackage=inpackage,
                        extensions=supportedExtensions,
                    )
                    moduleDocument = ModuleDocument(module)
                    doc = moduleDocument.genDocument()
                except OSError as v:
                    sys.stderr.write("{0} error: {1}\n".format(file, v[1]))
                    continue
                except ImportError as v:
                    sys.stderr.write("{0} error: {1}\n".format(file, v))
                    continue
                except Exception as ex:
                    sys.stderr.write(
                        "{0} error while parsing: {1}\n".format(file, str(ex))
                    )
                    raise

                f = FileSystemUtilities.joinext(
                    os.path.join(outputDir, moduleDocument.name()), ".html"
                )

                # remember for index file generation
                indexGenerator.remember(file, moduleDocument, basename)

                # remember for QtHelp generation
                if qtHelpCreation:
                    qtHelpGenerator.remember(file, moduleDocument, basename)

                if (
                    noempty or file.endswith("__init__.py")
                ) and moduleDocument.isEmpty():
                    continue

                # generate output
                try:
                    with open(f, "w", encoding="utf-8", newline=newline) as out:
                        out.write(doc)
                except OSError as v:
                    sys.stderr.write("{0} error: {1}\n".format(file, v[1]))
                except Exception as ex:
                    sys.stderr.write(
                        "{0} error while writing: {1}\n".format(file, str(ex))
                    )
                    raise
                else:
                    sys.stdout.write("{0} ok\n".format(f))

                sys.stdout.flush()
                sys.stderr.flush()

    sys.stdout.write("code documentation generated")

    sys.stdout.flush()
    sys.stderr.flush()

    # write index files
    if doIndex:
        indexGenerator.writeIndices(basename, newline=newline)

    # generate the QtHelp files
    if qtHelpCreation:
        qtHelpGenerator.generateFiles(newline=newline)

    sys.exit(0)


if __name__ == "__main__":
    main()

#
# eflag: noqa = M-801

eric ide

mercurial