Added format support for filtering arguments

This commit is contained in:
Tristan Ancelet
2026-08-12 16:12:59 -05:00
parent 53f510278c
commit ffde5165f7
5 changed files with 52 additions and 20 deletions
+2 -1
View File
@@ -30,7 +30,8 @@ module CliGen
validate: {% if anno[:validation] %} {{anno[:validation]}} {% else %} nil {% end %}, validate: {% if anno[:validation] %} {{anno[:validation]}} {% else %} nil {% end %},
on_match: {% if anno[:on_match] %} {{anno[:on_match]}} {% else %} nil {% end %}, on_match: {% if anno[:on_match] %} {{anno[:on_match]}} {% else %} nil {% end %},
options: {% if anno[:options] %} {{anno[:options]}} {% else %} nil {% end %}, options: {% if anno[:options] %} {{anno[:options]}} {% else %} nil {% end %},
delimiter: {% if anno[:delimiter] %} {{anno[:delimiter]}} {% else %} nil {% end %} delimiter: {% if anno[:delimiter] %} {{anno[:delimiter]}} {% else %} nil {% end %},
format: {% if anno[:format] %} {{anno[:format]}} {% else %} nil {% end %}
) )
{% debug if env("DEBUG") %} {% debug if env("DEBUG") %}
{% end %} {% end %}
+6 -2
View File
@@ -1,6 +1,6 @@
module CliGen module CliGen
class Command class Command
macro argument(variable, description, long = nil, short = nil, validation = nil, on_match = nil, def_setter = false, def_getter = false, options = nil, delimiter = ",") macro argument(variable, description, long = nil, short = nil, validation = nil, on_match = nil, def_setter = false, def_getter = false, options = nil, delimiter = ",", format = nil)
{% raise "ERROR : CliGen::Command.argument : def_setter must be a Bool" unless def_setter.is_a? BoolLiteral %} {% raise "ERROR : CliGen::Command.argument : def_setter must be a Bool" unless def_setter.is_a? BoolLiteral %}
{% raise "ERROR : CliGen::Command.argument : First argument (#{variable}) must be a TypeDeclaration (ex: '<var> : <type> [= val]')" unless variable.is_a? TypeDeclaration %} {% raise "ERROR : CliGen::Command.argument : First argument (#{variable}) must be a TypeDeclaration (ex: '<var> : <type> [= val]')" unless variable.is_a? TypeDeclaration %}
{% name = variable.var %} {% name = variable.var %}
@@ -37,8 +37,12 @@ module CliGen
{% options = options.resolve if options.is_a? Path %} {% options = options.resolve if options.is_a? Path %}
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided options must be an ArrayLiteral" unless options.is_a? ArrayLiteral %} {% raise "ERROR : CliGen::Command.argument(#{name}) : Provided options must be an ArrayLiteral" unless options.is_a? ArrayLiteral %}
{% end %} {% end %}
{% if format %}
{% format = format.resolve if format.is_a? Path %}
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided format must be a RegexLiteral" unless format.is_a? RegexLiteral %}
{% end %}
@[CliGen::Argument(short: {{short}}, long: {{long}}, description: {{description}}, validation: {{validation}}, on_match: {{on_match}}, options: {{options}}, delimiter: {{delimiter}})] @[CliGen::Argument(short: {{short}}, long: {{long}}, description: {{description}}, validation: {{validation}}, on_match: {{on_match}}, options: {{options}}, delimiter: {{delimiter}}, format: {{format}})]
@{{variable}} @{{variable}}
{% if def_getter %} {% if def_getter %}
+33 -10
View File
@@ -87,8 +87,6 @@ module CliGen
end end
when CliGen::Regex::FLAG_WITH_ARG when CliGen::Regex::FLAG_WITH_ARG
CliGen::MatchType::FlagWithArg CliGen::MatchType::FlagWithArg
when CliGen::Regex::SHORT_WITH_INLINE_ARG
CliGen::MatchType::ShortWithInlineArg
when CliGen::Regex::FLAG_MULTIPLE_SHORT when CliGen::Regex::FLAG_MULTIPLE_SHORT
CliGen::MatchType::FlagMultipleShort CliGen::MatchType::FlagMultipleShort
else else
@@ -108,7 +106,7 @@ module CliGen
subcommands.any?{|f| f.name == arg} subcommands.any?{|f| f.name == arg}
end end
def flag?(arg : String) : BaseFlag def flag?(arg : String) : Bool
@flags.any?(&.matches?(arg)) @flags.any?(&.matches?(arg))
end end
@@ -221,17 +219,43 @@ module CliGen
raise "Oh good, you broke regex. How the hell did it match in find_match but not above? What the hell is going on" raise "Oh good, you broke regex. How the hell did it match in find_match but not above? What the hell is going on"
end end
when MatchType::ShortWithInlineArg
abort "#{CliGen::APPNAME}: invalid flag '#{arg.value}' — inline values are not supported. Did you mean '#{arg.value[0..1]} #{arg.value[2..]}' ?"
when MatchType::FlagMultipleShort when MatchType::FlagMultipleShort
arg.value.gsub(/^-/, "").chars.map { |c| "-#{c}" }.each do |flag| val = arg.value.gsub(/^-/,"")
# If the next character in the series is a flag assume the remaining are flags as well.
if flag?("-#{val[1]}")
chars = val.chars
chars.map { |c| "-#{c}" }.each do |flag|
case match = find_match(flag) case match = find_match(flag)
when BaseFlag when BaseFlag
# If this is the last flag in the series let it process others
if flag == "-#{chars.last}"
if match.requires_arg?
match.process(args.reject(&.processed?))
else
match.process
end
else
abort "#{CliGen::APPNAME}: cannot bundle flag that requires an argument: #{flag}" if match.requires_arg? abort "#{CliGen::APPNAME}: cannot bundle flag that requires an argument: #{flag}" if match.requires_arg?
match.process match.process
end
when MatchType::NoMatch when MatchType::NoMatch
raise "ERROR : CommandNode(#{@name}).process : No flag match for '#{flag}'" abort "ERROR : CommandNode(#{@name}).process : No flag match for '#{flag}'"
end
end
else
# In this case we assume the characters following the flag is the argument
flg = "-#{val[0]}"
argument = Arg.new(value: val[1..], index: 0)
case match = find_match(flg)
when BaseFlag
if match.requires_arg?
match.process([argument])
else
abort "ERROR : CommandNode(#{@name}).process : Unknown characters proceeding a boolean flag(#{flg}) : #{val[1..]}"
end
when MatchType::NoMatch
abort "ERROR : CommandNode(#{@name}).process : No flag match for '#{arg.value}'"
end end
end end
@@ -246,8 +270,7 @@ module CliGen
{% unless T == Nil %} {% unless T == Nil %}
unless passed_execution unless passed_execution
cls = T.new(handler: self.as(CliGen::BaseCommandNode)) cls = T.new(handler: self.as(CliGen::BaseCommandNode))
{% prerun = T.methods.select(&.annotation(CliGen::PreRunCommand)) %} {% for cmd in T.methods.select(&.annotation(CliGen::PreRunCommand)) %}
{% for cmd in prerun %}
cls.{{cmd.name}} cls.{{cmd.name}}
{% end %} {% end %}
{% subcmds = T.methods.select(&.annotation(CliGen::SubCommand)) %} {% subcmds = T.methods.select(&.annotation(CliGen::SubCommand)) %}
+6 -1
View File
@@ -42,6 +42,7 @@ module CliGen
@options : Array(T)? @options : Array(T)?
@validate : (T -> Bool)? @validate : (T -> Bool)?
@on_match : Proc(Nil)? @on_match : Proc(Nil)?
@format : ::Regex?
def initialize( def initialize(
var : String, var : String,
@@ -53,7 +54,8 @@ module CliGen
@default : T? = nil, @default : T? = nil,
@options : Array(T)? = nil, @options : Array(T)? = nil,
@validate : (T -> Bool)? = nil, @validate : (T -> Bool)? = nil,
@on_match : Proc(Nil)? = nil @on_match : Proc(Nil)? = nil,
@format : ::Regex? = nil
) )
super(var, short, long, env_var, description, delimiter) super(var, short, long, env_var, description, delimiter)
end end
@@ -76,6 +78,9 @@ module CliGen
{% elem = T.type_vars.first %} {% elem = T.type_vars.first %}
argv.each do |arg| argv.each do |arg|
break if arg.flag? break if arg.flag?
unless @format.nil?
break unless arg.value =~ @format
end
{% if elem == Int32 %} {% if elem == Int32 %}
abort "ERROR : Flag({{T}}) : Provided arguemnt(#{arg.value}) was not an integer" unless arg.int? abort "ERROR : Flag({{T}}) : Provided arguemnt(#{arg.value}) was not an integer" unless arg.int?
(@value ||= [] of Int32) << arg.value.to_i (@value ||= [] of Int32) << arg.value.to_i
+2 -3
View File
@@ -1,8 +1,7 @@
module CliGen::Regex module CliGen::Regex
FLAG_REGEX=/^(-[a-zA-Z]|--[a-zA-Z-_0-9]+)/ FLAG_REGEX=/^(-[a-zA-Z]|--[a-zA-Z-_0-9]+)$/
FLAG_WITH_ARG=/^(?<flag>(-[a-zA-Z]|--[a-zA-Z-_]+))="?(?<arg>\S+?)"?$/ FLAG_WITH_ARG=/^(?<flag>(-[a-zA-Z]|--[a-zA-Z-_]+))="?(?<arg>\S+?)"?$/
FLAG_MULTIPLE_SHORT=/^-[a-zA-Z]+$/ FLAG_MULTIPLE_SHORT=/^-[a-zA-Z0-9]+/
SHORT_WITH_INLINE_ARG=/^-[a-zA-Z][a-zA-Z0-9]+$/
INPUT_DATETIME_REGEX = /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/ INPUT_DATETIME_REGEX = /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/
INPUT_DATE_REGEX = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ INPUT_DATE_REGEX = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/