diff --git a/src/cligen/app/generate.cr b/src/cligen/app/generate.cr index fa3230c..73c42f0 100644 --- a/src/cligen/app/generate.cr +++ b/src/cligen/app/generate.cr @@ -30,7 +30,8 @@ module CliGen validate: {% if anno[:validation] %} {{anno[:validation]}} {% else %} nil {% end %}, on_match: {% if anno[:on_match] %} {{anno[:on_match]}} {% 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") %} {% end %} diff --git a/src/cligen/command/argument.cr b/src/cligen/command/argument.cr index 79fd458..a443ab0 100644 --- a/src/cligen/command/argument.cr +++ b/src/cligen/command/argument.cr @@ -1,6 +1,6 @@ module CliGen 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 : First argument (#{variable}) must be a TypeDeclaration (ex: ' : [= val]')" unless variable.is_a? TypeDeclaration %} {% name = variable.var %} @@ -37,8 +37,12 @@ module CliGen {% 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 %} {% 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}} {% if def_getter %} diff --git a/src/cligen/command_node.cr b/src/cligen/command_node.cr index ec37386..c247cf0 100644 --- a/src/cligen/command_node.cr +++ b/src/cligen/command_node.cr @@ -87,8 +87,6 @@ module CliGen end when CliGen::Regex::FLAG_WITH_ARG CliGen::MatchType::FlagWithArg - when CliGen::Regex::SHORT_WITH_INLINE_ARG - CliGen::MatchType::ShortWithInlineArg when CliGen::Regex::FLAG_MULTIPLE_SHORT CliGen::MatchType::FlagMultipleShort else @@ -108,7 +106,7 @@ module CliGen subcommands.any?{|f| f.name == arg} end - def flag?(arg : String) : BaseFlag + def flag?(arg : String) : Bool @flags.any?(&.matches?(arg)) 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" 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 - arg.value.gsub(/^-/, "").chars.map { |c| "-#{c}" }.each do |flag| - case match = find_match(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) + 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? + match.process + end + when MatchType::NoMatch + 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 - abort "#{CliGen::APPNAME}: cannot bundle flag that requires an argument: #{flag}" if match.requires_arg? - match.process + 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 - raise "ERROR : CommandNode(#{@name}).process : No flag match for '#{flag}'" + abort "ERROR : CommandNode(#{@name}).process : No flag match for '#{arg.value}'" end end @@ -246,8 +270,7 @@ module CliGen {% unless T == Nil %} unless passed_execution cls = T.new(handler: self.as(CliGen::BaseCommandNode)) - {% prerun = T.methods.select(&.annotation(CliGen::PreRunCommand)) %} - {% for cmd in prerun %} + {% for cmd in T.methods.select(&.annotation(CliGen::PreRunCommand)) %} cls.{{cmd.name}} {% end %} {% subcmds = T.methods.select(&.annotation(CliGen::SubCommand)) %} diff --git a/src/cligen/flag.cr b/src/cligen/flag.cr index 167e361..c666f05 100644 --- a/src/cligen/flag.cr +++ b/src/cligen/flag.cr @@ -42,6 +42,7 @@ module CliGen @options : Array(T)? @validate : (T -> Bool)? @on_match : Proc(Nil)? + @format : ::Regex? def initialize( var : String, @@ -53,7 +54,8 @@ module CliGen @default : T? = nil, @options : Array(T)? = 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) end @@ -76,6 +78,9 @@ module CliGen {% elem = T.type_vars.first %} argv.each do |arg| break if arg.flag? + unless @format.nil? + break unless arg.value =~ @format + end {% if elem == Int32 %} abort "ERROR : Flag({{T}}) : Provided arguemnt(#{arg.value}) was not an integer" unless arg.int? (@value ||= [] of Int32) << arg.value.to_i diff --git a/src/cligen/regex.cr b/src/cligen/regex.cr index a5a2395..783370d 100644 --- a/src/cligen/regex.cr +++ b/src/cligen/regex.cr @@ -1,8 +1,7 @@ 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=/^(?(-[a-zA-Z]|--[a-zA-Z-_]+))="?(?\S+?)"?$/ - FLAG_MULTIPLE_SHORT=/^-[a-zA-Z]+$/ - SHORT_WITH_INLINE_ARG=/^-[a-zA-Z][a-zA-Z0-9]+$/ + FLAG_MULTIPLE_SHORT=/^-[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_DATE_REGEX = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/