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
Conversation
@tgamblin ready for review |
d9a4c48
to
3fe9f01
Compare
@tgamblin ping |
Switch to new spec.format method throughout internals
3fe9f01
to
064483a
Compare
0bf4a05
to
b77fa69
Compare
There was a problem hiding this 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}' |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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}' |
There was a problem hiding this comment.
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}' |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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}')) |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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.
# Set color codes for various attributes | ||
col = None | ||
if 'variants' in parts: | ||
col = '+' |
There was a problem hiding this comment.
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.
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:
Some additional special-case properties can be added:
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:The
@
sigil may also be used for any other property namedversion
. 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 asname=value
. To print variant names or values independently, usespec.format('{variants.<name>.name}')
orspec.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. Theformat
method will callold_format
if the character$
appears un-escaped in the format string.Args:
Keyword Args: