1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# Copyright (c) 2003 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
5#
6
7"""
8eric API Generator.
9
10This is the main Python script of the API generator. It is
11this script that gets called via the API generation interface.
12This script can be used via the commandline as well.
13"""
14
15import os
16import sys
17import glob
18import fnmatch
19
20sys.path.insert(1, os.path.dirname(__file__))
21
22import Utilities.ModuleParser
23from DocumentationTools.APIGenerator import APIGenerator
24from UI.Info import Version
25import Utilities
26import DocumentationTools
27
28
29def usage():
30    """
31    Function to print some usage information.
32
33    It prints a reference of all commandline parameters that may
34    be used and ends the application.
35    """
36    print("eric6_api")
37    print()
38    print("Copyright (c) 2004 - 2021 Detlev Offenbach"
39          " <detlev@die-offenbachs.de>.")
40    print()
41    print("Usage:")
42    print()
43    print("  eric6_api [options] files...")
44    print()
45    print("where files can be either python modules, package")
46    print("directories or ordinary directories.")
47    print()
48    print("Options:")
49    print()
50    print("  -b name or --base=name")
51    print("        Use the given name as the name of the base package.")
52    print("  -e eol-type or --eol=eol-type")
53    print("        Use the given eol type to terminate lines.")
54    print("        Valid values are 'cr', 'lf' and 'crlf'.")
55    print("  --exclude-file=pattern")
56    print("        Specify a filename pattern of files to be excluded.")
57    print("        This option may be repeated multiple times.")
58    print("  -h or --help")
59    print("        Show this help and exit.")
60    print("  -i or --ignore")
61    print("        Ignore the set of builtin modules")
62    print("  -l language or --language=language")
63    print("        Generate an API file for the given programming language.")
64    print("        Supported programming languages are:")
65    for lang in sorted(
66            DocumentationTools.supportedExtensionsDictForApis.keys()):
67        print("            * {0}".format(lang))
68    print("        The default is 'Python3'.")
69    print("        This option may be repeated multiple times.")
70    print("  -o filename or --output=filename")
71    print("        Write the API information to the named file."
72          " A '%L' placeholder")        # __IGNORE_WARNING_M601__
73    print("        is replaced by the language of the API file"
74          " (see --language).")
75    print("  -p or --private")
76    print("        Include private methods and functions.")
77    print("  -R, -r or --recursive")
78    print("        Perform a recursive search for source files.")
79    print("  -t ext or --extension=ext")
80    print("        Add the given extension to the list of file extensions.")
81    print("        This option may be given multiple times.")
82    print("  -V or --version")
83    print("        Show version information and exit.")
84    print("  -x directory or --exclude=directory")
85    print("        Specify a directory basename to be excluded.")
86    print("        This option may be repeated multiple times.")
87    sys.exit(1)
88
89
90def version():
91    """
92    Function to show the version information.
93    """
94    print(
95        """eric6_api  {0}\n"""
96        """\n"""
97        """eric API generator.\n"""
98        """\n"""
99        """Copyright (c) 2004 - 2021 Detlev Offenbach"""
100        """ <detlev@die-offenbachs.de>\n"""
101        """This is free software; see the LICENSE.GPL3 for copying"""
102        """ conditions.\n"""
103        """There is NO warranty; not even for MERCHANTABILITY or FITNESS"""
104        """ FOR A\n"""
105        """PARTICULAR PURPOSE.""".format(Version))
106    sys.exit(1)
107
108
109def main():
110    """
111    Main entry point into the application.
112    """
113    global supportedExtensions
114
115    import getopt
116
117    try:
118        opts, args = getopt.getopt(
119            sys.argv[1:], "b:e:hil:o:pRrt:Vx:",
120            ["base=", "eol=", "exclude=", "exclude-file=", "extension=",
121             "help", "ignore", "language=", "output=", "private", "recursive",
122             "version", ])
123    except getopt.error:
124        usage()
125
126    excludeDirs = [".svn", ".hg", ".git", ".ropeproject", ".eric6project",
127                   "dist", "build", "doc", "docs"]
128    excludePatterns = []
129    outputFileName = ""
130    recursive = False
131    basePackage = ""
132    includePrivate = False
133    progLanguages = []
134    extensions = []
135    newline = None
136    ignoreBuiltinModules = False
137
138    for k, v in opts:
139        if k in ["-o", "--output"]:
140            outputFileName = v
141        elif k in ["-R", "-r", "--recursive"]:
142            recursive = True
143        elif k in ["-x", "--exclude"]:
144            excludeDirs.append(v)
145        elif k == "--exclude-file":
146            excludePatterns.append(v)
147        elif k in ["-h", "--help"]:
148            usage()
149        elif k in ["-i", "--ignore"]:
150            ignoreBuiltinModules = True
151        elif k in ["-V", "--version"]:
152            version()
153        elif k in ["-t", "--extension"]:
154            if not v.startswith("."):
155                v = ".{0}".format(v)
156            extensions.append(v)
157        elif k in ["-b", "--base"]:
158            basePackage = v
159        elif k in ["-p", "--private"]:
160            includePrivate = True
161        elif k in ["-l", "--language"]:
162            if v not in progLanguages:
163                if v not in DocumentationTools.supportedExtensionsDictForApis:
164                    sys.stderr.write(
165                        "Wrong language given: {0}. Aborting\n".format(v))
166                    sys.exit(1)
167                else:
168                    progLanguages.append(v)
169        elif k in ["-e", "--eol"]:
170            if v.lower() == "cr":
171                newline = '\r'
172            elif v.lower() == "lf":
173                newline = '\n'
174            elif v.lower() == "crlf":
175                newline = '\r\n'
176
177    if not args:
178        usage()
179
180    if outputFileName == "":
181        sys.stderr.write("No output file given. Aborting\n")
182        sys.exit(1)
183
184    if len(progLanguages) == 0:
185        progLanguages = ["Python3"]
186
187    for progLanguage in sorted(progLanguages):
188        basename = ""
189        apis = []
190        basesDict = {}
191
192        supportedExtensions = (
193            DocumentationTools.supportedExtensionsDictForApis[progLanguage]
194        )
195        supportedExtensions.extend(extensions)
196        if "%L" in outputFileName:
197            outputFile = outputFileName.replace("%L", progLanguage)
198        else:
199            if len(progLanguages) == 1:
200                outputFile = outputFileName
201            else:
202                root, ext = os.path.splitext(outputFileName)
203                outputFile = "{0}-{1}{2}".format(root, progLanguage.lower(),
204                                                 ext)
205        basesFile = os.path.splitext(outputFile)[0] + '.bas'
206
207        for arg in args:
208            if os.path.isdir(arg):
209                if os.path.exists(os.path.join(
210                        arg, Utilities.joinext("__init__", ".py"))):
211                    basename = os.path.dirname(arg)
212                    if arg == '.':
213                        sys.stderr.write("The directory '.' is a package.\n")
214                        sys.stderr.write(
215                            "Please repeat the call giving its real name.\n")
216                        sys.stderr.write("Ignoring the directory.\n")
217                        continue
218                else:
219                    basename = arg
220                if basename:
221                    basename = "{0}{1}".format(basename, os.sep)
222
223                if recursive and not os.path.islink(arg):
224                    names = [arg] + Utilities.getDirs(arg, excludeDirs)
225                else:
226                    names = [arg]
227            else:
228                basename = ""
229                names = [arg]
230
231            for filename in sorted(names):
232                inpackage = False
233                if os.path.isdir(filename):
234                    files = []
235                    for ext in supportedExtensions:
236                        files.extend(glob.glob(os.path.join(
237                            filename, Utilities.joinext("*", ext))))
238                        initFile = os.path.join(
239                            filename, Utilities.joinext("__init__", ext))
240                        if initFile in files:
241                            inpackage = True
242                            files.remove(initFile)
243                            files.insert(0, initFile)
244                        elif progLanguage != "Python3":
245                            # assume package
246                            inpackage = True
247                else:
248                    if (
249                        Utilities.isWindowsPlatform() and
250                        glob.has_magic(filename)
251                    ):
252                        files = glob.glob(filename)
253                    else:
254                        files = [filename]
255
256                for file in files:
257                    skipIt = False
258                    for pattern in excludePatterns:
259                        if fnmatch.fnmatch(os.path.basename(file), pattern):
260                            skipIt = True
261                            break
262                    if skipIt:
263                        continue
264
265                    try:
266                        module = Utilities.ModuleParser.readModule(
267                            file,
268                            basename=basename, inpackage=inpackage,
269                            ignoreBuiltinModules=ignoreBuiltinModules)
270                        apiGenerator = APIGenerator(module)
271                        api = apiGenerator.genAPI(True, basePackage,
272                                                  includePrivate)
273                        bases = apiGenerator.genBases(includePrivate)
274                    except OSError as v:
275                        sys.stderr.write("{0} error: {1}\n".format(file, v[1]))
276                        continue
277                    except ImportError as v:
278                        sys.stderr.write("{0} error: {1}\n".format(file, v))
279                        continue
280
281                    for apiEntry in api:
282                        if apiEntry not in apis:
283                            apis.append(apiEntry)
284                    for basesEntry in bases:
285                        if bases[basesEntry]:
286                            basesDict[basesEntry] = bases[basesEntry][:]
287                    sys.stdout.write("-- {0} -- {1} ok\n".format(
288                        progLanguage, file))
289
290        outdir = os.path.dirname(outputFile)
291        if outdir and not os.path.exists(outdir):
292            os.makedirs(outdir)
293        try:
294            with open(outputFile, "w", encoding="utf-8",
295                      newline=newline) as out:
296                out.write("\n".join(sorted(apis)) + "\n")
297        except OSError as v:
298            sys.stderr.write("{0} error: {1}\n".format(outputFile, v[1]))
299            sys.exit(3)
300        try:
301            with open(basesFile, "w", encoding="utf-8",
302                      newline=newline) as out:
303                for baseEntry in sorted(basesDict.keys()):
304                    out.write("{0} {1}\n".format(
305                        baseEntry, " ".join(sorted(basesDict[baseEntry]))))
306        except OSError as v:
307            sys.stderr.write("{0} error: {1}\n".format(basesFile, v[1]))
308            sys.exit(3)
309
310    sys.stdout.write('\nDone.\n')
311    sys.exit(0)
312
313if __name__ == '__main__':
314    main()
315
316#
317# eflag: noqa = M801
318