require "colorize" require "time" require "./command/parser" module CliGen annotation CommandPreRun end annotation CommandInfo end annotation CommandArgument end annotation SubCommand end class Command extend CliGen::Parser macro inherited macro finished define_actions define_header define_runner define_parser define_action_setter end end macro define_actions @@action : String = "" ACTIONS = {{@type.class.methods.select(&.annotation(::CliGen::SubCommand)).map(&.name.stringify)}} of String end macro define_header {% name = @type.name.split("::").last.downcase.id %} {% info_annos = @type.class.methods.select(&.annotation(::CliGen::SubCommand)).map(&.annotation(::CliGen::SubCommand)) %} {% examples = [] of StringLiteral %} {% info_annos.select(&.[](:examples)).map(&.[](:examples).resolve).each(&.each{|example| examples << example}) %} HEADER = [ "#{CliGen::APPNAME} {{name}} [[flags]]", "", {% unless examples.empty? %} "Examples:".colorize(:green), "-------------------------------------------------------------------------------".colorize(:blue), {% for example in examples %} {{example}}, {% end %} {% end %} ].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 %} {% variable = arg_name unless variable %} {% _type = type.id.stringify %} {% _subtype = subtype.id.stringify %} {% if _type == "String" %} @@{{variable}} : {{type}} = "" {% elsif _type == "Bool" %} @@{{variable}} : {{type}} = false {% elsif _type == "Int32" %} @@{{variable}} : {{type}} = 0 {% elsif _type == "Array" %} {% raise "define_argument : subtype can only be Int32 or String" unless %w[ Int32 String ].includes?(_subtype) %} @@{{variable}} : {{type}}({{subtype}}) = [] of {{subtype}} {% elsif _type == "Time" %} @@{{variable}} : {{type}} = Time.unix(seconds: 0) {% end %} {% if def_getter %} def self.get_{{arg_name}} : {{type}} @@{{variable}} end {% end %} @[::CliGen::CommandArgument(type: {{type}}, short: {{short}}, long: {{long}}, description: {{description}})] def self.{{arg_name}}(var : String) : Nil {% if check %} ## If the check exists go ahead and call it {{check}}(var) {% end %} {% if 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 {% if logger %} {{logger.id}} "Command : subclass : argument_setter : {{arg_name}} : Entered with #{var}" {% end %} {% if _type == "Time" %} formats = [ CliGen::Regex::INPUT_DATETIME_REGEX, CliGen::Regex::INPUT_DATE_REGEX ] abort "ERROR : Command : {{arg_name}} : Incorrect format for provided data #{var}" unless formats.any?{|f| var.match(f)} if var =~ formats[0] @@{{variable}} = Time.parse_local(var, CliGen::Format::INPUT_DATETIME_FORMAT) elsif var =~ formats[1] @@{{variable}} = Time.parse_local(var, CliGen::Format::INPUT_DATE_FORMAT) end {% elsif _type == "Array" %} {% if _subtype == "Int32" %} var : Int32 = var.to_i {% elsif _subtype == "String" %} {% else %} var = {{subtype}}.new(var) {% end %} @@{{variable}} << var {% elsif _type == "Bool" %} @@{{variable}} = true {% elsif _type == "Int32" %} @@{{variable}} = var.to_i {% elsif _type == "String" %} @@{{variable}} = var {% end %} end end macro define_runner def self.run {% pre_run_commands = @type.class.methods.select(&.annotation(::CliGen::CommandPreRun)) %} {% unless pre_run_commands.empty? %} ## Ensuring that all runs that are tagged with the the CommandPreRun annotation are run {% for method in pre_run_commands %} {{method.name}} {% end %} {% end %} {% methods = @type.class.methods.select(&.annotation(::CliGen::SubCommand)) %} {% raise "ERROR : No commands defined for #{@type.name}" if methods.empty? %} {% begin %} case @@action {% for method in methods %} when {{method.name.stringify}} {{method.name}} {% end %} else abort "ERROR : No action provided to command {{@type.name}}" end {% end %} end end macro define_action_setter def self.action=(action : String) abort "ERROR : Action(#{action}) is not valid. Only #{ACTIONS.join(", ")} are acceptable" unless ACTIONS.includes?(action) @@action = action end end end end