misc: Added line wrapping functionality for Sim-Object desc

Descriptions were previously printed on one line, unless explicitly broken
when writing the description of the Sim-Object. In this commit, line
wrapping is enabled when printing these descriptions. Developers, when
writing the Sim-Object descriptions, may now over multiple lines with
triple double-quotes and still have the description output correctly when
viewing the Sim-Objects within the CLI.

E.g.: X86System previously had the following load_addr_mask component which
was output as:

load_addr_mask
            default: 18446744073709551615
               desc: Address to mask loading binaries with, if 0, system \
auto-calculates the mask to be the most restrictive, otherwise it obeys a \
custom mask.

This was defined by the developer via:

load_addr_mask = Param.UInt64(0xffffffffffffffff,
            "Address to mask loading binaries with, if 0, system "
            "auto-calculates the mask to be the most restrictive, "
            "otherwise it obeys a custom mask.")

This is now displayed as:

load_addr_mask
            default: 18446744073709551615
               desc: Address to mask loading binaries with, if 0,
                     system auto-calculates the mask to be the most
                     restrictive, otherwise it obeys a custom mask.

JiraID: Gem5-57
Built: Linux (GCC)
Tested: Ran quick tests for X86, ARM, and RISC-V
Change-Id: If012304e50af60f6ba10c1fa2b44da8bac1c09cf
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/21179
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Reviewed-by: Daniel Carvalho <odanrc@yahoo.com.br>
Maintainer: Andreas Sandberg <andreas.sandberg@arm.com>
diff --git a/src/python/SConscript b/src/python/SConscript
index 758ab3d..8264e38 100644
--- a/src/python/SConscript
+++ b/src/python/SConscript
@@ -61,6 +61,7 @@
 PySource('m5.util', 'm5/util/terminal.py')
 PySource('m5.util', 'm5/util/pybind.py')
 PySource('m5.util', 'm5/util/fdthelper.py')
+PySource('m5.util', 'm5/util/terminal_formatter.py')
 
 PySource('m5.internal', 'm5/internal/__init__.py')
 PySource('m5.internal', 'm5/internal/params.py')
diff --git a/src/python/m5/main.py b/src/python/m5/main.py
index 295108c..92d9bb4 100644
--- a/src/python/m5/main.py
+++ b/src/python/m5/main.py
@@ -227,6 +227,7 @@
     from . import trace
 
     from .util import inform, fatal, panic, isInteractive
+    from m5.util.terminal_formatter import TerminalFormatter
 
     if len(args) == 0:
         options, arguments = parse_options()
@@ -306,18 +307,21 @@
         print("SimObjects:")
         objects = list(SimObject.allClasses.keys())
         objects.sort()
+        terminal_formatter = TerminalFormatter()
         for name in objects:
             obj = SimObject.allClasses[name]
-            print("    %s" % obj)
+            print(terminal_formatter.format_output(str(obj), indent=4))
             params = list(obj._params.keys())
             params.sort()
             for pname in params:
                 param = obj._params[pname]
                 default = getattr(param, 'default', '')
-                print("        %s" % pname)
+                print(terminal_formatter.format_output(pname, indent=8))
                 if default:
-                    print("            default: %s" % default)
-                print("            desc: %s" % param.desc)
+                    print(terminal_formatter.format_output(
+                        str(default), label="default: ", indent=21))
+                print(terminal_formatter.format_output(
+                    param.desc, label="desc: ", indent=21))
                 print()
             print()
 
diff --git a/src/python/m5/util/terminal_formatter.py b/src/python/m5/util/terminal_formatter.py
new file mode 100644
index 0000000..84c21db
--- /dev/null
+++ b/src/python/m5/util/terminal_formatter.py
@@ -0,0 +1,133 @@
+# Copyright (c) 2019 The Regents of the University of California
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met: redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer;
+# redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution;
+# neither the name of the copyright holders nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# Authors: Bobby R. Bruce
+
+
+
+import textwrap
+
+class TerminalFormatter:
+
+    def __init__(self, max_width=80):
+        # text_width holds the actual width we'll be wrapping to.
+        # This takes into account the current terminal size.
+        self.__text_width = min(max_width, self.__terminal_size()[0])
+
+    def __terminal_size(self):
+        import fcntl, termios, struct
+        h, w, hp, wp = struct.unpack('HHHH',
+            fcntl.ioctl(0, termios.TIOCGWINSZ,
+            struct.pack('HHHH', 0, 0, 0, 0)))
+        return w, h
+
+    def __get_paragraphs(self, text, flatten=False):
+
+        """
+        This function takes a text and returns a list of constituent
+        paragraphs, defining a paragraph as a block of text separated from
+        other text by a blank line (or one containing only whitespace). If the
+        "flatten" argument is set to true, all line breaks within paragraphs
+        will be removed.
+
+        E.g.:
+
+        text = '''Hello, this is
+        paragraph number 1.
+
+        This is paragraph number 2.
+
+        And this is
+        paragraph number 3.
+        '''
+
+        __get_paragraphs(text, False)
+        ["Hello, this is\nparagraph number 1", "This is paragraph number 2.",
+            "And this is\npagraph number 3."]
+
+        __get_paragraphs(text, True)
+        ["Hello, this is paragraph number 1", "This is paragraph number 2.",
+            "And this is pagraph number 3."]
+        """
+
+        paragraphs = []
+        cur_paragraph = []
+
+        for line in text.splitlines():
+            stripped = line.strip()
+            if not stripped: #I.e. a blank line.
+                paragraphs.append(
+                        {False: "\n", True: " "}[flatten].join(cur_paragraph))
+                cur_paragraph = []
+            else:
+                cur_paragraph.append(stripped)
+
+        paragraphs.append(
+                {False: "\n", True: " "}[flatten].join(cur_paragraph))
+
+        return paragraphs
+
+    def format_output(self, text, label="", indent=0):
+        """
+        This function aids in the formatting of outputs. When obtaining
+        the list of sim object we desire the output in the following
+        format:
+
+        desc: Address to mask loading binaries with, if 0,
+              system auto-calculates the mask to be the most
+              restrictive, otherwise it obeys a custom mask.
+
+        We must take into account the width of the text, and wrap
+        accordingly. We also must display the label.
+
+        Keyword arguments:
+
+        text --- The description text.
+        label --- The label of the output (e.g. "desc: ").
+        indent --- The white space width before each line.
+        """
+
+        if not text.strip():
+            return ""
+
+        # The text  may be over multiple lines (as when using triple
+        # double quotes). First, we split the text into its constituent
+        # paragraphs and remove new line characters from each.
+        paragraphs = self.__get_paragraphs(text, True)
+
+        # Wrap and Indent the paragraphs
+        wrapper = textwrap.TextWrapper(width =
+                max((self.__text_width - indent),1))
+        # The first paragraph is special case due to the inclusion of the label
+        formatted_paragraphs = [' ' * max((indent - len(label)),0) \
+                + label + wrapper.wrap(paragraphs[0])[0]]
+        for paragraph in paragraphs:
+            for line in wrapper.wrap(paragraph[1:])[1:]:
+                formatted_paragraphs.append(' ' * indent + line)
+            formatted_paragraphs.append('\n')
+
+        # Remove the last line break
+        return '\n'.join(formatted_paragraphs[:-1])