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.
@@ -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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Thor
4
+ module Completion
5
+ VERSION = "0.0.0"
6
+ end
7
+ end