GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Add cmdlet 'Join-String' for creating text from pipeline input (#7660) · PowerShell/PowerShell@877b9a9

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • src
    • Microsoft.PowerShell.Commands.Utility/commands/utility
      • join-string.cs
    • Modules
      • Unix/Microsoft.PowerShell.Utility
        • Microsoft.PowerShell.Utility.psd1
      • Windows/Microsoft.PowerShell.Utility
        • Microsoft.PowerShell.Utility.psd1
    • System.Management.Automation/engine
      • CommandCompletion
        • CompletionCompleters.cs
      • LanguagePrimitives.cs
      • MshObject.cs
      • parser
        • Compiler.cs
  • test/powershell
    • Modules/Microsoft.PowerShell.Utility
      • Join-String.Tests.ps1
    • engine/Basic
      • DefaultCommands.Tests.ps1
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/Microsoft.PowerShell.Commands.Utility/commands/utility/join-string.cs‎

Copy file name to clipboard
+226Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using System.Globalization;
8+
using System.Linq;
9+
using System.Management.Automation;
10+
using System.Management.Automation.Internal;
11+
using System.Management.Automation.Language;
12+
using System.Text;
13+
14+
namespace Microsoft.PowerShell.Commands.Utility
15+
{
16+
/// <summary>
17+
/// Join-Object implementation.
18+
/// </summary>
19+
[Cmdlet(VerbsCommon.Join, "String", RemotingCapability = RemotingCapability.None, DefaultParameterSetName = "default")]
20+
[OutputType(typeof(string))]
21+
public sealed class JoinStringCommand : PSCmdlet
22+
{
23+
/// <summary>A bigger default to not get re-allocations in common use cases.</summary>
24+
private const int DefaultOutputStringCapacity = 256;
25+
private readonly StringBuilder _outputBuilder = new StringBuilder(DefaultOutputStringCapacity);
26+
private CultureInfo _cultureInfo = CultureInfo.InvariantCulture;
27+
private string _separator;
28+
private char _quoteChar;
29+
private bool _firstInputObject = true;
30+
31+
/// <summary>
32+
/// Gets or sets the property name or script block to use as the value to join.
33+
/// </summary>
34+
[Parameter(Position = 0)]
35+
[ArgumentCompleter(typeof(PropertyNameCompleter))]
36+
public PSPropertyExpression Property { get; set; }
37+
38+
/// <summary>
39+
/// Gets or sets the delimiter to join the output with.
40+
/// </summary>
41+
[Parameter(Position = 1)]
42+
[ArgumentCompleter(typeof(JoinItemCompleter))]
43+
[AllowEmptyString]
44+
public string Separator
45+
{
46+
get => _separator ?? LanguagePrimitives.ConvertTo<string>(GetVariableValue("OFS"));
47+
set => _separator = value;
48+
}
49+
50+
/// <summary>
51+
/// Gets or sets text to include before the joined input text.
52+
/// </summary>
53+
[Parameter]
54+
[Alias("op")]
55+
public string OutputPrefix { get; set; }
56+
57+
/// <summary>
58+
/// Gets or sets text to include after the joined input text.
59+
/// </summary>
60+
[Parameter]
61+
[Alias("os")]
62+
public string OutputSuffix { get; set; }
63+
64+
/// <summary>
65+
/// Gets or sets if the output items should we wrapped in single quotes.
66+
/// </summary>
67+
[Parameter(ParameterSetName = "SingleQuote")]
68+
public SwitchParameter SingleQuote { get; set; }
69+
70+
/// <summary>
71+
/// Gets or sets if the output items should we wrapped in double quotes.
72+
/// </summary>
73+
[Parameter(ParameterSetName = "DoubleQuote")]
74+
public SwitchParameter DoubleQuote { get; set; }
75+
76+
/// <summary>
77+
/// Gets or sets a format string that is applied to each input object.
78+
/// </summary>
79+
[Parameter(ParameterSetName = "Format")]
80+
[ArgumentCompleter(typeof(JoinItemCompleter))]
81+
public string FormatString { get; set; }
82+
83+
/// <summary>
84+
/// Gets or sets if the current culture should be used with formatting instead of the invariant culture.
85+
/// </summary>
86+
[Parameter]
87+
public SwitchParameter UseCulture { get; set; }
88+
89+
/// <summary>
90+
/// Gets or sets the input object to join into text.
91+
/// </summary>
92+
[Parameter(ValueFromPipeline = true)]
93+
public PSObject InputObject { get; set; }
94+
95+
/// <inheritdoc />
96+
protected override void BeginProcessing()
97+
{
98+
_quoteChar = SingleQuote ? '\'' : DoubleQuote ? '"' : char.MinValue;
99+
_outputBuilder.Append(OutputPrefix);
100+
if (UseCulture)
101+
{
102+
_cultureInfo = CultureInfo.CurrentCulture;
103+
}
104+
}
105+
106+
/// <inheritdoc />
107+
protected override void ProcessRecord()
108+
{
109+
if (InputObject != null && InputObject != AutomationNull.Value)
110+
{
111+
var inputValue = Property == null
112+
? InputObject
113+
: Property.GetValues(InputObject, false, true).FirstOrDefault()?.Result;
114+
115+
// conversion to string always succeeds.
116+
if (!LanguagePrimitives.TryConvertTo<string>(inputValue, _cultureInfo, out var stringValue))
117+
{
118+
throw new PSInvalidCastException("InvalidCastFromAnyTypeToString", ExtendedTypeSystem.InvalidCastCannotRetrieveString, null);
119+
}
120+
121+
if (_firstInputObject)
122+
{
123+
_firstInputObject = false;
124+
}
125+
else
126+
{
127+
_outputBuilder.Append(Separator);
128+
}
129+
130+
if (_quoteChar != char.MinValue)
131+
{
132+
_outputBuilder.Append(_quoteChar);
133+
_outputBuilder.Append(stringValue);
134+
_outputBuilder.Append(_quoteChar);
135+
}
136+
else if (string.IsNullOrEmpty(FormatString))
137+
{
138+
_outputBuilder.Append(stringValue);
139+
}
140+
else
141+
{
142+
_outputBuilder.AppendFormat(_cultureInfo, FormatString, stringValue);
143+
}
144+
}
145+
}
146+
147+
/// <inheritdoc />
148+
protected override void EndProcessing()
149+
{
150+
_outputBuilder.Append(OutputSuffix);
151+
WriteObject(_outputBuilder.ToString());
152+
}
153+
}
154+
155+
internal class JoinItemCompleter : IArgumentCompleter
156+
{
157+
public IEnumerable<CompletionResult> CompleteArgument(
158+
string commandName,
159+
string parameterName,
160+
string wordToComplete,
161+
CommandAst commandAst,
162+
IDictionary fakeBoundParameters)
163+
{
164+
switch (parameterName)
165+
{
166+
case "Separator": return CompleteSeparator(wordToComplete);
167+
case "FormatString": return CompleteFormatString(wordToComplete);
168+
}
169+
170+
return null;
171+
}
172+
173+
private IEnumerable<CompletionResult> CompleteFormatString(string wordToComplete)
174+
{
175+
var res = new List<CompletionResult>();
176+
void AddMatching(string completionText)
177+
{
178+
if (completionText.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase))
179+
{
180+
res.Add(new CompletionResult(completionText));
181+
}
182+
}
183+
184+
AddMatching("'[{0}]'");
185+
AddMatching("'{0:N2}'");
186+
AddMatching("\"`r`n `${0}\"");
187+
AddMatching("\"`r`n [string] `${0}\"");
188+
189+
return res;
190+
}
191+
192+
private IEnumerable<CompletionResult> CompleteSeparator(string wordToComplete)
193+
{
194+
var res = new List<CompletionResult>(10);
195+
196+
void AddMatching(string completionText, string listText, string toolTip)
197+
{
198+
if (completionText.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase))
199+
{
200+
res.Add(new CompletionResult(completionText, listText, CompletionResultType.ParameterValue, toolTip));
201+
}
202+
}
203+
204+
AddMatching("', '", "Comma-Space", "', ' - Comma-Space");
205+
AddMatching("';'", "Semi-Colon", "';' - Semi-Colon ");
206+
AddMatching("'; '", "Semi-Colon-Space", "'; ' - Semi-Colon-Space");
207+
AddMatching($"\"{NewLineText}\"", "Newline", $"{NewLineText} - Newline");
208+
AddMatching("','", "Comma", "',' - Comma");
209+
AddMatching("'-'", "Dash", "'-' - Dash");
210+
AddMatching("' '", "Space", "' ' - Space");
211+
return res;
212+
}
213+
214+
public string NewLineText
215+
{
216+
get
217+
{
218+
#if UNIX
219+
return "`n";
220+
#else
221+
return "`r`n";
222+
#endif
223+
}
224+
}
225+
}
226+
}
Collapse file

‎src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1‎

Copy file name to clipboardExpand all lines: src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide",
1414
"Export-Csv", "Import-Csv", "ConvertTo-Csv", "ConvertFrom-Csv", "Export-Alias", "Invoke-Expression",
1515
"Get-Alias", "Get-Culture", "Get-Date", "Get-Host", "Get-Member", "Get-Random", "Get-UICulture",
1616
"Get-Unique", "Export-PSSession", "Import-PSSession", "Import-Alias", "Import-LocalizedData",
17-
"Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date",
17+
"Join-String", "Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date",
1818
"Start-Sleep", "Tee-Object", "Measure-Command", "Update-TypeData", "Update-FormatData",
1919
"Remove-TypeData", "Get-TypeData", "Write-Host", "Write-Progress", "New-Object", "Select-Object",
2020
"Group-Object", "Sort-Object", "Get-Variable", "New-Variable", "Set-Variable", "Remove-Variable",
Collapse file

‎src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1‎

Copy file name to clipboardExpand all lines: src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide",
1414
"Export-Csv", "Import-Csv", "ConvertTo-Csv", "ConvertFrom-Csv", "Export-Alias", "Invoke-Expression",
1515
"Get-Alias", "Get-Culture", "Get-Date", "Get-Host", "Get-Member", "Get-Random", "Get-UICulture",
1616
"Get-Unique", "Export-PSSession", "Import-PSSession", "Import-Alias", "Import-LocalizedData",
17-
"Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date",
17+
"Join-String", "Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date",
1818
"Start-Sleep", "Tee-Object", "Measure-Command", "Update-TypeData", "Update-FormatData",
1919
"Remove-TypeData", "Get-TypeData", "Write-Host", "Write-Progress", "New-Object", "Select-Object",
2020
"Group-Object", "Sort-Object", "Get-Variable", "New-Variable", "Set-Variable", "Remove-Variable",
Collapse file

‎src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs‎

Copy file name to clipboardExpand all lines: src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs
+83-7Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3746,7 +3746,7 @@ private static void NativeCompletionMemberName(CompletionContext context, List<C
37463746
prevType = AstTypeInference.InferTypeOf(pipelineAst.PipelineElements[i - 1], context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval);
37473747
}
37483748

3749-
CompleteMemberByInferredType(context, prevType, result, context.WordToComplete + "*", filter: IsPropertyMember, isStatic: false);
3749+
CompleteMemberByInferredType(context.TypeInferenceContext, prevType, result, context.WordToComplete + "*", filter: IsPropertyMember, isStatic: false);
37503750
result.Add(CompletionResult.Null);
37513751
}
37523752

@@ -5027,7 +5027,7 @@ internal static List<CompletionResult> CompleteMember(CompletionContext context,
50275027
if (inferredTypes != null && inferredTypes.Length > 0)
50285028
{
50295029
// Use inferred types if we have any
5030-
CompleteMemberByInferredType(context, inferredTypes, results, memberName, filter: null, isStatic: @static);
5030+
CompleteMemberByInferredType(context.TypeInferenceContext, inferredTypes, results, memberName, filter: null, isStatic: @static);
50315031
}
50325032
else
50335033
{
@@ -5131,7 +5131,7 @@ private static bool IsInDscContext(ExpressionAst expression)
51315131
return Ast.GetAncestorAst<ConfigurationDefinitionAst>(expression) != null;
51325132
}
51335133

5134-
private static void CompleteMemberByInferredType(CompletionContext context, IEnumerable<PSTypeName> inferredTypes, List<CompletionResult> results, string memberName, Func<object, bool> filter, bool isStatic)
5134+
internal static void CompleteMemberByInferredType(TypeInferenceContext context, IEnumerable<PSTypeName> inferredTypes, List<CompletionResult> results, string memberName, Func<object, bool> filter, bool isStatic)
51355135
{
51365136
bool extensionMethodsAdded = false;
51375137
HashSet<string> typeNameUsed = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
@@ -5142,8 +5142,9 @@ private static void CompleteMemberByInferredType(CompletionContext context, IEnu
51425142
{
51435143
continue;
51445144
}
5145+
51455146
typeNameUsed.Add(psTypeName.Name);
5146-
var members = context.TypeInferenceContext.GetMembersByInferredType(psTypeName, isStatic, filter);
5147+
var members = context.GetMembersByInferredType(psTypeName, isStatic, filter);
51475148
foreach (var member in members)
51485149
{
51495150
AddInferredMember(member, memberNamePattern, results);
@@ -5276,7 +5277,7 @@ private static bool IsWriteablePropertyMember(object member)
52765277
return false;
52775278
}
52785279

5279-
private static bool IsPropertyMember(object member)
5280+
internal static bool IsPropertyMember(object member)
52805281
{
52815282
return member is PropertyInfo
52825283
|| member is FieldInfo
@@ -6154,7 +6155,7 @@ internal static List<CompletionResult> CompleteHashtableKey(CompletionContext co
61546155
{
61556156
var result = new List<CompletionResult>();
61566157
CompleteMemberByInferredType(
6157-
completionContext, AstTypeInference.InferTypeOf(typeAst, completionContext.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval),
6158+
completionContext.TypeInferenceContext, AstTypeInference.InferTypeOf(typeAst, completionContext.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval),
61586159
result, completionContext.WordToComplete + "*", IsWriteablePropertyMember, isStatic: false);
61596160
return result;
61606161
}
@@ -6265,7 +6266,7 @@ internal static List<CompletionResult> CompleteHashtableKey(CompletionContext co
62656266
var inferredType = AstTypeInference.InferTypeOf(commandAst, completionContext.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval);
62666267
var result = new List<CompletionResult>();
62676268
CompleteMemberByInferredType(
6268-
completionContext, inferredType,
6269+
completionContext.TypeInferenceContext, inferredType,
62696270
result, completionContext.WordToComplete + "*", IsWriteablePropertyMember, isStatic: false);
62706271
return result;
62716272
case "Select-Object":
@@ -6910,4 +6911,79 @@ public object VisitParenExpression(ParenExpressionAst parenExpressionAst)
69106911
return parenExpressionAst.Pipeline.Accept(this);
69116912
}
69126913
}
6914+
6915+
/// <summary>
6916+
/// Completes with the property names of the InputObject.
6917+
/// </summary>
6918+
internal class PropertyNameCompleter : IArgumentCompleter
6919+
{
6920+
private readonly string _parameterNameOfInput;
6921+
6922+
/// <summary>
6923+
/// Initializes a new instance of the <see cref="PropertyNameCompleter"/> class.
6924+
/// </summary>
6925+
public PropertyNameCompleter()
6926+
{
6927+
_parameterNameOfInput = "InputObject";
6928+
}
6929+
6930+
/// <summary>
6931+
/// Initializes a new instance of the <see cref="PropertyNameCompleter"/> class.
6932+
/// </summary>
6933+
/// <param name="parameterNameOfInput">The name of the property of the input object for witch to complete with property names.</param>
6934+
public PropertyNameCompleter(string parameterNameOfInput)
6935+
{
6936+
_parameterNameOfInput = parameterNameOfInput;
6937+
}
6938+
6939+
IEnumerable<CompletionResult> IArgumentCompleter.CompleteArgument(
6940+
string commandName,
6941+
string parameterName,
6942+
string wordToComplete,
6943+
CommandAst commandAst,
6944+
IDictionary fakeBoundParameters)
6945+
{
6946+
if (!(commandAst.Parent is PipelineAst pipelineAst))
6947+
{
6948+
return null;
6949+
}
6950+
6951+
int i;
6952+
for (i = 0; i < pipelineAst.PipelineElements.Count; i++)
6953+
{
6954+
if (pipelineAst.PipelineElements[i] == commandAst)
6955+
{
6956+
break;
6957+
}
6958+
}
6959+
6960+
var typeInferenceContext = new TypeInferenceContext();
6961+
IEnumerable<PSTypeName> prevType;
6962+
if (i == 0)
6963+
{
6964+
var parameterAst = (CommandParameterAst)commandAst.Find(ast => ast is CommandParameterAst cpa && cpa.ParameterName == "PropertyName", false);
6965+
var pseudoBinding = new PseudoParameterBinder().DoPseudoParameterBinding(commandAst, null, parameterAst, PseudoParameterBinder.BindingType.ParameterCompletion);
6966+
if (!pseudoBinding.BoundArguments.TryGetValue(_parameterNameOfInput, out var pair) || !pair.ArgumentSpecified)
6967+
{
6968+
return null;
6969+
}
6970+
6971+
if (pair is AstPair astPair && astPair.Argument != null)
6972+
{
6973+
prevType = AstTypeInference.InferTypeOf(astPair.Argument, typeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval);
6974+
}
6975+
6976+
return null;
6977+
}
6978+
else
6979+
{
6980+
prevType = AstTypeInference.InferTypeOf(pipelineAst.PipelineElements[i - 1], typeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval);
6981+
}
6982+
6983+
var result = new List<CompletionResult>();
6984+
6985+
CompletionCompleters.CompleteMemberByInferredType(typeInferenceContext, prevType, result, wordToComplete + "*", filter: CompletionCompleters.IsPropertyMember, isStatic: false);
6986+
return result;
6987+
}
6988+
}
69136989
}
Collapse file

‎src/System.Management.Automation/engine/LanguagePrimitives.cs‎

Copy file name to clipboardExpand all lines: src/System.Management.Automation/engine/LanguagePrimitives.cs
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3244,7 +3244,7 @@ private static string ConvertNonNumericToString(object valueToConvert,
32443244
try
32453245
{
32463246
typeConversion.WriteLine("Converting object to string.");
3247-
return PSObject.ToStringParser(ecFromTLS, valueToConvert);
3247+
return PSObject.ToStringParser(ecFromTLS, valueToConvert, formatProvider);
32483248
}
32493249
catch (ExtendedTypeSystemException e)
32503250
{

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.