Exemplo n.º 1
0
    def upgrade(self):
        warnings = OrderedDict()
        for vn, upgs in self.upgrades.items():
            for u in upgs:
                try:
                    exp = self.expand(u)
                except (KeyError, UpgradeError):
                    continue

                for upg in exp:
                    try:
                        old = self.get_item(upg['old'])
                    except KeyError:
                        # OK: deprecated item not found
                        pass
                    else:
                        msg = self.show_keys(upg['old'])
                        if upg['new']:
                            msg += ' -> ' + self.show_keys(upg['new'])
                        else:
                            upg['new'] = upg['old']
                        msg += " - " + upg['cvt'].describe()
                        if not upg['silent']:
                            warnings.setdefault(vn, [])
                            warnings[vn].append(msg)
                        self.del_item(upg['old'])
                        if upg['cvt'].describe() != "DELETED (OBSOLETE)":
                            # check self.cfg does not already contain a
                            # non-deprecated item matching upg['new']:
                            try:
                                self.get_item(upg['new'])
                            except KeyError:
                                self.put_item(upg['new'],
                                              upg['cvt'].convert(old))
                            else:
                                raise UpgradeError(
                                    'ERROR: Cannot upgrade deprecated '
                                    f'item "{msg}" because the upgraded '
                                    'item already exists'
                                )
        if warnings:
            level = WARNING
            if self.descr == self.SITE_CONFIG:
                # Site level configuration, user cannot easily fix.
                # Only log at debug level.
                level = DEBUG
            else:
                # User level configuration, user should be able to fix.
                # Log at warning level.
                level = WARNING
            LOG.log(level,
                    'deprecated items were automatically upgraded in '
                    f'"{self.descr}"')
            for vn, msgs in warnings.items():
                for msg in msgs:
                    LOG.log(level, ' * (%s) %s', vn, msg)
Exemplo n.º 2
0
Arquivo: upgrade.py Projeto: cylc/cylc
    def upgrade(self):
        warnings = OrderedDict()
        for vn, upgs in self.upgrades.items():
            for u in upgs:
                try:
                    exp = self.expand(u)
                except (KeyError, UpgradeError):
                    continue

                for upg in exp:
                    try:
                        old = self.get_item(upg['old'])
                    except KeyError:
                        # OK: deprecated item not found
                        pass
                    else:
                        msg = self.show_keys(upg['old'])
                        if upg['new']:
                            msg += ' -> ' + self.show_keys(upg['new'])
                        else:
                            upg['new'] = upg['old']
                        msg += " - " + upg['cvt'].describe()
                        if not upg['silent']:
                            warnings.setdefault(vn, [])
                            warnings[vn].append(msg)
                        self.del_item(upg['old'])
                        if upg['cvt'].describe() != "DELETED (OBSOLETE)":
                            self.put_item(upg['new'], upg['cvt'].convert(old))
        if warnings:
            level = WARNING
            if self.descr == self.SITE_CONFIG:
                # Site level configuration, user cannot easily fix.
                # Only log at debug level.
                level = DEBUG
            else:
                # User level configuration, user should be able to fix.
                # Log at warning level.
                level = WARNING
            LOG.log(
                level,
                "deprecated items were automatically upgraded in '%s':",
                self.descr)
            for vn, msgs in warnings.items():
                for msg in msgs:
                    LOG.log(level, ' * (%s) %s', vn, msg)
Exemplo n.º 3
0
 def get_summary_str(self):
     """Return the poll context as a summary string delimited by "|"."""
     ret = OrderedDict()
     for key in self.CONTEXT_ATTRIBUTES:
         value = getattr(self, key)
         if key == 'job_log_dir' or value is None:
             continue
         ret[key] = value
     return '%s|%s' % (self.job_log_dir, json.dumps(ret))
Exemplo n.º 4
0
 def test_expand_obsolete(self):
     upg = {
         'new': None,
         'cvt': None,
         'silent': True,
         'old': ['section', '__MANY__', 'a']
     }
     self.cfg['__MANY__'] = OrderedDict()
     self.cfg['__MANY__']['name'] = 'Arthur'
     self.u.obsolete('entry', ['section', '__MANY__'])
     self.u.upgrade()
     expanded = self.u.expand(upg)
     self.assertEqual(1, len(expanded))
     self.assertTrue(expanded[0]['new'] is None)
Exemplo n.º 5
0
 def setUp(self):
     self.cfg = OrderedDict()
     self.cfg['section'] = OrderedDict()
     self.cfg['section']['a'] = '1'
     self.cfg['section']['b'] = '2'
     self.u = upgrader(self.cfg, "1.0 to 2.0")
Exemplo n.º 6
0
Arquivo: upgrade.py Projeto: cylc/cylc
 def __init__(self, cfg, descr):
     """Store the config dict to be upgraded if necessary."""
     self.cfg = cfg
     self.descr = descr
     # upgrades must be ordered in case several act on the same item
     self.upgrades = OrderedDict()
Exemplo n.º 7
0
Arquivo: upgrade.py Projeto: cylc/cylc
class upgrader(object):
    """Handles upgrading of deprecated config values."""

    SITE_CONFIG = 'site config'
    USER_CONFIG = 'user config'

    def __init__(self, cfg, descr):
        """Store the config dict to be upgraded if necessary."""
        self.cfg = cfg
        self.descr = descr
        # upgrades must be ordered in case several act on the same item
        self.upgrades = OrderedDict()

    def deprecate(self, vn, oldkeys, newkeys=None, cvtr=None, silent=False):
        if vn not in self.upgrades:
            self.upgrades[vn] = []
        if cvtr is None:
            cvtr = converter(lambda x: x, "value unchanged")  # identity
        self.upgrades[vn].append(
            {'old': oldkeys, 'new': newkeys, 'cvt': cvtr, 'silent': silent})

    def obsolete(self, vn, oldkeys, newkeys=None, silent=False):
        if vn not in self.upgrades:
            self.upgrades[vn] = []
        cvtr = converter(lambda x: x, "DELETED (OBSOLETE)")  # identity
        self.upgrades[vn].append(
            {'old': oldkeys, 'new': newkeys, 'cvt': cvtr, 'silent': silent})

    def get_item(self, keys):
        item = self.cfg
        for key in keys:
            item = item[key]
        return item

    def put_item(self, keys, val):
        item = self.cfg
        for key in keys[:-1]:
            if key not in item:
                item[key] = {}
            item = item[key]
        item[keys[-1]] = val

    def del_item(self, keys):
        item = self.cfg
        for key in keys[:-1]:
            item = item[key]
        del item[keys[-1]]

    @staticmethod
    def show_keys(keys):
        return '[' + ']['.join(keys) + ']'

    def expand(self, upg):
        """Expands __MANY__ items."""
        if '__MANY__' not in upg['old']:
            return [upg]
        if upg['old'].count('__MANY__') > 1:
            raise UpgradeError(
                'Multiple simultaneous __MANY__ not supported: %s' %
                upg['old'])
        exp_upgs = []
        pre = []
        post = []
        many = []
        i = -1
        okeys = upg['old']
        for k in okeys:
            i += 1
            if k == "__MANY__":
                pre = okeys[:i]
                post = okeys[i + 1:]
                tmp = self.cfg
                for j in pre:
                    tmp = tmp[j]
                many = list(tmp.keys())
                break
        if not many:
            exp_upgs.append(upg)
        else:
            i = -1
            nkeys = upg['new']
            if nkeys is None:  # No new keys defined.
                for m in many:
                    exp_upgs.append({
                        'old': pre + [m] + post,
                        'new': None,
                        'cvt': upg['cvt'],
                        'silent': upg['silent'],
                    })
                return exp_upgs
            npre = []
            npost = []
            for k in nkeys:
                i += 1
                if k == "__MANY__":
                    npre = nkeys[:i]
                    npost = nkeys[i + 1:]
            if not npre or not npost:
                raise UpgradeError('__MANY__ mismatch')
            for m in many:
                exp_upgs.append({
                    'old': pre + [m] + post,
                    'new': npre + [m] + npost,
                    'cvt': upg['cvt'],
                    'silent': upg['silent'],
                })
        return exp_upgs

    def upgrade(self):
        warnings = OrderedDict()
        for vn, upgs in self.upgrades.items():
            for u in upgs:
                try:
                    exp = self.expand(u)
                except (KeyError, UpgradeError):
                    continue

                for upg in exp:
                    try:
                        old = self.get_item(upg['old'])
                    except KeyError:
                        # OK: deprecated item not found
                        pass
                    else:
                        msg = self.show_keys(upg['old'])
                        if upg['new']:
                            msg += ' -> ' + self.show_keys(upg['new'])
                        else:
                            upg['new'] = upg['old']
                        msg += " - " + upg['cvt'].describe()
                        if not upg['silent']:
                            warnings.setdefault(vn, [])
                            warnings[vn].append(msg)
                        self.del_item(upg['old'])
                        if upg['cvt'].describe() != "DELETED (OBSOLETE)":
                            self.put_item(upg['new'], upg['cvt'].convert(old))
        if warnings:
            level = WARNING
            if self.descr == self.SITE_CONFIG:
                # Site level configuration, user cannot easily fix.
                # Only log at debug level.
                level = DEBUG
            else:
                # User level configuration, user should be able to fix.
                # Log at warning level.
                level = WARNING
            LOG.log(
                level,
                "deprecated items were automatically upgraded in '%s':",
                self.descr)
            for vn, msgs in warnings.items():
                for msg in msgs:
                    LOG.log(level, ' * (%s) %s', vn, msg)
Exemplo n.º 8
0
 def __init__(self, cfg, descr):
     """Store the config dict to be upgraded if necessary."""
     self.cfg = cfg
     self.descr = descr
     # upgrades must be ordered in case several act on the same item
     self.upgrades = OrderedDict()
Exemplo n.º 9
0
class upgrader:
    """Handles upgrading of deprecated config values."""

    SITE_CONFIG = 'site config'
    USER_CONFIG = 'user config'

    def __init__(self, cfg, descr):
        """Store the config dict to be upgraded if necessary."""
        self.cfg = cfg
        self.descr = descr
        # upgrades must be ordered in case several act on the same item
        self.upgrades = OrderedDict()

    def deprecate(self, vn, oldkeys, newkeys=None, cvtr=None, silent=False):
        """Replace a deprecated key from a config
        Args:
            vn (str):
                Version at which this deprecation occurs.
            oldkeys (list):
                Path within config to be changed.
            newkeys (list):
                New location in the config for the item in "oldkeys".
            cvtr (cylc.flow.parsec.upgrade.Converter):
                Converter object containing a conversion function and a
                description of that function.
            silent:
                Set silent mode for this upgrade.
        """
        if vn not in self.upgrades:
            self.upgrades[vn] = []
        if cvtr is None:
            cvtr = converter(lambda x: x, "value unchanged")  # identity
        self.upgrades[vn].append(
            {'old': oldkeys, 'new': newkeys, 'cvt': cvtr, 'silent': silent})

    def obsolete(self, vn, oldkeys, silent=False):
        """Remove an obsolete key from a config
        Args:
            vn (str):
                Version at which this obsoletion occurs.
            oldkeys (list):
                Path within config to be removed.
            silent:
                Set silent mode for this upgrade.
        """
        if vn not in self.upgrades:
            self.upgrades[vn] = []
        cvtr = converter(lambda x: x, "DELETED (OBSOLETE)")  # identity
        self.upgrades[vn].append(
            {'old': oldkeys, 'new': None, 'cvt': cvtr, 'silent': silent})

    def get_item(self, keys):
        item = self.cfg
        for key in keys:
            item = item[key]
        return item

    def put_item(self, keys, val):
        item = self.cfg
        for key in keys[:-1]:
            if key not in item:
                item[key] = {}
            item = item[key]
        item[keys[-1]] = val

    def del_item(self, keys):
        item = self.cfg
        for key in keys[:-1]:
            item = item[key]
        del item[keys[-1]]

    @staticmethod
    def show_keys(keys):
        return '[' + ']['.join(keys) + ']'

    def expand(self, upg):
        """Expands __MANY__ items."""
        if '__MANY__' not in upg['old']:
            return [upg]
        if upg['old'].count('__MANY__') > 1:
            raise UpgradeError(
                f"Multiple simultaneous __MANY__ not supported: {upg['old']}")
        exp_upgs = []
        pre = []
        post = []
        many = []
        okeys = upg['old']
        for i, k in enumerate(okeys):
            if k == "__MANY__":
                pre = okeys[:i]
                post = okeys[i + 1:]
                tmp = self.cfg
                for j in pre:
                    tmp = tmp[j]
                many = list(tmp.keys())
                break
        if not many:
            exp_upgs.append(upg)
        else:
            i = -1
            nkeys = upg['new']
            if nkeys is None:  # No new keys defined.
                for m in many:
                    exp_upgs.append({
                        'old': pre + [m] + post,
                        'new': None,
                        'cvt': upg['cvt'],
                        'silent': upg['silent'],
                    })
                return exp_upgs
            npre = []
            npost = []
            for k in nkeys:
                i += 1  # noqa: SIM113 (multiple loops interacting)
                if k == "__MANY__":
                    npre = nkeys[:i]
                    npost = nkeys[i + 1:]
            if not npre or not npost:
                raise UpgradeError('__MANY__ mismatch')
            for m in many:
                exp_upgs.append({
                    'old': pre + [m] + post,
                    'new': npre + [m] + npost,
                    'cvt': upg['cvt'],
                    'silent': upg['silent'],
                })
        return exp_upgs

    def upgrade(self):
        warnings = OrderedDict()
        for vn, upgs in self.upgrades.items():
            for u in upgs:
                try:
                    exp = self.expand(u)
                except (KeyError, UpgradeError):
                    continue

                for upg in exp:
                    try:
                        old = self.get_item(upg['old'])
                    except KeyError:
                        # OK: deprecated item not found
                        pass
                    else:
                        msg = self.show_keys(upg['old'])
                        if upg['new']:
                            msg += ' -> ' + self.show_keys(upg['new'])
                        msg += " - " + upg['cvt'].describe()
                        if not upg['silent']:
                            warnings.setdefault(vn, [])
                            warnings[vn].append(msg)
                        self.del_item(upg['old'])
                        if upg['new']:
                            # check self.cfg does not already contain a
                            # non-deprecated item matching upg['new']:
                            nval = ""
                            with contextlib.suppress(KeyError):
                                nval = self.get_item(upg['new'])
                            if nval:
                                # Conflicting item exists, with non-null value.
                                raise UpgradeError(
                                    'ERROR: Cannot upgrade deprecated '
                                    f'item "{msg}" because the upgraded '
                                    'item already exists'
                                )
                            self.put_item(upg['new'],
                                          upg['cvt'].convert(old))
        if warnings:
            level = WARNING
            if self.descr == self.SITE_CONFIG:
                # Site level configuration, user cannot easily fix.
                # Only log at debug level.
                level = DEBUG
            else:
                # User level configuration, user should be able to fix.
                # Log at warning level.
                level = WARNING
            LOG.log(level,
                    'deprecated items were automatically upgraded in '
                    f'"{self.descr}"')
            for vn, msgs in warnings.items():
                for msg in msgs:
                    LOG.log(level, ' * (%s) %s', vn, msg)
Exemplo n.º 10
0
#
# 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 <http://www.gnu.org/licenses/>.
"""
An empty config file should successfully yield an empty sparse config dict.
"""


import os
import sys

from cylc.flow.parsec.config import ParsecConfig
from cylc.flow.parsec.validate import ParsecValidator as VDR
from cylc.flow.parsec.OrderedDict import OrderedDict

fpath = os.path.dirname(os.path.abspath(__file__))
# parsec
sys.path.append(fpath + '/../../..')

SPEC = {'meta': {'title': [VDR.V_STRING]}}
cfg = ParsecConfig(SPEC)
cfg.loadcfg("empty.rc")

if cfg.get(sparse=True) != OrderedDict():
    sys.exit(1)
Exemplo n.º 11
0
class upgrader(object):
    """Handles upgrading of deprecated config values."""

    SITE_CONFIG = 'site config'
    USER_CONFIG = 'user config'

    def __init__(self, cfg, descr):
        """Store the config dict to be upgraded if necessary."""
        self.cfg = cfg
        self.descr = descr
        # upgrades must be ordered in case several act on the same item
        self.upgrades = OrderedDict()

    def deprecate(self, vn, oldkeys, newkeys=None, cvtr=None, silent=False):
        if vn not in self.upgrades:
            self.upgrades[vn] = []
        if cvtr is None:
            cvtr = converter(lambda x: x, "value unchanged")  # identity
        self.upgrades[vn].append({
            'old': oldkeys,
            'new': newkeys,
            'cvt': cvtr,
            'silent': silent
        })

    def obsolete(self, vn, oldkeys, newkeys=None, silent=False):
        if vn not in self.upgrades:
            self.upgrades[vn] = []
        cvtr = converter(lambda x: x, "DELETED (OBSOLETE)")  # identity
        self.upgrades[vn].append({
            'old': oldkeys,
            'new': newkeys,
            'cvt': cvtr,
            'silent': silent
        })

    def get_item(self, keys):
        item = self.cfg
        for key in keys:
            item = item[key]
        return item

    def put_item(self, keys, val):
        item = self.cfg
        for key in keys[:-1]:
            if key not in item:
                item[key] = {}
            item = item[key]
        item[keys[-1]] = val

    def del_item(self, keys):
        item = self.cfg
        for key in keys[:-1]:
            item = item[key]
        del item[keys[-1]]

    @staticmethod
    def show_keys(keys):
        return '[' + ']['.join(keys) + ']'

    def expand(self, upg):
        """Expands __MANY__ items."""
        if '__MANY__' not in upg['old']:
            return [upg]
        if upg['old'].count('__MANY__') > 1:
            raise UpgradeError(
                'Multiple simultaneous __MANY__ not supported: %s' %
                upg['old'])
        exp_upgs = []
        pre = []
        post = []
        many = []
        i = -1
        okeys = upg['old']
        for k in okeys:
            i += 1
            if k == "__MANY__":
                pre = okeys[:i]
                post = okeys[i + 1:]
                tmp = self.cfg
                for j in pre:
                    tmp = tmp[j]
                many = list(tmp.keys())
                break
        if not many:
            exp_upgs.append(upg)
        else:
            i = -1
            nkeys = upg['new']
            if nkeys is None:  # No new keys defined.
                for m in many:
                    exp_upgs.append({
                        'old': pre + [m] + post,
                        'new': None,
                        'cvt': upg['cvt'],
                        'silent': upg['silent'],
                    })
                return exp_upgs
            npre = []
            npost = []
            for k in nkeys:
                i += 1
                if k == "__MANY__":
                    npre = nkeys[:i]
                    npost = nkeys[i + 1:]
            if not npre or not npost:
                raise UpgradeError('__MANY__ mismatch')
            for m in many:
                exp_upgs.append({
                    'old': pre + [m] + post,
                    'new': npre + [m] + npost,
                    'cvt': upg['cvt'],
                    'silent': upg['silent'],
                })
        return exp_upgs

    def upgrade(self):
        warnings = OrderedDict()
        for vn, upgs in self.upgrades.items():
            for u in upgs:
                try:
                    exp = self.expand(u)
                except (KeyError, UpgradeError):
                    continue

                for upg in exp:
                    try:
                        old = self.get_item(upg['old'])
                    except KeyError:
                        # OK: deprecated item not found
                        pass
                    else:
                        msg = self.show_keys(upg['old'])
                        if upg['new']:
                            msg += ' -> ' + self.show_keys(upg['new'])
                        else:
                            upg['new'] = upg['old']
                        msg += " - " + upg['cvt'].describe()
                        if not upg['silent']:
                            warnings.setdefault(vn, [])
                            warnings[vn].append(msg)
                        self.del_item(upg['old'])
                        if upg['cvt'].describe() != "DELETED (OBSOLETE)":
                            self.put_item(upg['new'], upg['cvt'].convert(old))
        if warnings:
            level = WARNING
            if self.descr == self.SITE_CONFIG:
                # Site level configuration, user cannot easily fix.
                # Only log at debug level.
                level = DEBUG
            else:
                # User level configuration, user should be able to fix.
                # Log at warning level.
                level = WARNING
            LOG.log(level,
                    "deprecated items were automatically upgraded in '%s':",
                    self.descr)
            for vn, msgs in warnings.items():
                for msg in msgs:
                    LOG.log(level, ' * (%s) %s', vn, msg)
Exemplo n.º 12
0
    def run(self, options, *args):
        suite = args[0]

        if len(args) > 1:
            try:
                user_at_host, options.port = args[1].split(":")
                options.owner, options.host = user_at_host.split("@")
            except ValueError:
                print(
                    ("USER_AT_HOST must take the form " '"user@host:port"'),
                    file=sys.stderr,
                )
                sys.exit(1)

        client_name = os.path.basename(sys.argv[0])
        if options.restricted:
            client_name += " -r"

        legend = ""
        for state in TASK_STATUSES_ORDERED:
            legend += get_status_prop(state, "ascii_ctrl")
        legend += f"{HELD_SYMBOL}"
        legend = legend.rstrip()

        len_header = sum(len(s) for s in TASK_STATUSES_ORDERED)

        self.reset(
            suite,
            options.owner,
            options.host,
            options.port,
            options.comms_timeout,
        )

        is_cont = False
        while True:
            if is_cont:
                if options.once:
                    break
                else:
                    sleep(float(options.update_interval))
            is_cont = True
            try:
                glbl, task_summaries = self.pclient("get_suite_state_summary")[
                    0:2
                ]
            except ClientError as exc:
                print("\033[1;37;41mERROR\033[0m", str(exc), file=sys.stderr)
                self.reset(
                    suite,
                    options.owner,
                    options.host,
                    options.port,
                    options.comms_timeout,
                )
            else:
                if not glbl:
                    print(
                        ("\033[1;37;41mWARNING\033[0m suite initialising"),
                        file=sys.stderr,
                    )
                    continue
                states = [
                    t["state"]
                    for t in task_summaries.values()
                    if ("state" in t)
                ]
                n_tasks_total = len(states)
                if options.restricted:
                    task_summaries = dict(
                        (i, j)
                        for i, j in task_summaries.items()
                        if (j["state"] in TASK_STATUSES_RESTRICTED)
                    )
                if not options.display_runahead:
                    task_summaries = dict(
                        (i, j)
                        for i, j in task_summaries.items()
                        if (j["state"] != TASK_STATUS_RUNAHEAD)
                    )
                try:
                    updated_at = get_time_string_from_unix_time(
                        glbl["last_updated"]
                    )
                except KeyError:
                    updated_at = time()
                except (TypeError, ValueError):
                    # Older suite.
                    updated_at = glbl["last_updated"].isoformat()

                run_mode = glbl["run_mode"]
                ns_defn_order = glbl["namespace definition order"]
                status_string = glbl["status_string"]

                task_info = {}
                name_list = set()
                task_ids = list(task_summaries)
                for task_id in task_ids:
                    name = task_summaries[task_id]["name"]
                    point_string = task_summaries[task_id]["label"]
                    state = task_summaries[task_id]["state"]
                    is_held = task_summaries[task_id]["is_held"]
                    name_list.add(name)
                    if is_held:
                        text = f"{HELD_SYMBOL}{name}"
                    else:
                        text = name
                    if point_string not in task_info:
                        task_info[point_string] = {}
                    task_info[point_string][name] = get_status_prop(
                        state, "ascii_ctrl", subst=text
                    )

                # Sort the tasks in each cycle point.
                if options.sort_order == "alphanumeric":
                    sorted_name_list = sorted(name_list)
                else:
                    sorted_name_list = ns_defn_order

                sorted_task_info = {}
                for point_str, info in task_info.items():
                    sorted_task_info[point_str] = OrderedDict()
                    for name in sorted_name_list:
                        if name in name_list:
                            # (Defn order includes family names.).
                            sorted_task_info[point_str][name] = info.get(name)

                # Construct lines to blit to the screen.
                blit = []

                suite_name = suite
                if run_mode != "live":
                    suite_name += " (%s)" % run_mode
                prefix = "%s - %d tasks" % (suite_name, int(n_tasks_total))
                suffix = "%s" % client_name
                title_str = " " * len_header
                title_str = prefix + title_str[len(prefix):]
                title_str = "\033[1;37;44m%s%s\033[0m" % (
                    title_str[: -len(suffix)],
                    suffix,
                )
                blit.append(title_str)
                blit.append(legend)

                updated_str = "updated: \033[1;38m%s\033[0m" % updated_at
                blit.append(updated_str)
                summary = "state summary:"
                state_totals = glbl["state totals"]
                for state, tot in state_totals.items():
                    subst = " %d " % tot
                    summary += get_status_prop(state, "ascii_ctrl", subst)
                blit.append(summary)

                # Print a divider line containing the suite status string.
                try:
                    status1, status2 = (
                        SUITE_STATUS_SPLIT_REC.match(status_string)
                    ).groups()
                except AttributeError:
                    status1 = status_string
                    status2 = ""
                suffix = "_".join(list(status1.replace(" ", "_"))) + status2
                divider_str = "_" * len_header
                divider_str = "\033[1;31m%s%s\033[0m" % (
                    divider_str[: -len(suffix)],
                    suffix,
                )
                blit.append(divider_str)

                blitlines = {}
                for point_str, val in sorted_task_info.items():
                    indx = point_str
                    line = "\033[1;34m%s\033[0m" % point_str
                    for name, info in val.items():
                        if info is not None:
                            line += " %s" % info
                        elif options.align_columns:
                            line += " %s" % (" " * len(name))
                    blitlines[indx] = line

                if not options.once:
                    os.system("clear")
                print("\n".join(blit))
                indxs = list(blitlines)
                try:
                    int(indxs[1])
                except (ValueError, IndexError):
                    indxs.sort()
                else:
                    indxs.sort(key=int)
                for ix in indxs:
                    print(blitlines[ix])