Got Array(T) working in flag.cr. Hacky stuff it is to convince the compiler that a value isn't going to be nil if I set it
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
require "./cligen/coercable"
|
||||
require "./cligen/parsable"
|
||||
require "./cligen/annotations"
|
||||
require "./cligen/format"
|
||||
@@ -10,6 +11,11 @@ require "./cligen/app"
|
||||
module CliGen
|
||||
VERSION = "0.1.0"
|
||||
|
||||
macro override_help_template(filepath)
|
||||
{% raise "ERROR : CliGen.override_help_template : File(#{filepath}) doesn't exist" unless file_exists?(filepath) %}
|
||||
CliGen::HELP_OVERRIDE_TEMPLATE = {{`readlink -f #{filepath}`.strip.stringify}}
|
||||
end
|
||||
|
||||
APPNAME = File.basename(PROGRAM_NAME)
|
||||
|
||||
record AdditionalDefaultFlag,
|
||||
|
||||
@@ -30,7 +30,7 @@ module CliGen
|
||||
validate: {% if anno[:validate] %} {{anno[:validate]}} {% else %} nil {% end %},
|
||||
on_match: {% if anno[:on_match] %} {{anno[:on_match]}} {% else %} nil {% end %}
|
||||
)
|
||||
{% debug %}
|
||||
{% debug if env("DEBUG") %}
|
||||
{% end %}
|
||||
|
||||
# Keep a copy of every flag on the root for global matching
|
||||
@@ -44,7 +44,7 @@ module CliGen
|
||||
post_run_commands: cmd_post_run_cmds,
|
||||
description: {{cmd_info[:description]}}
|
||||
)
|
||||
{% debug %}
|
||||
{% debug if env("DEBUG") %}
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
||||
|
||||
@@ -24,6 +24,30 @@ module CliGen
|
||||
def initialize(@value, @index)
|
||||
end
|
||||
|
||||
def flag? : Bool
|
||||
if @value =~ CliGen::Regex::FLAG_REGEX
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def int? : Bool
|
||||
if @value =~ /^[[:digit:]]+$/
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def float? : Bool
|
||||
if @value =~ /^[[:digit:]]+(\.[[:digit:]]+)?$/
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# This serves as a trigger that tells the object that it has been processed
|
||||
#
|
||||
# This will raise an exception if it is re-called after already having been
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module CliGen::Coercable
|
||||
abstract def coerce(arg : String) : self
|
||||
abstract def coerce(arg : String)
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ require "time"
|
||||
require "./annotations"
|
||||
require "./command/argument"
|
||||
require "./command/selection"
|
||||
require "./command/trigger"
|
||||
require "./command/help_template"
|
||||
require "./command/subcommand"
|
||||
|
||||
module CliGen
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
module CliGen
|
||||
class Command
|
||||
macro argument(variable, long, description, short = nil, validation = nil, on_match = nil)
|
||||
macro argument(variable, description, long = nil, short = nil, validation = nil, on_match = nil, def_setter = false)
|
||||
{% 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 %}
|
||||
{% name = variable.var %}
|
||||
{% type = variable.type %}
|
||||
{% if short %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided short must be a string" unless short.is_a? StringLiteral %}
|
||||
{% end %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided long must be a string" unless long.is_a? StringLiteral || long == nil %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : You must provide a short or long" unless long || short %}
|
||||
{% if long %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided long must be a string" unless long.is_a? StringLiteral %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided long must match --[a-zA-Z0-9-_]+" unless long =~ /^--[a-zA-Z0-9-_]+/ %}
|
||||
{% else %}
|
||||
{% long = "--#{name.downcase}" %}
|
||||
{% end %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : You must provide a description" unless description %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided description must be a String" unless description.is_a? StringLiteral %}
|
||||
{% unless on_match.nil? %}
|
||||
{% puts on_match %}
|
||||
{% puts "DEBUG : #{@type.name}.argument(#{name}) : OnMatch:\n\tid: #{on_match}\n\treturn_type: #{on_match.return_type}\n\tinput_vars: #{on_match.args}" if env("DEBUG")%}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided on_match must be a Proc" unless on_match.is_a? ProcLiteral %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided on_match return type must be a Bool" unless on_match.return_type == Nil %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided on_match return type must be Nil" unless on_match.return_type.resolve == Nil %}
|
||||
{% end %}
|
||||
{% unless validation.nil? %}
|
||||
{% puts "DEBUG : #{@type.name}.argument(#{name}) : Validation:\n\tid: #{validation}\n\treturn_type: #{validation.return_type}\n\tinput_vars: #{validation.args}" if env("DEBUG")%}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided validation must be a Proc" unless validation.is_a? ProcLiteral %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided validation return type must be a Bool" unless validation.return_type == Bool %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided validation return type must be a Bool" unless validation.return_type.resolve == Bool %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided validation provided validation must have an input variable" if validation.args.empty? %}
|
||||
{% arg = validation.args.first %}
|
||||
{% unless arg.restriction == type %}
|
||||
{% example = "->(#{arg.name} : #{type}) : #{type} { #{validation.body} }" %}
|
||||
{% example = "->(#{arg.name} : #{type}) : Bool { #{validation.body} }" %}
|
||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided validation input value must be #{type}. EX: #{example}" %}
|
||||
{% end %}
|
||||
{% end %}
|
||||
@@ -30,12 +36,14 @@ module CliGen
|
||||
@[CliGen::Argument(short: {{short}}, long: {{long}}, description: {{description}}, validation: {{validation}}, on_match: {{on_match}})]
|
||||
@{{variable}}
|
||||
|
||||
{% if def_setter %}
|
||||
def {{variable.var}}= (value : {{type}})
|
||||
{% unless validation.nil? %}
|
||||
raise "ERROR : #{@type.name}##{@def.name} : Provided value #{value} is not passing validation" unless {{validation}}.call(value)
|
||||
{% end %}
|
||||
@{{name}} = value
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
module CliGen
|
||||
class Command
|
||||
macro help_template(filepath)
|
||||
{% raise "ERROR : CliGen::Command.help_template : #{filepath} does not exist" unless file_exists? filepath %}
|
||||
HELP_TEMPLATE = {{`readlink -f #{filepath}`.strip.stringify}}
|
||||
{% puts "DEBUG : #{@type.name} : Set HELP_TEMPLATE to #{filepath}" if env("DEBUG") %}
|
||||
{% debug if env("DEBUG") %}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
module CliGen
|
||||
class Command
|
||||
macro trigger(short, long, argument = nil, &on_match)
|
||||
{% raise "ERROR : CliGen::Command.trigger : Provided short must be a string" unless short.is_a? StringLiteral %}
|
||||
{% raise "ERROR : CliGen::Command.trigger : Provided long must be a string" unless long.is_a? StringLiteral %}
|
||||
{% raise "ERROR : CliGen::Command.trigger : Must provide a block for on_match trigger" unless on_match %}
|
||||
{% name = long.gsub(/--/, "") %}
|
||||
|
||||
@[CliGen::Trigger(short: {{short}}, long: {{long}}, argument: {{argument}})]
|
||||
{% if argument %}
|
||||
def self.__cligen_trigger__{{name}}__({{name}} : {{argument}}) : Nil
|
||||
{{on_match.body}}
|
||||
end
|
||||
{% else %}
|
||||
def self.__cligen_trigger__{{name}}__ : Nil
|
||||
{{on_match.body}}
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -33,10 +33,6 @@ module CliGen
|
||||
@flags = (flags + CliGen::GLOBAL_FLAGS + @commands.flat_map(&.flags)).uniq
|
||||
end
|
||||
|
||||
def help : String
|
||||
ECR.render("src/cligen/template/cmd_help.ecr")
|
||||
end
|
||||
|
||||
def check_for_duplicates!(flags : Array(BaseFlag)) : Nil
|
||||
shorts = flags.compact_map(&.short)
|
||||
short_duplicates = [] of String
|
||||
@@ -79,6 +75,7 @@ module CliGen
|
||||
if subcommand?(arg)
|
||||
return CliGen::MatchType::SubCommand
|
||||
end
|
||||
|
||||
case arg
|
||||
when "-h", "--help"
|
||||
CliGen::MatchType::Help
|
||||
@@ -111,6 +108,10 @@ module CliGen
|
||||
subcommands.any?{|f| f.name == arg}
|
||||
end
|
||||
|
||||
def flag?(arg : String) : BaseFlag
|
||||
@flags.any?(&.matches?(arg))
|
||||
end
|
||||
|
||||
# Converts String array to Arg array and hands off to the typed process method
|
||||
def process(args : Array(String)) : Nil
|
||||
new_args = args.each_with_index.map { |arg, i| CliGen::Arg.new(value: arg, index: i) }.to_a
|
||||
@@ -143,6 +144,22 @@ module CliGen
|
||||
{% end %}
|
||||
end
|
||||
|
||||
def help : String
|
||||
{% begin %}
|
||||
{% if T.has_constant? "HELP_TEMPLATE" %}
|
||||
{% puts "#{T} was found to have HELP_TEMPLATE defined using this instead" if env("DEBUG") %}
|
||||
ECR.render({{T.constant("HELP_TEMPLATE")}})
|
||||
{% elsif CliGen.has_constant? "HELP_OVERRIDE_TEMPLATE" %} # If we have a global override use it
|
||||
{% puts "Global override found. Using" if env("DEBUG") %}
|
||||
ECR.render({{CliGen::HELP_OVERRIDE_TEMPLATE}})
|
||||
{% else %}
|
||||
{% puts "No type overrided help output. Using default" if env("DEBUG") %}
|
||||
ECR.render("src/cligen/template/cmd_help.ecr")
|
||||
{% end %} # otherwise
|
||||
{% debug if env("DEBUG") %}
|
||||
{% end %}
|
||||
end
|
||||
|
||||
def check! : Nil
|
||||
@flags.each(&.check!)
|
||||
check_for_duplicates!(@flags)
|
||||
@@ -238,7 +255,7 @@ module CliGen
|
||||
{% if T.has_method?(:main) %}
|
||||
cls.main
|
||||
{% else %}
|
||||
raise "ERROR : CommandNode({{T}})#{{@def.name}} : No subcommand matched and no #main defined"
|
||||
raise "ERROR : CommandNode({{T}})\#{{@def.name}} : No subcommand matched and no #main defined"
|
||||
{% end %}
|
||||
end
|
||||
{% end %}
|
||||
|
||||
+23
-16
@@ -17,8 +17,8 @@ module CliGen
|
||||
@description : String
|
||||
)
|
||||
# if the user provides just a "--long" I want the @long_key to match it
|
||||
if @long.includes?(" ")
|
||||
@long_key = @long.split(" ").first
|
||||
if @long =~ /\s|=/
|
||||
@long_key = @long.split(/\s|=/).first
|
||||
else
|
||||
@long_key = @long
|
||||
end
|
||||
@@ -51,7 +51,7 @@ module CliGen
|
||||
@options : Array(T)? = nil,
|
||||
@validate : (T -> Bool)? = nil,
|
||||
@on_match : Proc(Nil)? = nil
|
||||
)
|
||||
)
|
||||
super(var, short, long, env_var, description)
|
||||
end
|
||||
|
||||
@@ -64,25 +64,31 @@ module CliGen
|
||||
raise "ERROR : Flag({{T}}) : Array requires an argument but provided array is empty" if argv.empty?
|
||||
end
|
||||
|
||||
if argv.first.flag?
|
||||
abort "ERROR : Flag({{T}}) : Provided argument was a flag (#{argv.first.value})"
|
||||
end
|
||||
|
||||
{% if T == Bool %}
|
||||
@value = true
|
||||
{% elsif T <= Array %}
|
||||
{% raise "ERROR : Flag(#{T}) : You cannot define multiple types of array entries" if T.type_vars.size > 1 %}
|
||||
{% elem = T.type_vars.first %}
|
||||
argv.each do |arg|
|
||||
break if arg.value.starts_with('-')
|
||||
break if arg.flag?
|
||||
{% 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
|
||||
{% elsif elem == String %}
|
||||
(@value ||= [] of String) << arg.value
|
||||
{% elsif elem < CliGen::Coercable %}
|
||||
if arg.value.includes?(",")
|
||||
(@value ||= [] of {{elem}}) += arg.value.split(",").map{|i| {{elem}}.coerce(i)}
|
||||
{% elsif elem.class < CliGen::Coercable %}
|
||||
{% delim = elem.has_constant?("DELIMITER") ? elem.constant("DELIMITER") : ',' %}
|
||||
if arg.value.includes?({{delim}})
|
||||
@value = (@value || [] of {{elem}}) + arg.value.split({{delim}}).map{|i| {{elem}}.coerce(i)}
|
||||
else
|
||||
(@value ||= [] of {{elem}}) << {{elem}}.coerce(arg.value)
|
||||
@value = (@value || [] of {{elem}}) + [({{elem}}.coerce(arg.value))]
|
||||
end
|
||||
{% else %}
|
||||
{% raise "ERROR : Flag(#{T}) : #{elem} is not a coercable type. If you wish to coerce it from a bare string include CliGen::Coercable & implement the class method" %}
|
||||
{% raise "ERROR : Flag(#{T}) : #{elem} is not a coercable type. If you wish to coerce it from a bare string extend with CliGen::Coercable & implement the class method" %}
|
||||
{% end %}
|
||||
arg.processed
|
||||
end
|
||||
@@ -95,7 +101,7 @@ module CliGen
|
||||
{% elsif T == String %} # String
|
||||
@value = argv.first.value
|
||||
argv.first.processed
|
||||
{% elsif T < CliGen::Parsable %}
|
||||
{% elsif T.class < CliGen::Parseable %}
|
||||
processed = argv.select(&.processed?)
|
||||
@value = T.parse_args(argv)
|
||||
post_processed = argv.select(&.processed?)
|
||||
@@ -103,7 +109,7 @@ module CliGen
|
||||
raise "ERROR : Flag({{T}}, long: #{@long_key})#process : Your {{T}}#process_args did not mark processed args as processed. Please check your code"
|
||||
end
|
||||
{% else %}
|
||||
{% raise "ERROR : Flag({{T}}, long: #{@long_key})#process : Generic Type #{T} is not supported. To add support you must include CliGen::Parsable & implement the class method" %}
|
||||
{% raise "ERROR : Flag({{T}}#process : Generic Type #{T} is not supported. To add support you must include CliGen::Parsable & implement the class method" %}
|
||||
{% end %}
|
||||
|
||||
validate!
|
||||
@@ -180,17 +186,18 @@ module CliGen
|
||||
raw.split(',').map(&.to_i)
|
||||
{% elsif elem == String %}
|
||||
raw.split(',')
|
||||
{% elsif elem < CliGen::Coercable %}
|
||||
raw.split(',').map{|i| {{elem}}.coerce(i)}
|
||||
{% elsif elem.class < CliGen::Coercable %}
|
||||
{% delim = elem.has_constant?("DELIMITER") ? elem.constant("DELIMITER") : ',' %}
|
||||
raw.split({{delim}}).map{|i| {{elem}}.coerce(i)}
|
||||
{% else %}
|
||||
{% raise "ERROR : Flag(#{T}) : #{elem} is not a coercable type. If you wish to coerce it from a bare string include CliGen::Coercable & implement the class method" %}
|
||||
{% raise "ERROR : Flag(#{T}) : #{elem} is not a coercable type. If you wish to coerce it from a bare string extend CliGen::Coercable & implement the class method" %}
|
||||
{% end %}
|
||||
{% elsif T == String %} # String
|
||||
raw
|
||||
{% elsif T < CliGen::Coercable %}
|
||||
{% elsif T.class < CliGen::Coercable %}
|
||||
T.coerce(raw)
|
||||
{% else %}
|
||||
{% raise "ERROR : Flag(#{T}) : #{T} is not a coercable type. If you wish to coerce it from a bare string include CliGen::Coercable & implement the class method" %}
|
||||
{% raise "ERROR : Flag(#{T}) : #{T} is not a coercable type. If you wish to coerce it from a bare string extend CliGen::Coercable & implement the class method" %}
|
||||
{% end %}
|
||||
end
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module CliGen::Parseable
|
||||
abstract def parse_args(args : Array(CliGen::Arg)) : self
|
||||
abstract def parse_args(args : Array(CliGen::Arg))
|
||||
end
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module CliGen::Regex
|
||||
FLAG_REGEX=/^(-[a-zA-Z]|--[a-zA-Z-_]+)$/
|
||||
FLAG_REGEX=/^(-[a-zA-Z]|--[a-zA-Z-_0-9]+)/
|
||||
FLAG_WITH_ARG=/^(?<flag>(-[a-zA-Z]|--[a-zA-Z-_]+))="?(?<arg>\S+?)"?$/
|
||||
FLAG_MULTIPLE_SHORT=/^-[a-zA-Z]+$/
|
||||
SHORT_WITH_INLINE_ARG=/^-[a-zA-Z][a-zA-Z0-9]+$/
|
||||
|
||||
@@ -17,7 +17,7 @@ Flags:
|
||||
Other Commands
|
||||
---------------------------------------------------------------
|
||||
<%- @commands.each do |command| -%>
|
||||
<%= "%-10s %s" % [ command.name, command.description ] %>
|
||||
<%= "%-15s %s" % [ command.name, command.description ] %>
|
||||
<%- end -%>
|
||||
|
||||
<%- end -%>
|
||||
@@ -25,7 +25,7 @@ Other Commands
|
||||
SubCommands of <%= @name %>:
|
||||
---------------------------------------------------------------
|
||||
<%- subcommands.each do |cmd| -%>
|
||||
<%= "%-10s %s" % [cmd.name, cmd.description] %>
|
||||
<%= "%-15s %s" % [cmd.name, cmd.description] %>
|
||||
<%- end -%>
|
||||
|
||||
<%- cmds = subcommands.select{|c| ! c.examples.nil? } -%>
|
||||
|
||||
Reference in New Issue
Block a user