aboutsummaryrefslogtreecommitdiffstats
path: root/pyGHDL
diff options
context:
space:
mode:
authorPatrick Lehmann <Patrick.Lehmann@plc2.de>2021-06-30 20:45:03 +0200
committerPatrick Lehmann <Patrick.Lehmann@plc2.de>2021-07-01 06:39:47 +0200
commitc75693ff6f5c7b05d49ce916e34aaba61b688e31 (patch)
treeac5eca19c8fe114be81d3f2cb6e47c18a462865a /pyGHDL
parent301dea333ec3e28e95a43b1a4af569ebbedd6ab9 (diff)
downloadghdl-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__.py5
-rwxr-xr-xpyGHDL/cli/DOM.py339
-rw-r--r--pyGHDL/requirements.txt3
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