thor-completion 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/CODEOWNERS +4 -0
- data/.github/FUNDING.yml +4 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- data/.github/ISSUE_TEMPLATE/config.yml +5 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +38 -0
- data/.github/dependabot.yml +15 -0
- data/.github/workflows/ruby-tests.yml +52 -0
- data/.gitignore +12 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +16 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +79 -0
- data/Gemfile +5 -0
- data/LICENSE.md +20 -0
- data/README.md +85 -0
- data/Rakefile +15 -0
- data/bin/console +16 -0
- data/bin/setup +10 -0
- data/lib/thor/completion/bash.rb +254 -0
- data/lib/thor/completion/builder.rb +170 -0
- data/lib/thor/completion/fish.rb +186 -0
- data/lib/thor/completion/powershell.rb +164 -0
- data/lib/thor/completion/schema.json +208 -0
- data/lib/thor/completion/schema_validator.rb +16 -0
- data/lib/thor/completion/version.rb +7 -0
- data/lib/thor/completion/zsh.rb +343 -0
- data/lib/thor/completion.rb +33 -0
- data/lib/thor-completion.rb +3 -0
- data/thor-completion.gemspec +46 -0
- metadata +189 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Thor
|
|
4
|
+
module Completion
|
|
5
|
+
class Powershell
|
|
6
|
+
include SchemaValidator
|
|
7
|
+
|
|
8
|
+
attr_reader :output, :schema, :name, :commands, :global_options
|
|
9
|
+
|
|
10
|
+
def initialize(schema)
|
|
11
|
+
validate_schema!(schema)
|
|
12
|
+
|
|
13
|
+
@schema = schema
|
|
14
|
+
@name = schema[:name]
|
|
15
|
+
@commands = schema[:commands] || []
|
|
16
|
+
@global_options = schema[:globalOptions] || []
|
|
17
|
+
@output = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
generate_completion_function
|
|
22
|
+
|
|
23
|
+
output.join("\n")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private def generate_completion_function
|
|
27
|
+
output << "using namespace System.Management.Automation"
|
|
28
|
+
output << "using namespace System.Management.Automation.Language"
|
|
29
|
+
output << ""
|
|
30
|
+
output << "Register-ArgumentCompleter -Native -CommandName #{name} -ScriptBlock {"
|
|
31
|
+
output << " param($wordToComplete, $commandAst, $cursorPosition)"
|
|
32
|
+
output << ""
|
|
33
|
+
output << " $commandElements = $commandAst.CommandElements"
|
|
34
|
+
output << " $command = @("
|
|
35
|
+
output << " '#{name}'"
|
|
36
|
+
output << " for ($i = 1; $i -lt $commandElements.Count; $i++) {"
|
|
37
|
+
output << " $element = $commandElements[$i]"
|
|
38
|
+
output << " if ($element -isnot [StringConstantExpressionAst] -or"
|
|
39
|
+
output << " $element.StringConstantType -ne [StringConstantType]::BareWord -or"
|
|
40
|
+
output << " $element.Value.StartsWith('-') -or"
|
|
41
|
+
output << " $element.Value -eq $wordToComplete) {"
|
|
42
|
+
output << " break"
|
|
43
|
+
output << " }"
|
|
44
|
+
output << " $element.Value"
|
|
45
|
+
output << " }"
|
|
46
|
+
output << " ) -join ';'"
|
|
47
|
+
output << ""
|
|
48
|
+
output << " $completions = @(switch ($command) {"
|
|
49
|
+
|
|
50
|
+
generate_command_completions("", commands, [name])
|
|
51
|
+
|
|
52
|
+
output << " })"
|
|
53
|
+
output << ""
|
|
54
|
+
output << " $completions.Where{ $_.CompletionText -like \"$wordToComplete*\" } |"
|
|
55
|
+
output << " Sort-Object -Property ListItemText"
|
|
56
|
+
output << "}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private def generate_command_completions(prefix, cmds, path)
|
|
60
|
+
path_str = path.join(";")
|
|
61
|
+
|
|
62
|
+
# Generate completions for this level
|
|
63
|
+
output << " #{quote_ps(path_str)} {"
|
|
64
|
+
|
|
65
|
+
# Add commands/subcommands
|
|
66
|
+
cmds.reject {|c| c[:hidden] }.each do |cmd|
|
|
67
|
+
cmd_name = cmd[:name]
|
|
68
|
+
cmd_desc = escape_ps(cmd[:description] || "")
|
|
69
|
+
|
|
70
|
+
output << " [CompletionResult]::new(#{quote_ps(cmd_name)}, #{quote_ps(cmd_name)}, [CompletionResultType]::ParameterValue, #{quote_ps(cmd_desc)})"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Add global options if at root level
|
|
74
|
+
if path.length == 1
|
|
75
|
+
global_options.reject {|opt| opt[:hidden] }.each do |opt|
|
|
76
|
+
generate_option_completions(opt)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
output << " break"
|
|
81
|
+
output << " }"
|
|
82
|
+
|
|
83
|
+
# Recursively generate completions for subcommands
|
|
84
|
+
cmds.each do |cmd|
|
|
85
|
+
next if cmd[:hidden]
|
|
86
|
+
|
|
87
|
+
cmd_name = cmd[:name]
|
|
88
|
+
new_path = path + [cmd_name]
|
|
89
|
+
|
|
90
|
+
# Generate completions for this command's options and arguments
|
|
91
|
+
if cmd[:options]&.any? || cmd[:arguments]&.any?
|
|
92
|
+
cmd_path_str = new_path.join(";")
|
|
93
|
+
output << " #{quote_ps(cmd_path_str)} {"
|
|
94
|
+
|
|
95
|
+
# Add options
|
|
96
|
+
if cmd[:options]&.any?
|
|
97
|
+
cmd[:options].reject {|opt| opt[:hidden] }.each do |opt|
|
|
98
|
+
generate_option_completions(opt)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Add arguments completion (file completion as default)
|
|
103
|
+
if cmd[:arguments]&.any?
|
|
104
|
+
arg = cmd[:arguments].first
|
|
105
|
+
if arg[:completion]
|
|
106
|
+
case arg[:completion][:type]
|
|
107
|
+
when "file"
|
|
108
|
+
output << " # File completion"
|
|
109
|
+
output << " Get-ChildItem -Path . -File | ForEach-Object {"
|
|
110
|
+
output << " [CompletionResult]::new($_.Name, $_.Name, [CompletionResultType]::ParameterValue, $_.Name)"
|
|
111
|
+
output << " }"
|
|
112
|
+
when "directory"
|
|
113
|
+
output << " # Directory completion"
|
|
114
|
+
output << " Get-ChildItem -Path . -Directory | ForEach-Object {"
|
|
115
|
+
output << " [CompletionResult]::new($_.Name, $_.Name, [CompletionResultType]::ParameterValue, $_.Name)"
|
|
116
|
+
output << " }"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
output << " break"
|
|
122
|
+
output << " }"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Recurse for subcommands
|
|
126
|
+
generate_command_completions("#{prefix}#{cmd_name};", cmd[:subcommands], new_path) if cmd[:subcommands]&.any?
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private def generate_option_completions(opt)
|
|
131
|
+
opt_desc = escape_ps(opt[:description] || "")
|
|
132
|
+
|
|
133
|
+
# Add short option if available
|
|
134
|
+
if opt[:short]
|
|
135
|
+
Array(opt[:short]).each do |short|
|
|
136
|
+
short_flag = "-#{short}"
|
|
137
|
+
output << " [CompletionResult]::new(#{quote_ps(short_flag)}, #{quote_ps(short_flag)}, [CompletionResultType]::ParameterName, #{quote_ps(opt_desc)})"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Add long option
|
|
142
|
+
return unless opt[:name]
|
|
143
|
+
|
|
144
|
+
long_flag = "--#{opt[:name]}"
|
|
145
|
+
output << " [CompletionResult]::new(#{quote_ps(long_flag)}, #{quote_ps(long_flag)}, [CompletionResultType]::ParameterName, #{quote_ps(opt_desc)})"
|
|
146
|
+
|
|
147
|
+
# For options with enum values, we could add value completions
|
|
148
|
+
# but that would require tracking the previous word, which is complex in PowerShell
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private def quote_ps(str)
|
|
152
|
+
"'#{str.to_s.gsub("'", "''")}'"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private def escape_ps(str)
|
|
156
|
+
str.to_s.gsub("'", "''")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private def sanitize_name(name)
|
|
160
|
+
name.to_s.gsub(/[^a-zA-Z0-9_]/, "_")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Shell Completion Schema",
|
|
3
|
+
"description": "A universal schema for defining shell completions that can be converted to bash, zsh, fish, etc.",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["name", "commands"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "The name of the CLI program"
|
|
10
|
+
},
|
|
11
|
+
"description": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "A brief description of the CLI program"
|
|
14
|
+
},
|
|
15
|
+
"version": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "The version of the CLI program"
|
|
18
|
+
},
|
|
19
|
+
"commands": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"description": "List of available commands",
|
|
22
|
+
"items": {
|
|
23
|
+
"$ref": "#/definitions/command"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"globalOptions": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"description": "Options available to all commands",
|
|
29
|
+
"items": {
|
|
30
|
+
"$ref": "#/definitions/option"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"definitions": {
|
|
35
|
+
"command": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"required": ["name"],
|
|
38
|
+
"properties": {
|
|
39
|
+
"name": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "The command name"
|
|
42
|
+
},
|
|
43
|
+
"description": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "A brief description of what the command does"
|
|
46
|
+
},
|
|
47
|
+
"aliases": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"description": "Alternative names for this command",
|
|
50
|
+
"items": {
|
|
51
|
+
"type": "string"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"options": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"description": "Command-specific options",
|
|
57
|
+
"items": {
|
|
58
|
+
"$ref": "#/definitions/option"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"arguments": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"description": "Positional arguments for the command",
|
|
64
|
+
"items": {
|
|
65
|
+
"$ref": "#/definitions/argument"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"subcommands": {
|
|
69
|
+
"type": "array",
|
|
70
|
+
"description": "Nested subcommands",
|
|
71
|
+
"items": {
|
|
72
|
+
"$ref": "#/definitions/command"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"hidden": {
|
|
76
|
+
"type": "boolean",
|
|
77
|
+
"description": "Whether this command should be hidden from completions",
|
|
78
|
+
"default": false
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"option": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"required": ["name"],
|
|
85
|
+
"properties": {
|
|
86
|
+
"name": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"description": "The long option name (without --)"
|
|
89
|
+
},
|
|
90
|
+
"short": {
|
|
91
|
+
"oneOf": [
|
|
92
|
+
{
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "The short option flag (single character)",
|
|
95
|
+
"pattern": "^[a-zA-Z0-9]+$"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"type": "array",
|
|
99
|
+
"description": "Multiple short option flags",
|
|
100
|
+
"items": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"pattern": "^[a-zA-Z0-9]+$"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
"description": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"description": "A brief description of the option"
|
|
110
|
+
},
|
|
111
|
+
"type": {
|
|
112
|
+
"type": "string",
|
|
113
|
+
"enum": ["boolean", "string", "integer", "float", "array", "hash"],
|
|
114
|
+
"description": "The type of value this option accepts",
|
|
115
|
+
"default": "boolean"
|
|
116
|
+
},
|
|
117
|
+
"required": {
|
|
118
|
+
"type": "boolean",
|
|
119
|
+
"description": "Whether this option is required",
|
|
120
|
+
"default": false
|
|
121
|
+
},
|
|
122
|
+
"default": {
|
|
123
|
+
"description": "The default value for this option"
|
|
124
|
+
},
|
|
125
|
+
"enum": {
|
|
126
|
+
"type": "array",
|
|
127
|
+
"description": "Valid values for this option",
|
|
128
|
+
"items": {
|
|
129
|
+
"type": "string"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"completion": {
|
|
133
|
+
"$ref": "#/definitions/completion"
|
|
134
|
+
},
|
|
135
|
+
"repeatable": {
|
|
136
|
+
"type": "boolean",
|
|
137
|
+
"description": "Whether this option can be specified multiple times",
|
|
138
|
+
"default": false
|
|
139
|
+
},
|
|
140
|
+
"hidden": {
|
|
141
|
+
"type": "boolean",
|
|
142
|
+
"description": "Whether this option should be hidden from completions",
|
|
143
|
+
"default": false
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"argument": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"required": ["name"],
|
|
150
|
+
"properties": {
|
|
151
|
+
"name": {
|
|
152
|
+
"type": "string",
|
|
153
|
+
"description": "The argument name"
|
|
154
|
+
},
|
|
155
|
+
"description": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"description": "A brief description of the argument"
|
|
158
|
+
},
|
|
159
|
+
"required": {
|
|
160
|
+
"type": "boolean",
|
|
161
|
+
"description": "Whether this argument is required",
|
|
162
|
+
"default": true
|
|
163
|
+
},
|
|
164
|
+
"variadic": {
|
|
165
|
+
"type": "boolean",
|
|
166
|
+
"description": "Whether this argument accepts multiple values",
|
|
167
|
+
"default": false
|
|
168
|
+
},
|
|
169
|
+
"completion": {
|
|
170
|
+
"$ref": "#/definitions/completion"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"completion": {
|
|
175
|
+
"type": "object",
|
|
176
|
+
"description": "Defines how to complete values for this option or argument",
|
|
177
|
+
"properties": {
|
|
178
|
+
"type": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"enum": ["static", "dynamic", "file", "directory", "command"],
|
|
181
|
+
"description": "The type of completion to perform"
|
|
182
|
+
},
|
|
183
|
+
"values": {
|
|
184
|
+
"type": "array",
|
|
185
|
+
"description": "Static list of completion values (for type: static)",
|
|
186
|
+
"items": {
|
|
187
|
+
"type": "string"
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"command": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"description": "Shell command to generate completions (for type: dynamic)"
|
|
193
|
+
},
|
|
194
|
+
"pattern": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"description": "File glob pattern (for type: file)"
|
|
197
|
+
},
|
|
198
|
+
"extensions": {
|
|
199
|
+
"type": "array",
|
|
200
|
+
"description": "File extensions to complete (for type: file)",
|
|
201
|
+
"items": {
|
|
202
|
+
"type": "string"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Thor
|
|
4
|
+
module Completion
|
|
5
|
+
module SchemaValidator
|
|
6
|
+
def validate_schema!(schema)
|
|
7
|
+
schema_path = File.join(__dir__, "schema.json")
|
|
8
|
+
errors = JSON::Validator.fully_validate("file://#{schema_path}", schema)
|
|
9
|
+
|
|
10
|
+
return if errors.empty?
|
|
11
|
+
|
|
12
|
+
raise ArgumentError, "Invalid attributes: #{errors.first}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|