diff --git a/src/cligen.cr b/src/cligen.cr index 354f06c..e799265 100644 --- a/src/cligen.cr +++ b/src/cligen.cr @@ -44,21 +44,21 @@ module CliGen {{parser}}.invalid_option{|opt| case opt when /^-+[a-z-]+/ - Logger.debug "#{CliGen::APPNAME} : ERROR : {{parser}} : invalid_option : Inavlid option provided #{opt}" + STDERR.puts "#{CliGen::APPNAME} : ERROR : {{@type.name}} : {{parser}} : invalid_option : Inavlid option provided #{opt}" else - abort "#{CliGen::APPNAME} : ERROR : {{parser}} : invalid_option : Inavlid option provided #{opt}" + abort "#{CliGen::APPNAME} : ERROR : {{@type.name}} : {{parser}} : invalid_option : Inavlid option provided #{opt}" end } {{parser}}.missing_option{|opt| - abort "ERROR : {{parser}} : missing_option : Argument was not provided to #{opt}" + abort "ERROR : {{@type.name}} : {{parser}} : missing_option : Argument was not provided to #{opt}" } {{parser}}.unknown_args{|opt| unless opt.empty? case opt.first when /^-+[a-z-]+$/ - Logger.debug "ERROR : {{parser}} : unknown_args : #{opt} not a configured argument" + STDERR.puts "ERROR : {{@type.name}} : {{parser}} : unknown_args : #{opt} not a configured argument" else - abort "ERROR : {{parser}} : unknown_args : #{opt} not a configured argument" + abort "ERROR : {{@type.name}} : {{parser}} : unknown_args : #{opt} not a configured argument" end end } diff --git a/src/command.cr b/src/command.cr index 0697698..0908c47 100644 --- a/src/command.cr +++ b/src/command.cr @@ -14,6 +14,9 @@ module CliGen annotation CommandArgument end + annotation CommandSelection + end + annotation SubCommand end @@ -23,13 +26,18 @@ module CliGen macro inherited + {% verbatim do %} macro finished define_actions define_header + {% anno = @type.annotation(::CliGen::CommandInfo) %} + {% unless anno[:def_runner] == false %} define_runner + {% end %} define_parser define_action_setter end + {% end %} end macro define_actions @@ -40,6 +48,7 @@ module CliGen macro define_header {% name = @type.name.split("::").last.downcase.id %} {% info_annos = @type.class.methods.select(&.annotation(::CliGen::SubCommand)).map(&.annotation(::CliGen::SubCommand)) %} + {% info_annos += @type.class.methods.select(&.annotation(::CliGen::CommandSelection)).map(&.annotation(::CliGen::CommandSelection)) %} {% examples = [] of StringLiteral %} {% info_annos.select(&.[](:examples)).map(&.[](:examples).resolve).each(&.each{|example| examples << example}) %} HEADER = [ @@ -55,9 +64,28 @@ module CliGen ].join("\n") end - macro define_argument(arg_name, variable = nil, type = String, short = "", long = "", description = nil, subtype = Nil, format = nil, check = nil, def_getter = false, default = nil, logger = nil) - {% raise "define_argument : You must provide a format or set it to \"auto\" to use builtin formats when defining a Time argument" if type.id.stringify == "Time" && format == nil %} - {% raise "define_argument : You MUST provide a description" unless description %} + macro define_selection(selection_name, short = "", long = "", description = nil, default = "", values = [] of StringLiteral) + {% if values.is_a? Path %} + {% values = values.resolve %} + {% end %} + {% raise "ERROR : define_selection : You MUST provide a description" unless description %} + {% raise "ERROR : define_selection : You must provide a valid set of values in Array(String) format" if values.empty? %} + {% description = "#{description.id} (valid: #{values.join(", ").id})" %} + {% unless default.empty? %} + {% description = "#{description.id} (default: #{default.id})" %} + {% end %} + @@{{selection_name}} : String = {{default}} + + @[::CliGen::CommandArgument(short: {{short}}, long: {{long}}, description: {{description}} )] + def self.{{selection_name}}= (selection : String) + raise "ERROR : {{selection_name}} : Invalid Selection(#{selection}) (valid: {{values.map(&.id).join(", ").id}})" unless {{values}}.includes?(selection) + @@{{selection_name}} = selection + end + end + + macro define_argument(arg_name, variable = nil, type = String, short = "", long = "", description = nil, subtype = Nil, format = nil, check = nil, def_getter = false, default = nil, logger = nil, &block?) + {% raise "ERROR : define_argument : You must provide a format or set it to \"auto\" to use builtin formats when defining a Time argument" if type.id.stringify == "Time" && format == nil %} + {% raise "ERROR : define_argument : You MUST provide a description" unless description %} {% variable = arg_name unless variable %} {% _type = type.id.stringify %} {% _subtype = subtype.id.stringify %} @@ -86,7 +114,7 @@ module CliGen {{check}}(var) {% end %} - {% if format %} + {% if format != "auto" && format %} abort "ERROR : Action : {{arg_name}} : Incorrect format for provided data #{var}" unless var =~ {{format}} {% end %} ## If a logger method has been provided by the user @@ -132,13 +160,22 @@ module CliGen {% end %} {% end %} + {% selections = @type.class.methods.select(&.annotation(::CliGen::CommandSelection)) %} {% methods = @type.class.methods.select(&.annotation(::CliGen::SubCommand)) %} - {% raise "ERROR : No commands defined for #{@type.name}" if methods.empty? %} + {% raise "ERROR : No commands or selections defined for #{@type.name}" if methods.empty? && selections.empty? %} + {% raise "ERROR : Can't define both selections and commands for work in #{@type.name}" if ! methods.empty? && ! selections.empty? %} + {% var = methods.empty? ? nil : "action" %} + {% targets = methods.empty? ? selections : methods %} + {% if targets == selections %} + {% selectors = selections.map(&.annotation(::CliGen::CommandSelection)).map(&.[:selector]).uniq %} + {% raise "ERROR : Can't provide more than a single selector for runner" if selectors.size > 1 %} + {% var = selectors.first %} + {% end %} {% begin %} - case @@action - {% for method in methods %} - when {{method.name.stringify}} - {{method.name}} + case @@{{var.id}} + {% for target in targets %} + when {{target.name.stringify}} + {{target.name}} {% end %} else abort "ERROR : No action provided to command {{@type.name}}" diff --git a/src/command/parser.cr b/src/command/parser.cr index 649aa76..11774f0 100644 --- a/src/command/parser.cr +++ b/src/command/parser.cr @@ -7,11 +7,24 @@ module CliGen::Parser {% puts "#{@type.name} OptionParser is being generated" %} {% name = @type.name.split("::").last %} {% var = name.downcase %} + {% methods = @type.class.methods %} {% info = @type.annotation(::CliGen::CommandInfo) %} {% raise "ERROR : No CommandInfo annotation provided to {{@type.name}}" unless info %} subparser = OptionParser.new do |parser| parser.banner = {{@type.name}}::HEADER - {% subcommands = @type.class.methods.select(&.annotation(::CliGen::SubCommand)) %} + + {% selections = methods.select(&.annotation(::CliGen::CommandSelection)) %} + {% if selections.size > 0 %} + CliGen.define_section("Selections", parser) + {% for selection in selections %} + {% selection_anno = selection.annotation(::CliGen::CommandSelection) %} + parser.on({{selection.name.stringify}}, {{selection_anno[:description]}}){ + {{@type.name}}.{{selection_anno[:selector]}}= {{selection.name.stringify}} + } + {% end %} + {% end %} + + {% subcommands = methods.select(&.annotation(::CliGen::SubCommand)) %} {% if subcommands.size > 0 %} CliGen.define_section("Subcommands", parser) {% for subcommand in subcommands %} @@ -22,7 +35,7 @@ module CliGen::Parser {% end %} {% end %} - {% arguments = @type.class.methods.select(&.annotation(::CliGen::CommandArgument)) %} + {% arguments = methods.select(&.annotation(::CliGen::CommandArgument)) %} {% if arguments.size > 0 %} CliGen.define_section("Provide Arguments", parser) {% for argument in arguments %} @@ -41,7 +54,10 @@ module CliGen::Parser CliGen.define_default_flags(parser) end parent_parser.on({{var}}, {{info[:description]}}){ - ARGV.delete({{var}}) + raise "ERROR : {{var.id}} not found in ARGV" unless i = ARGV.index({{var}}) + ## Removing all preceeding arguments so that only the actual required + ## args for the next command are present + ARGV.shift(i+1) abort subparser if ARGV.empty? subparser.parse {{@type.name}}.run