Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features: Improve Spec format strings #10556

Merged
merged 5 commits into from Apr 18, 2019
Merged

Conversation

becker33
Copy link
Member

@becker33 becker33 commented Feb 8, 2019

The old spec.format method had two separate syntaxes. One was concise but very hard to read ('$_$@$%@+$+$=' would be an example). The other was more verbose, but the formatting names were not aligned with the variable names on the spec, which made it unnecessarily hard on users. Additionally, it had unnecessary $-signs on all of the format specifiers.

This PR replaces the old spec.format method with a new one with a single, self-documenting syntax that revolves around the names of the spec attributes.

From the new spec.format documentation:

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 un-escaped 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

@becker33
Copy link
Member Author

@tgamblin ready for review

@becker33 becker33 force-pushed the features/improve-format-strings branch from d9a4c48 to 3fe9f01 Compare February 11, 2019 21:59
@becker33
Copy link
Member Author

@tgamblin ping

@becker33 becker33 force-pushed the features/improve-format-strings branch from 3fe9f01 to 064483a Compare April 16, 2019 20:50
Copy link
Member

@tgamblin tgamblin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@becker33: two minor change requests, but otherwise I don't think I've been quite as satisfied looking over changes in a PR in recent memory. Every single change in here makes the code more readable. Looks awesome to me.

Can you address the r""" and color-map changes? Then let's get this in.

@@ -48,7 +48,7 @@ the installation paths. For example
.. code-block:: yaml

config:
install_path_scheme: '${PACKAGE}/${VERSION}/${HASH:7}'
install_path_scheme: '{name}/{version}/{hash:7}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so much more readable

@@ -349,16 +349,16 @@ def __init__(self, plat=None, os=None, target=None):
def concrete(self):
return all((self.platform is not None,
isinstance(self.platform, Platform),
self.platform_os is not None,
isinstance(self.platform_os, OperatingSystem),
self.os is not None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omg SO satisfying to be rid of platform_

args = ["%s matches multiple packages." % spec,
"Matching packages:"]
args += [colorize(" @K{%s} " % s.dag_hash(7)) +
s.cformat('$_$@$%@$=') for s in matching_specs]
s.cformat(format_string) for s in matching_specs]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, ok, it is fairly clear that my original format strings were horrendous. 🙈

@@ -42,7 +42,8 @@ def dependencies(parser, args):
env = ev.get_env(args, 'dependencies')
spec = spack.cmd.disambiguate_spec(specs[0], env)

tty.msg("Dependencies of %s" % spec.format('$_$@$%@$/', color=True))
format_string = '{name}{@version}{%compiler}{/hash:7}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code, it does not swear at me anymore. 😄

@@ -344,7 +344,9 @@ def modules_cmd(parser, args, module_type, callbacks=callbacks):
except MultipleSpecsMatch:
msg = "the constraint '{query}' matches multiple packages:\n"
for s in specs:
msg += '\t' + s.cformat(format_string='$/ $_$@$+$%@+$+$=') + '\n'
spec_fmt = '{hash:7} {name}{@version}{%compiler}'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow. So much regret for creating those prior format strings as I look at this. The horror.

layout_package7 = YamlDirectoryLayout(str(tmpdir),
path_scheme=scheme_package7)
path_package7 = layout_package7.relative_path_for_spec(spec)

assert(package7 == path_package7)

# Test separation of architecture
arch_scheme_package = "${PLATFORM}/${TARGET}/${OS}/${PACKAGE}/${VERSION}/${HASH:7}" # NOQA: ignore=E501
arch_scheme_package = "{architecture.platform}/{architecture.target}/{architecture.os}/{name}/{version}/{hash:7}" # NOQA: ignore=E501
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might consider allowing architecture to be just arch relatively soon here. Or to allow all of these to be top-level attributes of the spec (i.e., just plain old os, platform, target instead of architecture.os, etc.) This format string gets a bit tedious.

Not a change that needs to be made immediately, but I think it would be beneficial.

libelf_hash, libelf_lbl = (
s['libelf'].dag_hash(), s['libelf'].format('$_$/'))
s['libelf'].dag_hash(), s['libelf'].format('{name}{/hash:7}'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having some sensible default for, e.g., hash:short might be good eventually -- I don't think it needs to change here, but I don't like the number of places we have to write 7.

@@ -3010,10 +3022,245 @@ def _cmp_key(self):
def colorized(self):
return colorize_spec(self)

def format(self, format_string='$_$@$%@+$+$=', **kwargs):
def format(self, format_string=default_format, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So much yes in this one change.

lib/spack/spack/spec.py Outdated Show resolved Hide resolved
# Set color codes for various attributes
col = None
if 'variants' in parts:
col = '+'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is cleaner at this point to just add extra keys to the dict -- i.e., map 'variants' to the same color as '+' in the map above rather than polluting the already long format() method with this logic.

@tgamblin tgamblin merged commit f242f5f into develop Apr 18, 2019
@tgamblin tgamblin deleted the features/improve-format-strings branch April 18, 2019 04:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants