-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Join-String cmdlet for creating text from pipeline input #7660
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
Conversation
|
'Object' in the cmdlet name implies that we can join objects of any type - array + array, array + hash, hash + hash, strings and so on. This cmdlet is more like ConvertTo-String |
|
It is intended to be the pipeline equivalent of the |
|
If its going to be the pipeline equivalent of -join wouldn't Join-String be a more descriptive name. Shouldn't there also be a Split-? cmdlet to match the -split operator |
|
It depends. It can join objects, or the properties of objects. The output is a string, but it works with object input. Just like Group-Object and Sort-Object. |
| /// </summary> | ||
| [Cmdlet(VerbsCommon.Join, "Object", RemotingCapability = RemotingCapability.None, DefaultParameterSetName = "default")] | ||
| [OutputType(typeof(string))] | ||
| public class JoinObjectCommand : PSCmdlet |
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.
I think this should be public sealed class.
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
| public class JoinObjectCommand : PSCmdlet | ||
| { | ||
| // ReSharper disable once CollectionNeverQueried.Local | ||
| private readonly List<PSObject> _inputObjects = new List<PSObject>(50); |
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.
I normally don't see List size initialization unless the size is known. I am curious why you initialize the size to this value?
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 cheaper to allocate a list that is a little larger than to reallocate and copy when out-grown.
But the size can always be argued.
When I profile memory allocations, Array allocations on List.Resize is not uncommon, and when I see them, I try to find a sensible default size do get rid of the most common once.
| namespace Microsoft.PowerShell.Commands.Utility | ||
| { | ||
| /// <summary> | ||
| /// Group-Object implementation. |
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.
Join-Object implementation
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.
oops :)
|
|
||
| if (PropertyName == null) | ||
| { | ||
| if (_inputObjects.Count > 0) |
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.
I think you can just check count once and early out or just skip the if/else processing.
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, much better!
src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs
Show resolved
Hide resolved
src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs
Outdated
Show resolved
Hide resolved
src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs
Show resolved
Hide resolved
| public TypeInferenceContext() | ||
| : this(PowerShell.Create(RunspaceMode.CurrentRunspace)) | ||
| { | ||
| _ownsPowerShell = true; |
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.
This doesn't look right to me. This class can own PowerShell/Runspace only if PowerShell is created with Runspace.NewRunspace mode. Otherwise you are disposing a runspace created by someone else (and possibly the thread default runspace).
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.
Good catch!
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.
Reverting the whole file.
|
@PaulHigin Thx for the review. |
| CommandAst commandAst, | ||
| IDictionary fakeBoundParameters) | ||
| { | ||
| var res = new List<CompletionResult>(10); |
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.
Can it be static?
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.
Then you end up with races if used from multiple runspaces.
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 list looks as a const for current session. Do I skip something?
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, it is captured by the local function AddMatching.
So the result is depending on wordToComplete.
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.
I see now. :-) It is Friday. I can not catch up with my idea :-)
I am looking at 'new CompletionResult()'. If they isn't modifed later we could make it static and only reference in target List?
src/Microsoft.PowerShell.Commands.Utility/commands/utility/join-object.cs
Outdated
Show resolved
Hide resolved
test/powershell/Modules/Microsoft.PowerShell.Utility/Join-Object.Tests.ps1
Outdated
Show resolved
Hide resolved
| /// </summary> | ||
| [Parameter(Position = 0)] | ||
| [ArgumentCompleter(typeof(PropertyNameCompleter))] | ||
| public object PropertyName { get; set; } |
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.
Can we use PSPropertyExpression type like in #6934?
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.
PropertyExpression has some extra semantics, handling hashtables like @{n="x";ex={2,3,4}}.
I looked at it, but was not convinced it was a fit. But there's definitely an overlap. Input here is most welcome.
| case "\r": return "`r"; | ||
| case "\n": return "`n"; | ||
| case "\r\n": return "`r`n"; | ||
| default: return Environment.NewLine.Replace("\r", "`r").Replace("\n", "`n"); |
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.
Seems the switch is superfluous. We could leave only
``'c#
return Environment.NewLine.Replace("\r", "r").Replace("\n", "n");
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 it could. I wrote it the way I did to reduce allocated objects. May not be worth it.
The string literals will be loaded from metadata and interned. There will only exist one instance of them. The default line will create two new strings each time.
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.
Environment.NewLine is static. So I think we should use the usual method
public static string NewLineText
{
get
{
#if UNIX
return "`n"
#else
return "`r`n"
#endif
}
}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.
Good point!
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.
But I think MacOS uses `r
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.
No, only very old MacOs versions. See my comment below.
powercode
left a comment
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.
A general question is how we should handle null and AutomationNull.Value in the input?
| CommandAst commandAst, | ||
| IDictionary fakeBoundParameters) | ||
| { | ||
| var res = new List<CompletionResult>(10); |
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, it is captured by the local function AddMatching.
So the result is depending on wordToComplete.
|
Ex.: $null,$null | % { 1 }
1
1
[System.Management.Automation.Internal.AutomationNull]::Value,[System.Management.Automation.Internal.AutomationNull]::Value | % { 1 } |
|
Should it be named |
PaulHigin
left a comment
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.
LGTM
|
I'd be inclined to call it Join-String instead, since nothing of the original objects are preserved (counter to things like Group-Object or Select-Object) |
PreScript to Prefix PostScript to Suffix. PropertyName to Property.
|
I restarted failed test runs; we'll see how they go... |
|
@BrucePay can you please take a look again at this PR? Thanks. |
|
|
||
| /// <summary> | ||
| /// Gets or sets a format string that is applied to each input object. | ||
| /// </summary> |
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.
I'd expand the comment with what is the format and/or add a link on docs.
| [OutputType(typeof(string))] | ||
| public sealed class JoinStringCommand : PSCmdlet | ||
| { | ||
| /// <summary>A bigger default to not get re-allocations in common use cases.</summary> |
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.
Please use file pattern:
/// <Summary>
/// A bigger default to not get re-allocations in common use cases.
/// </Summary> | { | ||
| _outputBuilder.Append(_quoteChar); | ||
| _outputBuilder.Append(stringValue); | ||
| _outputBuilder.Append(_quoteChar); |
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 could use
_outputBuilder.Append(_quoteChar).Append(stringValue).(_quoteChar); | } | ||
| else | ||
| { | ||
| _outputBuilder.AppendFormat(CultureInfo.CurrentCulture, FormatString, stringValue); |
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.
I'd want to get confirmation that this should be CurrentCulture not InvariantCulture.
And maybe discuss -FormatCulture parameter.
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.
Same here. Now sure what is right here.
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.
PowerShell consistently uses the invariant culture in to/from-string conversions, so it should probably be InvariantCulture, at least by default, perhaps with an optional -UseCulture switch (analogous to the *-Csv cmdlets).
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.
As a guideline, we use InvariantCulture when manipulating data so that the behaviour of the code is predictable (invariant) across locales and we use current culture when presenting to the user. This cmdlet could be used in both scenarios but I expect it will be used more for data processing so I suggest defaulting to InvariantCulture with an option to override for current culture.
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.
Adding UseCulture switch as suggested.
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.
gcm -ParameterName Culture
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Compare-Object 3.1.0.0 Microsoft.PowerShell.Utility
Cmdlet Group-Object 3.1.0.0 Microsoft.PowerShell.Utility
Cmdlet New-PSSessionOption 6.0.0.0 Microsoft.PowerShell.Core
Cmdlet Sort-Object 3.1.0.0 Microsoft.PowerShell.UtilityWe already have -Culture parameter. Make sense re-use the name?
Or -UseCurrentCulture? Looks as limited option.
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.
I think -UseCulture is the correct name, after all:
-
The
-Culture <string>parameters you reference:- require an argument, namely a given culture
- are used with cmdlets that are current-culture-sensitive by default, to allow opt-in to a different culture.
-
The
-UseCultureswitch, by contrast:- is used with cmdlets that are culture-invariant by default, to allow opt-in to the current culture (currently applies only to the
*-Csvcmdlets).
- is used with cmdlets that are culture-invariant by default, to allow opt-in to the current culture (currently applies only to the
The latter is what is being implemented here.
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.
@mklement0 Thanks! We need discuss Select-String too.
| get | ||
| { | ||
| #if UNIX | ||
| return Platform.IsMacOS ? "`r" : "`n"; |
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.
Please address the comment.
|
|
||
| It "Should be called using an object as piped without error with no switches" { | ||
| {$testObject | Join-String } | Should -Not -Throw | ||
| } |
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 could remove the test - any test below do the test.
BrucePay
left a comment
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.
LGTM
|
@powercode Looks like this PR still needs a couple of minor updates that are mentioned in @iSazonov's review; especially InvariantCulture issue. Otherwise it seems close to being done. |
|
Sorry - been a bit preoccupied. |
|
I just realized that when joining dates, I always get the "US", Invariant formatting, which is literally never the output a Swede would want. To get the I will add a new commit with this change - feel free to ignore it if you don't think it should go with this PR. |
|
The last commit ( |
|
What is current policy: don't merge until doc issue is created? |
…Shell#7660) The cmdlet syntax is as follows: ``` Join-String [[-Property] <pspropertyexpression>] [[-Separator] <string>] [-OutputPrefix <string>] [-OutputSuffix <string>] [-UseCulture] [-InputObject <psobject>] [<CommonParameters>] Join-String [[-Property] <pspropertyexpression>] [[-Separator] <string>] [-OutputPrefix <string>] [-OutputSuffix <string>] [-SingleQuote] [-UseCulture] [-InputObject <psobject>] [<CommonParameters>] Join-String [[-Property] <pspropertyexpression>] [[-Separator] <string>] [-OutputPrefix <string>] [-OutputSuffix <string>] [-DoubleQuote] [-UseCulture] [-InputObject <psobject>] [<CommonParameters>] Join-String [[-Property] <pspropertyexpression>] [[-Separator] <string>] [-OutputPrefix <string>] [-OutputSuffix <string>] [-FormatString <string>] [-UseCulture] [-InputObject <psobject>] [<CommonParameters>] ```
PR Summary
A join-object cmdlet that joins pipeline input to text.
#6697
As requested by @BrucePay.
Final syntax of the cmdlet:
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:to the beginning of the title and remove the prefix when the PR is ready.[feature]if the change is significant or affects feature tests