Class: Pvectl::ArgvPreprocessor
- Inherits:
-
Object
- Object
- Pvectl::ArgvPreprocessor
- Defined in:
- lib/pvectl/argv_preprocessor.rb,
sig/pvectl/argv_preprocessor.rbs
Overview
Command line argument preprocessor.
Normalizes ARGV arguments before passing to GLI by reordering flags to appear before positional arguments, using GLI command metadata for dynamic flag discovery.
GLI with subcommand_option_handling :normal requires flags before
positional arguments. This preprocessor allows kubectl-style flag
placement anywhere on the command line.
Defined Under Namespace
Classes: DuplicateFlagError
Constant Summary collapse
- MAX_ARGUMENTS =
Returns Maximum number of arguments (DoS protection).
10_000- MAX_ARGUMENT_LENGTH =
Returns Maximum length of single argument in bytes.
4096- PASSTHROUGH_FLAGS =
Returns Flags passed through without processing.
%w[--help -h --version].freeze
Class Method Summary collapse
-
.process(argv, cli_app: Pvectl::CLI) ⇒ Array<String>
Processes command line arguments.
Instance Method Summary collapse
-
#call ⇒ Array<String>
Executes argument processing.
-
#extract_global_flags(args) ⇒ Array(Array<String>, Array<String>)
Extracts global flags from anywhere in the argument list.
-
#find_command_flag(arg, cmd) ⇒ Boolean?
Finds a command flag/switch definition matching the given argument.
-
#find_gli_command(name) ⇒ GLI::Command?
Finds a GLI command by name (supports aliases and dash-to-underscore).
-
#find_gli_subcommand(cmd, name) ⇒ GLI::Command?
Finds a GLI subcommand by name (supports aliases and dash-to-underscore).
-
#find_global_flag(arg) ⇒ Array(Symbol, Boolean)?
Finds a global flag definition matching the given argument.
-
#flag_display_name(gli_flag) ⇒ Symbol
Returns the display name for a GLI flag/switch (prefers long name).
-
#flag_matches?(gli_flag, flag_part) ⇒ Boolean
Checks if a GLI flag/switch matches the given argument string.
-
#identify_command(args) ⇒ Array(String, Array<String>, Array<String>)
Identifies the command name from remaining args (after global flag extraction).
-
#initialize(argv, cli_app: Pvectl::CLI) ⇒ ArgvPreprocessor
constructor
Initializes preprocessor with a copy of arguments.
-
#passthrough_mode? ⇒ Boolean
Checks if arguments contain flags requiring passthrough.
-
#reorder_all_flags ⇒ Array<String>
Main reordering logic.
-
#reorder_command_flags(args, cmd) ⇒ Array<String>
Reorders command flags to appear before positional arguments.
-
#store_global_flag(store, name, value) ⇒ void
Stores a global flag value with duplicate detection.
-
#validate_input_limits! ⇒ void
Validates input limits for DoS attack protection.
-
#validate_value!(value, flag_name) ⇒ void
Validates flag value for security.
Constructor Details
#initialize(argv, cli_app: Pvectl::CLI) ⇒ ArgvPreprocessor
Initializes preprocessor with a copy of arguments.
63 64 65 66 |
# File 'lib/pvectl/argv_preprocessor.rb', line 63 def initialize(argv, cli_app: Pvectl::CLI) @argv = argv.dup @cli_app = cli_app end |
Class Method Details
.process(argv, cli_app: Pvectl::CLI) ⇒ Array<String>
Processes command line arguments.
55 56 57 |
# File 'lib/pvectl/argv_preprocessor.rb', line 55 def self.process(argv, cli_app: Pvectl::CLI) new(argv, cli_app: cli_app).call end |
Instance Method Details
#call ⇒ Array<String>
Executes argument processing.
73 74 75 76 77 78 79 |
# File 'lib/pvectl/argv_preprocessor.rb', line 73 def call validate_input_limits! return @argv if passthrough_mode? return [] if @argv.empty? reorder_all_flags end |
#extract_global_flags(args) ⇒ Array(Array<String>, Array<String>)
Extracts global flags from anywhere in the argument list.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/pvectl/argv_preprocessor.rb', line 137 def extract_global_flags(args) global_flags_collected = {} global_result = [] remaining = [] index = 0 while index < args.length arg = args[index] if arg == "--" remaining.concat(args[index..]) break end flag_info = find_global_flag(arg) if flag_info name, has_value = flag_info if arg.include?("=") _, value = arg.split("=", 2) validate_value!(value, name) store_global_flag(global_flags_collected, name, value) global_result << arg elsif has_value raise ArgumentError, "Missing value for flag #{arg}" if index + 1 >= args.length value = args[index + 1] validate_value!(value, name) store_global_flag(global_flags_collected, name, value) global_result << arg << value index += 1 else store_global_flag(global_flags_collected, name, true) global_result << arg end else remaining << arg end index += 1 end [global_result, remaining] end |
#find_command_flag(arg, cmd) ⇒ Boolean?
Finds a command flag/switch definition matching the given argument.
320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/pvectl/argv_preprocessor.rb', line 320 def find_command_flag(arg, cmd) flag_part = arg.split("=", 2).first cmd.flags.each_value do |flag| return true if flag_matches?(flag, flag_part) end cmd.switches.each_value do |sw| return false if flag_matches?(sw, flag_part) end nil end |
#find_gli_command(name) ⇒ GLI::Command?
Finds a GLI command by name (supports aliases and dash-to-underscore).
265 266 267 |
# File 'lib/pvectl/argv_preprocessor.rb', line 265 def find_gli_command(name) @cli_app.commands[name.to_sym] || @cli_app.commands[name.tr("-", "_").to_sym] end |
#find_gli_subcommand(cmd, name) ⇒ GLI::Command?
Finds a GLI subcommand by name (supports aliases and dash-to-underscore).
274 275 276 |
# File 'lib/pvectl/argv_preprocessor.rb', line 274 def find_gli_subcommand(cmd, name) cmd.commands[name.to_sym] || cmd.commands[name.tr("-", "_").to_sym] end |
#find_global_flag(arg) ⇒ Array(Symbol, Boolean)?
Finds a global flag definition matching the given argument.
185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/pvectl/argv_preprocessor.rb', line 185 def find_global_flag(arg) flag_part = arg.split("=", 2).first @cli_app.flags.each_value do |flag| return [flag_display_name(flag), true] if flag_matches?(flag, flag_part) end @cli_app.switches.each_value do |sw| return [flag_display_name(sw), false] if flag_matches?(sw, flag_part) end nil end |
#flag_display_name(gli_flag) ⇒ Symbol
Returns the display name for a GLI flag/switch (prefers long name).
216 217 218 219 220 |
# File 'lib/pvectl/argv_preprocessor.rb', line 216 def flag_display_name(gli_flag) all_names = [gli_flag.name] + (gli_flag.aliases || []) long_name = all_names.find { |n| n.to_s.length > 1 } long_name || gli_flag.name end |
#flag_matches?(gli_flag, flag_part) ⇒ Boolean
Checks if a GLI flag/switch matches the given argument string.
204 205 206 207 208 209 210 |
# File 'lib/pvectl/argv_preprocessor.rb', line 204 def flag_matches?(gli_flag, flag_part) all_names = [gli_flag.name] + (gli_flag.aliases || []) all_names.any? do |name| prefix = name.to_s.length == 1 ? "-" : "--" "#{prefix}#{name}" == flag_part end end |
#identify_command(args) ⇒ Array(String, Array<String>, Array<String>)
Identifies the command name from remaining args (after global flag extraction).
254 255 256 257 258 259 |
# File 'lib/pvectl/argv_preprocessor.rb', line 254 def identify_command(args) return [nil, [], args] if args.empty? || args.first.start_with?("-") command_name = args.first [command_name, [command_name], args[1..]] end |
#passthrough_mode? ⇒ Boolean
Checks if arguments contain flags requiring passthrough.
98 99 100 |
# File 'lib/pvectl/argv_preprocessor.rb', line 98 def passthrough_mode? (@argv & PASSTHROUGH_FLAGS).any? end |
#reorder_all_flags ⇒ Array<String>
Main reordering logic. Three phases:
- Extract global flags to front
- Identify command (and optional subcommand)
- Reorder command/subcommand flags before positional args
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/pvectl/argv_preprocessor.rb', line 108 def reorder_all_flags global_flags, rest = extract_global_flags(@argv) return global_flags + rest if rest.empty? command_name, command_tokens, after_command = identify_command(rest) return global_flags + rest unless command_name cmd = find_gli_command(command_name) return global_flags + rest unless cmd # Check for subcommand if after_command.any? && cmd.commands.any? sub_name = after_command.first sub_cmd = find_gli_subcommand(cmd, sub_name) if sub_cmd subcommand_tokens = [after_command.shift] reordered = reorder_command_flags(after_command, sub_cmd) return global_flags + command_tokens + subcommand_tokens + reordered end end reordered = reorder_command_flags(after_command, cmd) global_flags + command_tokens + reordered end |
#reorder_command_flags(args, cmd) ⇒ Array<String>
Reorders command flags to appear before positional arguments.
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/pvectl/argv_preprocessor.rb', line 283 def reorder_command_flags(args, cmd) flags = [] positional = [] index = 0 while index < args.length arg = args[index] if arg == "--" positional.concat(args[index..]) break end flag_info = find_command_flag(arg, cmd) unless flag_info.nil? has_value = flag_info if has_value && !arg.include?("=") && index + 1 < args.length flags << arg << args[index + 1] index += 1 else flags << arg end else positional << arg end index += 1 end flags + positional end |
#store_global_flag(store, name, value) ⇒ void
This method returns an undefined value.
Stores a global flag value with duplicate detection.
229 230 231 232 233 234 235 236 |
# File 'lib/pvectl/argv_preprocessor.rb', line 229 def store_global_flag(store, name, value) if store.key?(name) existing = store[name] raise DuplicateFlagError.new(name, existing, value) if existing != value else store[name] = value end end |
#validate_input_limits! ⇒ void
This method returns an undefined value.
Validates input limits for DoS attack protection.
87 88 89 90 91 92 93 |
# File 'lib/pvectl/argv_preprocessor.rb', line 87 def validate_input_limits! raise ArgumentError, "Too many arguments (max #{MAX_ARGUMENTS})" if @argv.length > MAX_ARGUMENTS @argv.each do |arg| raise ArgumentError, "Argument too long (max #{MAX_ARGUMENT_LENGTH})" if arg.length > MAX_ARGUMENT_LENGTH end end |
#validate_value!(value, flag_name) ⇒ void
This method returns an undefined value.
Validates flag value for security.
244 245 246 247 248 |
# File 'lib/pvectl/argv_preprocessor.rb', line 244 def validate_value!(value, flag_name) return if value == true raise ArgumentError, "Invalid null byte in value for --#{flag_name}" if value.to_s.include?("\x00") end |