Skip to content

Commit 7333f10

Browse files
tablackburnclaude
andcommitted
Refactor ArgumentCompleters to use testable helper functions
Extract duplicated quote handling and CompletionResult creation logic from 15 public functions into two new private helper functions: - ConvertFrom-PatCompleterInput: Parses ArgumentCompleter input, extracting leading quote characters and returning stripped word - New-PatCompletionResult: Creates properly-quoted CompletionResult objects with support for custom tooltips and list item text This refactoring: - Removes ~430 lines of duplicated code across public functions - Adds 46 unit tests for the extracted logic (100% testable) - Standardizes variable naming ($getParameters instead of $getParams) - Consolidates duplicate if/else branches for ServerUri handling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d5f84c8 commit 7333f10

19 files changed

Lines changed: 734 additions & 641 deletions
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
function ConvertFrom-PatCompleterInput {
2+
<#
3+
.SYNOPSIS
4+
Parses ArgumentCompleter input, extracting quote characters and the stripped word.
5+
6+
.DESCRIPTION
7+
ArgumentCompleters receive $wordToComplete which may include leading quote characters
8+
(single or double). This function extracts the quote character (if present) and
9+
returns the stripped word for matching, along with the quote char for proper result formatting.
10+
11+
.PARAMETER WordToComplete
12+
The word being completed, as passed to the ArgumentCompleter.
13+
14+
.OUTPUTS
15+
PSCustomObject with properties:
16+
- QuoteChar: The leading quote character (empty string if none)
17+
- StrippedWord: The word with leading quote removed
18+
19+
.EXAMPLE
20+
ConvertFrom-PatCompleterInput -WordToComplete "Movies"
21+
# Returns: @{ QuoteChar = ''; StrippedWord = 'Movies' }
22+
23+
.EXAMPLE
24+
ConvertFrom-PatCompleterInput -WordToComplete "'My Library"
25+
# Returns: @{ QuoteChar = "'"; StrippedWord = 'My Library' }
26+
27+
.EXAMPLE
28+
ConvertFrom-PatCompleterInput -WordToComplete '"Action Movies'
29+
# Returns: @{ QuoteChar = '"'; StrippedWord = 'Action Movies' }
30+
#>
31+
[CmdletBinding()]
32+
[OutputType([PSCustomObject])]
33+
param (
34+
[Parameter(Mandatory = $false)]
35+
[AllowEmptyString()]
36+
[string]
37+
$WordToComplete = ''
38+
)
39+
40+
$quoteChar = ''
41+
$strippedWord = $WordToComplete
42+
43+
if ($WordToComplete -match "^([`"'])(.*)$") {
44+
$quoteChar = $Matches[1]
45+
$strippedWord = $Matches[2]
46+
}
47+
48+
[PSCustomObject]@{
49+
QuoteChar = $quoteChar
50+
StrippedWord = $strippedWord
51+
}
52+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
function New-PatCompletionResult {
2+
<#
3+
.SYNOPSIS
4+
Creates a CompletionResult with proper quoting for values containing spaces.
5+
6+
.DESCRIPTION
7+
Builds a System.Management.Automation.CompletionResult object with intelligent quoting.
8+
If the original input had a quote character, the result preserves that quoting style.
9+
If the value contains spaces and no quote was specified, single quotes are added.
10+
11+
.PARAMETER Value
12+
The completion value (e.g., library name, collection title).
13+
14+
.PARAMETER QuoteChar
15+
Optional quote character from the original input. If provided, the completion
16+
text will be wrapped with this character.
17+
18+
.PARAMETER ToolTip
19+
Optional tooltip to display. Defaults to the Value if not specified.
20+
21+
.PARAMETER ListItemText
22+
Optional text to display in the completion list. Defaults to the Value if not specified.
23+
24+
.OUTPUTS
25+
System.Management.Automation.CompletionResult
26+
27+
.EXAMPLE
28+
New-PatCompletionResult -Value 'Movies'
29+
# Creates completion with text 'Movies' (no quotes needed)
30+
31+
.EXAMPLE
32+
New-PatCompletionResult -Value 'Action Movies'
33+
# Creates completion with text "'Action Movies'" (auto-quoted for spaces)
34+
35+
.EXAMPLE
36+
New-PatCompletionResult -Value 'Action Movies' -QuoteChar '"'
37+
# Creates completion with text '"Action Movies"' (preserves double quotes)
38+
39+
.EXAMPLE
40+
New-PatCompletionResult -Value '12345' -ToolTip 'Movies (ID: 12345)'
41+
# Creates completion with custom tooltip
42+
#>
43+
[CmdletBinding()]
44+
[OutputType([System.Management.Automation.CompletionResult])]
45+
param (
46+
[Parameter(Mandatory = $true)]
47+
[ValidateNotNullOrEmpty()]
48+
[string]
49+
$Value,
50+
51+
[Parameter(Mandatory = $false)]
52+
[AllowEmptyString()]
53+
[string]
54+
$QuoteChar = '',
55+
56+
[Parameter(Mandatory = $false)]
57+
[string]
58+
$ToolTip,
59+
60+
[Parameter(Mandatory = $false)]
61+
[string]
62+
$ListItemText
63+
)
64+
65+
$tooltip = if ($ToolTip) { $ToolTip } else { $Value }
66+
$listItem = if ($ListItemText) { $ListItemText } else { $Value }
67+
68+
# Determine completion text with proper quoting
69+
if ($QuoteChar) {
70+
$text = "$QuoteChar$Value$QuoteChar"
71+
}
72+
elseif ($Value -match '\s') {
73+
$text = "'$Value'"
74+
}
75+
else {
76+
$text = $Value
77+
}
78+
79+
[System.Management.Automation.CompletionResult]::new(
80+
$text,
81+
$listItem,
82+
'ParameterValue',
83+
$tooltip
84+
)
85+
}

PlexAutomationToolkit/Public/Add-PatCollectionItem.ps1

Lines changed: 14 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -92,48 +92,27 @@ function Add-PatCollectionItem {
9292
[ArgumentCompleter({
9393
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
9494

95-
$quoteChar = ''
96-
$strippedWord = $wordToComplete
97-
if ($wordToComplete -match "^([`"'])(.*)$") {
98-
$quoteChar = $Matches[1]
99-
$strippedWord = $Matches[2]
100-
}
95+
$completerInput = ConvertFrom-PatCompleterInput -WordToComplete $wordToComplete
10196

102-
$getParams = @{ ErrorAction = 'SilentlyContinue' }
97+
$getParameters = @{ ErrorAction = 'SilentlyContinue' }
10398
if ($fakeBoundParameters.ContainsKey('ServerUri')) {
104-
$getParams['ServerUri'] = $fakeBoundParameters['ServerUri']
99+
$getParameters['ServerUri'] = $fakeBoundParameters['ServerUri']
105100
}
106101
if ($fakeBoundParameters.ContainsKey('LibraryName')) {
107-
$getParams['LibraryName'] = $fakeBoundParameters['LibraryName']
102+
$getParameters['LibraryName'] = $fakeBoundParameters['LibraryName']
108103
}
109104
elseif ($fakeBoundParameters.ContainsKey('LibraryId')) {
110-
$getParams['LibraryId'] = $fakeBoundParameters['LibraryId']
105+
$getParameters['LibraryId'] = $fakeBoundParameters['LibraryId']
111106
}
112107
else {
113108
return
114109
}
115110

116-
$collections = Get-PatCollection @getParams
111+
$collections = Get-PatCollection @getParameters
117112

118113
foreach ($collection in $collections) {
119-
if ($collection.Title -ilike "$strippedWord*") {
120-
$title = $collection.Title
121-
if ($quoteChar) {
122-
$text = "$quoteChar$title$quoteChar"
123-
}
124-
elseif ($title -match '\s') {
125-
$text = "'$title'"
126-
}
127-
else {
128-
$text = $title
129-
}
130-
131-
[System.Management.Automation.CompletionResult]::new(
132-
$text,
133-
$title,
134-
'ParameterValue',
135-
$title
136-
)
114+
if ($collection.Title -ilike "$($completerInput.StrippedWord)*") {
115+
New-PatCompletionResult -Value $collection.Title -QuoteChar $completerInput.QuoteChar
137116
}
138117
}
139118
})]
@@ -145,39 +124,18 @@ function Add-PatCollectionItem {
145124
[ArgumentCompleter({
146125
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
147126

148-
$quoteChar = ''
149-
$strippedWord = $wordToComplete
150-
if ($wordToComplete -match "^([`"'])(.*)$") {
151-
$quoteChar = $Matches[1]
152-
$strippedWord = $Matches[2]
153-
}
127+
$completerInput = ConvertFrom-PatCompleterInput -WordToComplete $wordToComplete
154128

155-
$getParams = @{ ErrorAction = 'SilentlyContinue' }
129+
$getParameters = @{ ErrorAction = 'SilentlyContinue' }
156130
if ($fakeBoundParameters.ContainsKey('ServerUri')) {
157-
$getParams['ServerUri'] = $fakeBoundParameters['ServerUri']
131+
$getParameters['ServerUri'] = $fakeBoundParameters['ServerUri']
158132
}
159133

160-
$libraries = Get-PatLibrary @getParams
134+
$libraries = Get-PatLibrary @getParameters
161135

162136
foreach ($lib in $libraries.Directory) {
163-
if ($lib.title -ilike "$strippedWord*") {
164-
$title = $lib.title
165-
if ($quoteChar) {
166-
$text = "$quoteChar$title$quoteChar"
167-
}
168-
elseif ($title -match '\s') {
169-
$text = "'$title'"
170-
}
171-
else {
172-
$text = $title
173-
}
174-
175-
[System.Management.Automation.CompletionResult]::new(
176-
$text,
177-
$title,
178-
'ParameterValue',
179-
$title
180-
)
137+
if ($lib.title -ilike "$($completerInput.StrippedWord)*") {
138+
New-PatCompletionResult -Value $lib.title -QuoteChar $completerInput.QuoteChar
181139
}
182140
}
183141
})]

PlexAutomationToolkit/Public/Add-PatPlaylistItem.ps1

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -83,39 +83,18 @@ function Add-PatPlaylistItem {
8383
[ArgumentCompleter({
8484
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
8585

86-
$quoteChar = ''
87-
$strippedWord = $wordToComplete
88-
if ($wordToComplete -match "^([`"'])(.*)$") {
89-
$quoteChar = $Matches[1]
90-
$strippedWord = $Matches[2]
91-
}
86+
$completerInput = ConvertFrom-PatCompleterInput -WordToComplete $wordToComplete
9287

93-
$getParams = @{ ErrorAction = 'SilentlyContinue' }
88+
$getParameters = @{ ErrorAction = 'SilentlyContinue' }
9489
if ($fakeBoundParameters.ContainsKey('ServerUri')) {
95-
$getParams['ServerUri'] = $fakeBoundParameters['ServerUri']
90+
$getParameters['ServerUri'] = $fakeBoundParameters['ServerUri']
9691
}
9792

98-
$playlists = Get-PatPlaylist @getParams
93+
$playlists = Get-PatPlaylist @getParameters
9994

10095
foreach ($playlist in $playlists) {
101-
if ($playlist.Title -ilike "$strippedWord*") {
102-
$title = $playlist.Title
103-
if ($quoteChar) {
104-
$text = "$quoteChar$title$quoteChar"
105-
}
106-
elseif ($title -match '\s') {
107-
$text = "'$title'"
108-
}
109-
else {
110-
$text = $title
111-
}
112-
113-
[System.Management.Automation.CompletionResult]::new(
114-
$text,
115-
$title,
116-
'ParameterValue',
117-
$title
118-
)
96+
if ($playlist.Title -ilike "$($completerInput.StrippedWord)*") {
97+
New-PatCompletionResult -Value $playlist.Title -QuoteChar $completerInput.QuoteChar
11998
}
12099
}
121100
})]

PlexAutomationToolkit/Public/Get-PatCollection.ps1

Lines changed: 16 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -109,51 +109,30 @@ function Get-PatCollection {
109109
[ArgumentCompleter({
110110
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
111111

112-
$quoteChar = ''
113-
$strippedWord = $wordToComplete
114-
if ($wordToComplete -match "^([`"'])(.*)$") {
115-
$quoteChar = $Matches[1]
116-
$strippedWord = $Matches[2]
117-
}
112+
$completerInput = ConvertFrom-PatCompleterInput -WordToComplete $wordToComplete
118113

119-
$getParams = @{ ErrorAction = 'SilentlyContinue' }
114+
$getParameters = @{ ErrorAction = 'SilentlyContinue' }
120115
if ($fakeBoundParameters.ContainsKey('ServerUri')) {
121-
$getParams['ServerUri'] = $fakeBoundParameters['ServerUri']
116+
$getParameters['ServerUri'] = $fakeBoundParameters['ServerUri']
122117
}
123118
if ($fakeBoundParameters.ContainsKey('Token')) {
124-
$getParams['Token'] = $fakeBoundParameters['Token']
119+
$getParameters['Token'] = $fakeBoundParameters['Token']
125120
}
126121
if ($fakeBoundParameters.ContainsKey('LibraryName')) {
127-
$getParams['LibraryName'] = $fakeBoundParameters['LibraryName']
122+
$getParameters['LibraryName'] = $fakeBoundParameters['LibraryName']
128123
}
129124
elseif ($fakeBoundParameters.ContainsKey('LibraryId')) {
130-
$getParams['LibraryId'] = $fakeBoundParameters['LibraryId']
125+
$getParameters['LibraryId'] = $fakeBoundParameters['LibraryId']
131126
}
132127
else {
133128
return
134129
}
135130

136-
$collections = Get-PatCollection @getParams
131+
$collections = Get-PatCollection @getParameters
137132

138133
foreach ($collection in $collections) {
139-
if ($collection.Title -ilike "$strippedWord*") {
140-
$title = $collection.Title
141-
if ($quoteChar) {
142-
$text = "$quoteChar$title$quoteChar"
143-
}
144-
elseif ($title -match '\s') {
145-
$text = "'$title'"
146-
}
147-
else {
148-
$text = $title
149-
}
150-
151-
[System.Management.Automation.CompletionResult]::new(
152-
$text,
153-
$title,
154-
'ParameterValue',
155-
$title
156-
)
134+
if ($collection.Title -ilike "$($completerInput.StrippedWord)*") {
135+
New-PatCompletionResult -Value $collection.Title -QuoteChar $completerInput.QuoteChar
157136
}
158137
}
159138
})]
@@ -166,42 +145,21 @@ function Get-PatCollection {
166145
[ArgumentCompleter({
167146
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
168147

169-
$quoteChar = ''
170-
$strippedWord = $wordToComplete
171-
if ($wordToComplete -match "^([`"'])(.*)$") {
172-
$quoteChar = $Matches[1]
173-
$strippedWord = $Matches[2]
174-
}
148+
$completerInput = ConvertFrom-PatCompleterInput -WordToComplete $wordToComplete
175149

176-
$getParams = @{ ErrorAction = 'SilentlyContinue' }
150+
$getParameters = @{ ErrorAction = 'SilentlyContinue' }
177151
if ($fakeBoundParameters.ContainsKey('ServerUri')) {
178-
$getParams['ServerUri'] = $fakeBoundParameters['ServerUri']
152+
$getParameters['ServerUri'] = $fakeBoundParameters['ServerUri']
179153
}
180154
if ($fakeBoundParameters.ContainsKey('Token')) {
181-
$getParams['Token'] = $fakeBoundParameters['Token']
155+
$getParameters['Token'] = $fakeBoundParameters['Token']
182156
}
183157

184-
$libraries = Get-PatLibrary @getParams
158+
$libraries = Get-PatLibrary @getParameters
185159

186160
foreach ($lib in $libraries.Directory) {
187-
if ($lib.title -ilike "$strippedWord*") {
188-
$title = $lib.title
189-
if ($quoteChar) {
190-
$text = "$quoteChar$title$quoteChar"
191-
}
192-
elseif ($title -match '\s') {
193-
$text = "'$title'"
194-
}
195-
else {
196-
$text = $title
197-
}
198-
199-
[System.Management.Automation.CompletionResult]::new(
200-
$text,
201-
$title,
202-
'ParameterValue',
203-
$title
204-
)
161+
if ($lib.title -ilike "$($completerInput.StrippedWord)*") {
162+
New-PatCompletionResult -Value $lib.title -QuoteChar $completerInput.QuoteChar
205163
}
206164
}
207165
})]

0 commit comments

Comments
 (0)