diff options
author | Patrick Lehmann <Patrick.Lehmann@plc2.de> | 2021-06-30 20:45:03 +0200 |
---|---|---|
committer | Patrick Lehmann <Patrick.Lehmann@plc2.de> | 2021-07-01 06:39:47 +0200 |
commit | c75693ff6f5c7b05d49ce916e34aaba61b688e31 (patch) | |
tree | ac5eca19c8fe114be81d3f2cb6e47c18a462865a /pyGHDL | |
parent | 301dea333ec3e28e95a43b1a4af569ebbedd6ab9 (diff) | |
download | ghdl-c75693ff6f5c7b05d49ce916e34aaba61b688e31.tar.gz ghdl-c75693ff6f5c7b05d49ce916e34aaba61b688e31.tar.bz2 ghdl-c75693ff6f5c7b05d49ce916e34aaba61b688e31.zip |
New command line interface for DOM.
Diffstat (limited to 'pyGHDL')
-rw-r--r-- | pyGHDL/__init__.py | 5 | ||||
-rwxr-xr-x | pyGHDL/cli/DOM.py | 339 | ||||
-rw-r--r-- | pyGHDL/requirements.txt | 3 |
3 files changed, 265 insertions, 82 deletions
diff --git a/pyGHDL/__init__.py b/pyGHDL/__init__.py index 0324bd15d..60572d05a 100644 --- a/pyGHDL/__init__.py +++ b/pyGHDL/__init__.py @@ -52,4 +52,7 @@ from pydecor import export @export class GHDLBaseException(Exception): - pass + + @property + def message(self) -> str: + return str(self) diff --git a/pyGHDL/cli/DOM.py b/pyGHDL/cli/DOM.py index 622a70d16..7fc659027 100755 --- a/pyGHDL/cli/DOM.py +++ b/pyGHDL/cli/DOM.py @@ -1,121 +1,298 @@ #!/usr/bin/env python3 - -from sys import argv -from sys import exit as sysexit - +# ============================================================================= +# ____ _ _ ____ _ _ +# _ __ _ _ / ___| | | | _ \| | __| | ___ _ __ ___ +# | '_ \| | | | | _| |_| | | | | | / _` |/ _ \| '_ ` _ \ +# | |_) | |_| | |_| | _ | |_| | |___ | (_| | (_) | | | | | | +# | .__/ \__, |\____|_| |_|____/|_____(_)__,_|\___/|_| |_| |_| +# |_| |___/ +# ============================================================================= +# Authors: +# Patrick Lehmann +# Unai Martinez-Corral +# +# Package module: DOM: Interface items (e.g. generic or port) +# +# License: +# ============================================================================ +# Copyright (C) 2019-2021 Tristan Gingold +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-2.0-or-later +# ============================================================================ +from argparse import RawDescriptionHelpFormatter from pathlib import Path -from textwrap import dedent +from platform import system as platform_system +from textwrap import wrap, dedent + +from pyGHDL.dom import DOMException +from pyGHDL.libghdl import LibGHDLException from pydecor import export +from pyMetaClasses import Singleton +from pyAttributes import Attribute +from pyAttributes.ArgParseAttributes import ArgParseMixin, CommonSwitchArgumentAttribute, DefaultAttribute, CommandAttribute, ArgumentAttribute, \ + SwitchArgumentAttribute +from pyTerminalUI import LineTerminal, Severity from pyGHDL import GHDLBaseException -from pyGHDL.libghdl import LibGHDLException -from pyGHDL.dom import DOMException from pyGHDL.dom.NonStandard import Design, Document from pyGHDL.dom.formatting.prettyprint import PrettyPrint, PrettyPrintException +__author__ = "Tristan Gingold" +__copyright__ = "Copyright (C) 2019-2021 Tristan Gingold" +__maintainer__ = "Tristan Gingold" +__email__ = "" +__version__ = "0.0.0" +__status__ = "Alpha" +__license__ = "" __all__ = [] __api__ = __all__ +class SourceAttribute(Attribute): + def __call__(self, func): + self._AppendAttribute(func, ArgumentAttribute("-f", "--file", action="append", metavar="file", dest="Files", type=Path, help="The filename to parse (can be used multiple times).")) + self._AppendAttribute(func, ArgumentAttribute("-F", "--files", metavar="files", dest="Files", type=Path, nargs="+", help="List of filenames to parse.")) + self._AppendAttribute(func, ArgumentAttribute("-D", "--directory", metavar="dir", dest="Directory", type=Path, help="The directory to parse.")) + return func + @export -class Application: +class Application(LineTerminal, ArgParseMixin): + HeadLine = "pyGHDL.dom - Test Application" + + # load platform information (Windows, Linux, Darwin, ...) + __PLATFORM = platform_system() + _design: Design - def __init__(self): + def __init__(self, debug=False, verbose=False, quiet=False, sphinx=False): + super().__init__(verbose, debug, quiet) + + # Initialize the Terminal class + # -------------------------------------------------------------------------- + Singleton.Register(LineTerminal, self) + + # Initialize DOM with an empty design + # -------------------------------------------------------------------------- self._design = Design() - def addFile(self, filename: Path, library: str): - lib = self._design.GetLibrary(library) + # Call the constructor of the ArgParseMixin + # -------------------------------------------------------------------------- + textWidth = min(self.Width, 160) + description = dedent("""\ + Application to test pyGHDL's DOM API. + """) + epilog = "\n".join(wrap(dedent("""\ + pyGHDL is a Python binding for libghdl. + """), textWidth, replace_whitespace=False)) - document = Document(filename) - self._design.AddDocument(document, lib) + class HelpFormatter(RawDescriptionHelpFormatter): + def __init__(self, *args, **kwargs): + kwargs['max_help_position'] = 30 + kwargs['width'] = textWidth + super().__init__(*args, **kwargs) + + ArgParseMixin.__init__( + self, + description=description, + epilog=epilog, + formatter_class=HelpFormatter, + add_help=False + ) + + # If executed in Sphinx to auto-document CLI arguments, exit now + # -------------------------------------------------------------------------- + if sphinx: + return + + # Change error and warning reporting + # -------------------------------------------------------------------------- + self._LOG_MESSAGE_FORMAT__[Severity.Fatal] = "{DARK_RED}[FATAL] {message}{NOCOLOR}" + self._LOG_MESSAGE_FORMAT__[Severity.Error] = "{RED}[ERROR] {message}{NOCOLOR}" + self._LOG_MESSAGE_FORMAT__[Severity.Warning] = "{YELLOW}[WARNING] {message}{NOCOLOR}" + self._LOG_MESSAGE_FORMAT__[Severity.Normal] = "{GRAY}{message}{NOCOLOR}" + + # class properties + # ============================================================================ + @property + def Platform(self): + return self.__PLATFORM + + def PrintHeadline(self): + self.WriteNormal(dedent("""\ + {HEADLINE}{line} + {headline: ^80s} + {line}""").format(line="=" * 80, headline=self.HeadLine, **LineTerminal.Foreground)) + + # ============================================================================ + # Common commands + # ============================================================================ + # common arguments valid for all commands + # ---------------------------------------------------------------------------- + @CommonSwitchArgumentAttribute("-d", "--debug", dest="debug", help="Enable debug mode.") + @CommonSwitchArgumentAttribute("-v", "--verbose", dest="verbose", help="Print out detailed messages.") + @CommonSwitchArgumentAttribute("-q", "--quiet", dest="quiet", help="Reduce messages to a minimum.") + def Run(self): + ArgParseMixin.Run(self) + + @DefaultAttribute() + def HandleDefault(self, _): + self.PrintHeadline() + self.MainParser.print_help() + + self.WriteNormal("") + self.exit() + + # ---------------------------------------------------------------------------- + # create the sub-parser for the "help" command + # ---------------------------------------------------------------------------- + @CommandAttribute("help", help="Display help page(s) for the given command name.") + @ArgumentAttribute(metavar="Command", dest="Command", type=str, nargs="?", help="Print help page(s) for a command.") + def HandleHelp(self, args): + self.PrintHeadline() + + if (args.Command is None): + self.MainParser.print_help() + elif (args.Command == "help"): + self.WriteError("This is a recursion ...") + else: + try: + self.SubParsers[args.Command].print_help() + except KeyError: + self.WriteError("Command {0} is unknown.".format(args.Command)) + + self.WriteNormal("") + self.exit() + + # ---------------------------------------------------------------------------- + # create the sub-parser for the "version" command + # ---------------------------------------------------------------------------- + @CommandAttribute("version", help="Display tool and version information.") + def HandleInfo(self, args): + self.PrintHeadline() + + copyrights = __copyright__.split("\n", 1) + self.WriteNormal("Copyright: {0}".format(copyrights[0])) + for copyright in copyrights[1:]: + self.WriteNormal(" {0}".format(copyright)) + self.WriteNormal("License: {0}".format(__license__)) + authors = __author__.split(", ") + self.WriteNormal("Authors: {0}".format(authors[0])) + for author in authors[1:]: + self.WriteNormal(" {0}".format(author)) + self.WriteNormal("Version: {0}".format(__version__)) + self.exit() + + + # ---------------------------------------------------------------------------- + # create the sub-parser for the "token-stream" command + # ---------------------------------------------------------------------------- + @CommandAttribute("pretty", help="Pretty-print the DOM to console.", description="Translate a source file into a DOM and pretty-print the DOM.") + @SourceAttribute() + def HandlePretty(self, args): + self.PrintHeadline() + + if args.Files is not None: + for file in args.Files: + if (not file.exists()): + self.WriteError("File '{0!s}' does not exist.".format(file)) + + self.WriteNormal("Parsing file '{!s}'".format(file)) + document = self.addFile(file, "pretty") + self.WriteInfo(dedent( + """\ + libghdl processing time: {: 5.3f} us + DOM translation time: {:5.3f} us + """).format( + document.LibGHDLProcessingTime * 10**6, + document.DOMTranslationTime * 10**6, + ) + ) - def prettyPrint(self): PP = PrettyPrint() buffer = [] - buffer.append("Design:") for line in PP.formatDesign(self._design, 1): buffer.append(line) print("\n".join(buffer)) - document: Document = self._design.Documents[0] - print() - print( - "libghdl processing time: {: 5.3f} us".format( - document.LibGHDLProcessingTime * 10 ** 6 - ) - ) - print( - "DOM translation time: {:5.3f} us".format( - document.DOMTranslationTime * 10 ** 6 - ) - ) + self.exit() + def addFile(self, filename: Path, library: str) -> Document: + lib = self._design.GetLibrary(library) -def handleException(ex): - if isinstance(ex, PrettyPrintException): - print("PP:", ex) - return 0 - elif isinstance(ex, DOMException): - print("DOM:", ex) - ex2 = ex.__cause__ + document = Document(filename) + self._design.AddDocument(document, lib) + return document + + +# main program +def main(): # mccabe:disable=MC0001 + """This is the entry point for pyghdl.cli.dom written as a function. + + 1. It extracts common flags from the script's arguments list, before :py:class:`~argparse.ArgumentParser` is fully loaded. + 2. It creates an instance of DOM test application and hands over to a class based execution. + All is wrapped in a big ``try..except`` block to catch every unhandled exception. + 3. Shutdown the script and return its exit code. + """ + from sys import argv as sys_argv + + debug = "-d" in sys_argv + verbose = "-v" in sys_argv + quiet = "-q" in sys_argv + + try: + # handover to a class instance + app = Application(debug, verbose, quiet) + app.Run() + app.exit() + except PrettyPrintException as ex: + print("PP: {!s}".format(ex)) + LineTerminal.exit() + + except DOMException as ex: + print("DOM: {!s}".format(ex)) + ex2: LibGHDLException = ex.__cause__ if ex2 is not None: for message in ex2.InternalErrors: print("libghdl: {message}".format(message=message)) - return 0 - return 4 - elif isinstance(ex, LibGHDLException): - print("LIB:", ex) + LineTerminal.exit(0) + + LineTerminal.exit(6) + + except LibGHDLException as ex: + print("LIB: {!s}".format(ex)) for message in ex.InternalErrors: print(" {message}".format(message=message)) - return 3 - elif isinstance(ex, GHDLBaseException): - print("GHDL:", ex) - return 2 - else: - print( - "Fatal: An unhandled exception has reached to the top-most exception handler." - ) - return 1 - - -def main(items=argv[1:]): - _exitcode = 0 - - if len(items) < 1: - print("Please, provide the files to be analyzed as CLI arguments.") - print("Using <testsuite/pyunit/SimpleEntity.vhdl> for demo purposes.\n") - items = ["testsuite/pyunit/Current.vhdl"] - - for item in items: - try: - app = Application() - app.addFile(Path(item), "default_lib") - app.prettyPrint() - except GHDLBaseException as ex: - _exitcode = handleException(ex) - except Exception as ex: - print( - dedent( - """\ - Fatal: An unhandled exception has reached to the top-most exception handler. - Exception: {name} - """.format( - name=ex.__class__.__name__ - ) - ) - ) - if isinstance(ex, ValueError): - print(" Message: {msg}".format(msg=str(ex))) - if ex.__cause__ is not None: - print("Cause: {msg}".format(msg=str(ex.__cause__))) + LineTerminal.exit(5) + + except GHDLBaseException as ex: + LineTerminal.printExceptionBase(ex) - return _exitcode + except NotImplementedError as ex: + LineTerminal.printNotImplementedError(ex) + # except ImportError as ex: + # printImportError(ex) + except Exception as ex: + LineTerminal.printException(ex) +# entry point if __name__ == "__main__": - sysexit(main()) + LineTerminal.versionCheck((3, 6, 0)) + main() diff --git a/pyGHDL/requirements.txt b/pyGHDL/requirements.txt index 013f8cbef..ecc97deaf 100644 --- a/pyGHDL/requirements.txt +++ b/pyGHDL/requirements.txt @@ -1,3 +1,6 @@ pydecor>=2.0.1 +pyAttributes==2.1.0 +pyMetaClasses==1.2.1 #pyVHDLModel==0.11.1 https://github.com/VHDL/pyVHDLModel/archive/dev.zip#pyVHDLModel +pyTerminalUI==1.3.4 |