def format()

in lib/ramble/spack/spec.py [0:0]


    def format(self, format_string=default_format, **kwargs):
        r"""Prints out particular pieces of a spec, depending on what is
        in the format string.

        Using the ``{attribute}`` syntax, any field of the spec can be
        selected.  Those attributes can be recursive. For example,
        ``s.format({compiler.version})`` will print the version of the
        compiler.

        Commonly used attributes of the Spec for format strings include::

            name
            version
            compiler
            compiler.name
            compiler.version
            compiler_flags
            variants
            architecture
            architecture.platform
            architecture.os
            architecture.target
            prefix

        Some additional special-case properties can be added::

            hash[:len]    The DAG hash with optional length argument
            spack_root    The spack root directory
            spack_install The spack install directory

        The ``^`` sigil can be used to access dependencies by name.
        ``s.format({^mpi.name})`` will print the name of the MPI
        implementation in the spec.

        The ``@``, ``%``, ``arch=``, and ``/`` sigils
        can be used to include the sigil with the printed
        string. These sigils may only be used with the appropriate
        attributes, listed below::

            @        ``{@version}``, ``{@compiler.version}``
            %        ``{%compiler}``, ``{%compiler.name}``
            arch=    ``{arch=architecture}``
            /        ``{/hash}``, ``{/hash:7}``, etc

        The ``@`` sigil may also be used for any other property named
        ``version``. Sigils printed with the attribute string are only
        printed if the attribute string is non-empty, and are colored
        according to the color of the attribute.

        Sigils are not used for printing variants. Variants listed by
        name naturally print with their sigil. For example,
        ``spec.format('{variants.debug}')`` would print either
        ``+debug`` or ``~debug`` depending on the name of the
        variant. Non-boolean variants print as ``name=value``. To
        print variant names or values independently, use
        ``spec.format('{variants.<name>.name}')`` or
        ``spec.format('{variants.<name>.value}')``.

        Spec format strings use ``\`` as the escape character. Use
        ``\{`` and ``\}`` for literal braces, and ``\\`` for the
        literal ``\`` character. Also use ``\$`` for the literal ``$``
        to differentiate from previous, deprecated format string
        syntax.

        The previous format strings are deprecated. They can still be
        accessed by the ``old_format`` method. The ``format`` method
        will call ``old_format`` if the character ``$`` appears
        unescaped in the format string.


        Args:
            format_string (str): string containing the format to be expanded

        Keyword Args:
            color (bool): True if returned string is colored
            transform (dict): maps full-string formats to a callable \
                that accepts a string and returns another one

        """
        # If we have an unescaped $ sigil, use the deprecated format strings
        if re.search(r'[^\\]*\$', format_string):
            return self.old_format(format_string, **kwargs)

        color = kwargs.get('color', False)
        transform = kwargs.get('transform', {})

        out = io.StringIO()

        def write(s, c=None):
            f = clr.cescape(s)
            if c is not None:
                f = color_formats[c] + f + '@.'
            clr.cwrite(f, stream=out, color=color)

        def write_attribute(spec, attribute, color):
            current = spec
            if attribute.startswith('^'):
                attribute = attribute[1:]
                dep, attribute = attribute.split('.', 1)
                current = self[dep]

            if attribute == '':
                raise SpecFormatStringError(
                    'Format string attributes must be non-empty')
            attribute = attribute.lower()

            sig = ''
            if attribute[0] in '@%/':
                # color sigils that are inside braces
                sig = attribute[0]
                attribute = attribute[1:]
            elif attribute.startswith('arch='):
                sig = ' arch='  # include space as separator
                attribute = attribute[5:]

            parts = attribute.split('.')
            assert parts

            # check that the sigil is valid for the attribute.
            if sig == '@' and parts[-1] not in ('versions', 'version'):
                raise SpecFormatSigilError(sig, 'versions', attribute)
            elif sig == '%' and attribute not in ('compiler', 'compiler.name'):
                raise SpecFormatSigilError(sig, 'compilers', attribute)
            elif sig == '/' and not re.match(r'hash(:\d+)?$', attribute):
                raise SpecFormatSigilError(sig, 'DAG hashes', attribute)
            elif sig == ' arch=' and attribute not in ('architecture', 'arch'):
                raise SpecFormatSigilError(sig, 'the architecture', attribute)

            # find the morph function for our attribute
            morph = transform.get(attribute, lambda s, x: x)

            # Special cases for non-spec attributes and hashes.
            # These must be the only non-dep component of the format attribute
            if attribute == 'spack_root':
                write(morph(spec, spack.paths.spack_root))
                return
            elif attribute == 'spack_install':
                write(morph(spec, spack.store.layout.root))
                return
            elif re.match(r'hash(:\d)?', attribute):
                col = '#'
                if ':' in attribute:
                    _, length = attribute.split(':')
                    write(sig + morph(spec, spec.dag_hash(int(length))), col)
                else:
                    write(sig + morph(spec, spec.dag_hash()), col)
                return

            # Iterate over components using getattr to get next element
            for idx, part in enumerate(parts):
                if not part:
                    raise SpecFormatStringError(
                        'Format string attributes must be non-empty'
                    )
                if part.startswith('_'):
                    raise SpecFormatStringError(
                        'Attempted to format private attribute'
                    )
                else:
                    if isinstance(current, vt.VariantMap):
                        # subscript instead of getattr for variant names
                        current = current[part]
                    else:
                        # aliases
                        if part == 'arch':
                            part = 'architecture'
                        elif part == 'version':
                            # Version requires concrete spec, versions does not
                            # when concrete, they print the same thing
                            part = 'versions'
                        try:
                            current = getattr(current, part)
                        except AttributeError:
                            parent = '.'.join(parts[:idx])
                            m = 'Attempted to format attribute %s.' % attribute
                            m += 'Spec.%s has no attribute %s' % (parent, part)
                            raise SpecFormatStringError(m)
                        if isinstance(current, vn.VersionList):
                            if current == _any_version:
                                # We don't print empty version lists
                                return

                    if callable(current):
                        raise SpecFormatStringError(
                            'Attempted to format callable object'
                        )
                    if not current:
                        # We're not printing anything
                        return

            # Set color codes for various attributes
            col = None
            if 'variants' in parts:
                col = '+'
            elif 'architecture' in parts:
                col = '='
            elif 'compiler' in parts or 'compiler_flags' in parts:
                col = '%'
            elif 'version' in parts:
                col = '@'

            # Finally, write the output
            write(sig + morph(spec, str(current)), col)

        attribute = ''
        in_attribute = False
        escape = False

        for c in format_string:
            if escape:
                out.write(c)
                escape = False
            elif c == '\\':
                escape = True
            elif in_attribute:
                if c == '}':
                    write_attribute(self, attribute, color)
                    attribute = ''
                    in_attribute = False
                else:
                    attribute += c
            else:
                if c == '}':
                    raise SpecFormatStringError(
                        'Encountered closing } before opening {'
                    )
                elif c == '{':
                    in_attribute = True
                else:
                    out.write(c)
        if in_attribute:
            raise SpecFormatStringError(
                'Format string terminated while reading attribute.'
                'Missing terminating }.'
            )

        formatted_spec = out.getvalue()
        return formatted_spec.strip()