question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Named format template parameters

See original GitHub issue

Summary of the new feature/enhancement

Something that would be really useful in PowerShell is a readable, safe, hygienic way to format a template string given a set of key/value pairs.

Today in PowerShell, there are many ways to achieve string formatting, such as:

  • "Hello my name is {0}" -f "Henry"
  • "Hello my name is $Name"
  • [string]::Format("Hello my name is {0}", $Name)
  • Even "Hello my name is " + $Name

However, a scenario I see crop up from time to time is needing to read a template string from some file and wanting to insert values into it in a parameter-like way.

Today this can be done in a few ways, but none is quite ideal:

  1. Use positional format templates. For example:

    $str = '{0} likes the colour {1}'
    $str -f 'Molly','red'
    

    This is both safe (there’s no risk that the template string will have a side-effect) and hygienic (there’s no risk the formatting will replace something not intended to be a parameter), but is not readable; it’s not clear what the author has in mind in the template from {0} and {1}, and when the template is instantiated, the values "Molly", and "red" are again decontextualised – we have no idea how they’re being used. So the greater the distance/abstraction between the template and its parameters, the harder the script is to reason about. Two more points detract here:

    • Multiple uses of {0} are unclear from both the template and instantiation perspective (hard to keep track of parameters)
    • Heterogeneous templates, for example where some use all parameters and others don’t, are hard to manage like this
  2. Use string replacement. For example:

    $str = 'VAR_NAME likes the colour VAR_COLOR'
    ($str -replace 'VAR_NAME','Molly') -replace 'VAR_COLOR','red'
    

    This is more readable, and still safe, but:

    • It’s not hygienic, consider if the variable was just called COLOUR or if VAR_NAME were replaced with VAR_COLOR. In general there’s no system of syntax at work to ensure that parts of the string aren’t intentionally replaced.
    • It’s not ergonomic; for each variable we must use a new -replace expression
    • It’s inefficient; each variable causes a new string to be allocated, when we could really do all of this in one pass
  3. Use PowerShell variables. For example:

    $str = '$name likes the colour $color'
    & { $name = 'Molly'; $color = 'red'; Invoke-Expression $str }
    

    This is both hygienic and readable, since it reuses PowerShell’s own variable-driven string expansion concepts to drive the template, but:

    • It’s not at all safe, since it executes the template string as given. This may cause arbitrary code execution (from within a $(...) subexpression). So if the template is not trusted, then this cannot be used.
    • It’s not efficient; we must execute a PowerShell pipeline to do a simple string template instantiation
    • It’s not ergonomic to do in a clean way; we are forced to instantiate variables in the calling scope, which is why I invoke it in a new scriptblock in my example, so our parameters don’t leak into the wider context

Proposed technical implementation details (optional)

Instead of these options, I think Python sets an excellent example for string formatting. In particular:

params = {
    'purpose': 'find the Holy Grail!'
}

"My quest is to {purpose}".format(**params)

Here the template string can be specified in a way that makes it easily understood in the absence of concrete parameters, while the parameters can listed in a convenient and readable order. There’s also a simple correspondence here between the general concept of splatting and named parameters in template strings.

In PowerShell today, we support template strings with positional parameters supplied as an array:

"My quest is to {0}" -f "find the Holy Grail!"

I think it’s a logical extension for -f to accept a hashtable:

"My quest is to {purpose}" -f @{
    purpose = "find the Holy Grail!"
}

Just to motivate this a bit further, the reason I opened this issue is that I was confronted with a heterogeneous list of templates stored in JSON, of which some accept different parameters to others, or accept the same parameters in different order. Moreover I could imagine new entries being added that need a different format, which would make things less convenient again with positional parameters:

[
    {
        // Other fields...
        "template": "{packageName}_{release}-1.debian.9_amd64.deb"
    },
    {
        "template": "{packageName}_lts_{release}_{sku}..."
    }
]

In such a scenario, I would love to simply parameterise the strings as above so I can do something like:

Get-Content -Raw ./packages.json |
    ConvertFrom-Json |
    ForEach-Object { $_.template -f @{ packageName = "powershell"; release = "7.2"; sku = "normal"; ... } }

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:5
  • Comments:29 (9 by maintainers)

github_iconTop GitHub Comments

4reactions
mklement0commented, Sep 23, 2020

The C# 6+ interpolated strings (e.g, $"Expand token {name}") are the direct equivalent of PowerShell’s expandable strings, only with different syntax.

The proposal in https://github.com/dotnet/runtime/issues/20317, which was rejected (“String.Format is already a huge source of bugs and performance issues and adding more layers is not something we really want to do. I like the general idea of a basic templating system but I think it would be better served as a higher level concept not directly on string.”) would have been the equivalent of what we already have, namely in $ExecutionContext.InvokeCommand.ExpandString(), which #11693 proposes surfacing in a friendlier - and safer - manner as a cmdlet: that is, you craft a verbatim string as you normally would an expandable one, and later expand it on demand, with the then-current values of the variables referenced and / or output from the embedded expressions.

Of course, just like -f and expandable strings happily coexist currently, with different syntax forms, there’s no reason not to implement both #11693 and the hashtable extension to -f proposed here.

Given that the proposed -f extension would be a strict superset of the .NET String.Format() method, I think it is conceptually unproblematic, as long as the relationship is clearly documented.

3reactions
rjmholtcommented, Sep 23, 2020

@jhoneill good summary.

-f is AIUI a wrapper for [string]::format so the idea of allowing it to take a hash table, while a good one, is probably better done with a new operator.

In an ideal world, I don’t think anyone using PowerShell should care about the implementation details for an operator, and I think a hashtable does map nicely, conceptually speaking. With that said, it probably introduces an opportunity for us to break something or otherwise clobber an underlying .NET functionality, which I think PowerShell has done somewhat badly in the past.

{placeholder} already comes unstuck if you are trying to insert things into, say, JSON text

Yeah, any templating scheme is going to have some kind of input that collides with its syntax, which is why it must have an escaping mechanism. The nice thing about the existing -f positional template syntax is that it already covers both the template syntax and how to escape it, so it’s only one extra step to introduce naming, rather than teaching people a new operator and possibly a new syntax/mini-language underneath it.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Named Placeholders in String Formatting
Learn how to replace parameters in template-based strings from a set of values.
Read more >
format string template with named parameters to literal c# - ...
I have an application that creates string templates with named variables. This is done in accordance to the logging guide for ASP.NET Core....
Read more >
Python String Formatting Best Practices
Learn the four main approaches to string formatting in Python, as well as their strengths and weaknesses. You'll also get a simple rule...
Read more >
Named String Formatting - Getting Help - Go Forum
Hi Folks, I wanted to interpolate string and get the formatted output using named place holders. The requirement is somewhat similar to ...
Read more >
Project and item template parameters - Visual Studio
Template parameters are declared in the format $parameter$. For example: $rootnamespace$. $guid1$. $guid5$. Enable parameter substitution in ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found