Added options for array checking of base types Strings/Int32/etc
This commit is contained in:
+2
-5
@@ -14,11 +14,8 @@ module CliGen
|
|||||||
@@instance = self
|
@@instance = self
|
||||||
end
|
end
|
||||||
|
|
||||||
def check! : Nil
|
def check!
|
||||||
CliGen::GLOBAL_FLAGS.each(&.check!)
|
super
|
||||||
# @flags is the amalgamation of all child flags — checked by the commands themselves
|
|
||||||
check_for_duplicates!([CliGen::GLOBAL_FLAGS, @flags].flatten)
|
|
||||||
@commands.each(&.check!)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convenience entry point; defaults to ARGV
|
# Convenience entry point; defaults to ARGV
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ 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 %} "," {% end %},
|
||||||
format: {% if anno[:format] %} {{anno[:format]}} {% else %} nil {% end %}
|
format: {% if anno[:format] %} {{anno[:format]}} {% else %} nil {% end %}
|
||||||
)
|
)
|
||||||
{% debug if env("DEBUG") %}
|
{% debug if env("DEBUG") %}
|
||||||
|
|||||||
+2
-22
@@ -7,6 +7,7 @@ require "./command/selection"
|
|||||||
require "./command/help_template"
|
require "./command/help_template"
|
||||||
require "./command/subcommand"
|
require "./command/subcommand"
|
||||||
require "./command/def_init"
|
require "./command/def_init"
|
||||||
|
require "./command/define_command_initializer"
|
||||||
|
|
||||||
module CliGen
|
module CliGen
|
||||||
class Command
|
class Command
|
||||||
@@ -18,28 +19,7 @@ module CliGen
|
|||||||
{% if anno[:def_init] %}
|
{% if anno[:def_init] %}
|
||||||
def_init
|
def_init
|
||||||
{% end %}
|
{% end %}
|
||||||
def initialize(*, handler : CliGen::BaseCommandNode)
|
define_command_initializer
|
||||||
{% verbatim do %}
|
|
||||||
{% for var in @type.instance_vars %}
|
|
||||||
{% anno = (var.annotation(CliGen::Argument) || var.annotation(CliGen::Selection)) %}
|
|
||||||
{% if anno %}
|
|
||||||
{% raise "ERROR : #{@type.name}#initialize : Argument '#{var.name}' cannot be a nilable type (#{var.type}) — flags always resolve to a concrete value" if var.type.union? %}
|
|
||||||
if flg = handler.flags.find{|f| f.var == {{var.name.stringify}} && f.long == {{anno[:long]}}}
|
|
||||||
@{{var.id}} = flg.as(CliGen::Flag({{var.type}})).value!
|
|
||||||
else
|
|
||||||
raise "ERROR : {{@type.name}}\#{{@def.name}} : No flag found for \"{{var.name}}\"?"
|
|
||||||
end
|
|
||||||
{% else %}
|
|
||||||
{% raise "ERROR : #{@type.name}#{@def.name} : Instance Variable(#{var.name}) is not handled by CliGen and does not have a default value" unless var.default_value %}
|
|
||||||
@{{var.id}} = {{var.default_value}}
|
|
||||||
{% end %}
|
|
||||||
{% end %}
|
|
||||||
|
|
||||||
{% if @type.has_method? :after_initialize %}
|
|
||||||
after_initialize
|
|
||||||
{% end %}
|
|
||||||
{% end %}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -44,11 +44,17 @@ module CliGen
|
|||||||
{% if type.resolve <= Array && format.nil? %}
|
{% if type.resolve <= Array && format.nil? %}
|
||||||
{% elem = type.type_vars.first %}
|
{% elem = type.type_vars.first %}
|
||||||
{% unless elem == Int32 %}
|
{% unless elem == Int32 %}
|
||||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : When providing custom data types for Array(T) or using Array(String) you must provide a format for argument filtering so that parsing can be done deterministically" %}
|
{% if format.nil? && options.nil? %}
|
||||||
|
{% raise "ERROR : CliGen::Command.argument(#{name}) : When providing custom data types for Array(T) or using Array(String) you must provide a format or options for argument filtering so that parsing can be done deterministically" %}
|
||||||
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
|
{% if type.resolve < Array && ! options.nil? %}
|
||||||
|
@[CliGen::Argument(short: {{short}}, long: {{long}}, description: {{description}}, validation: {{validation}}, on_match: {{on_match}}, options: [{{options}}], delimiter: {{delimiter}}, format: {{format}})]
|
||||||
|
{% else %}
|
||||||
@[CliGen::Argument(short: {{short}}, long: {{long}}, description: {{description}}, validation: {{validation}}, on_match: {{on_match}}, options: {{options}}, delimiter: {{delimiter}}, format: {{format}})]
|
@[CliGen::Argument(short: {{short}}, long: {{long}}, description: {{description}}, validation: {{validation}}, on_match: {{on_match}}, options: {{options}}, delimiter: {{delimiter}}, format: {{format}})]
|
||||||
|
{% end %}
|
||||||
@{{variable}}
|
@{{variable}}
|
||||||
|
|
||||||
{% if def_getter %}
|
{% if def_getter %}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
module CliGen
|
||||||
|
class Command
|
||||||
|
macro define_command_initializer
|
||||||
|
def initialize(*, handler : CliGen::BaseCommandNode)
|
||||||
|
{% verbatim do %}
|
||||||
|
{% for var in @type.instance_vars %}
|
||||||
|
{% anno = (var.annotation(CliGen::Argument) || var.annotation(CliGen::Selection)) %}
|
||||||
|
{% if anno %}
|
||||||
|
{% raise "ERROR : #{@type.name}#initialize : Argument '#{var.name}' cannot be a nilable type (#{var.type}) — flags always resolve to a concrete value" if var.type.union? %}
|
||||||
|
if flg = handler.flags.find{|f| f.var == {{var.name.stringify}} && f.long == {{anno[:long]}}}
|
||||||
|
flg.validate!
|
||||||
|
@{{var.id}} = flg.as(CliGen::Flag({{var.type}})).value!
|
||||||
|
else
|
||||||
|
raise "ERROR : {{@type.name}}\#{{@def.name}} : No flag found for \"{{var.name}}\"?"
|
||||||
|
end
|
||||||
|
{% else %}
|
||||||
|
{% raise "ERROR : #{@type.name}#{@def.name} : Instance Variable(#{var.name}) is not handled by CliGen and does not have a default value" unless var.default_value %}
|
||||||
|
@{{var.id}} = {{var.default_value}}
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% if @type.has_method? :after_initialize %}
|
||||||
|
after_initialize
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -29,7 +29,7 @@ module CliGen
|
|||||||
@pre_run_commands : Array(RunCommand),
|
@pre_run_commands : Array(RunCommand),
|
||||||
@post_run_commands : Array(RunCommand),
|
@post_run_commands : Array(RunCommand),
|
||||||
@description : String? = nil
|
@description : String? = nil
|
||||||
)
|
)
|
||||||
@flags = (flags + CliGen::GLOBAL_FLAGS + @commands.flat_map(&.flags)).uniq
|
@flags = (flags + CliGen::GLOBAL_FLAGS + @commands.flat_map(&.flags)).uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -67,8 +67,12 @@ module CliGen
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(flag_long : String) : BaseFlag?
|
def get(*, long : String) : BaseFlag?
|
||||||
@flags.find{|f| f.long_key == flag_long}
|
@flags.find{|f| f.long_key == long}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(*, short : String) : BaseFlag?
|
||||||
|
@flags.find{|f| f.short == short}
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_match(arg : String)
|
def find_match(arg : String)
|
||||||
@@ -142,6 +146,11 @@ module CliGen
|
|||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def verbose? : Bool
|
||||||
|
@verbose_flag ||= get(long: "--verbose").not_nil!.as(Flag(Bool))
|
||||||
|
@verbose_flag.not_nil!.value!
|
||||||
|
end
|
||||||
|
|
||||||
def help : String
|
def help : String
|
||||||
{% begin %}
|
{% begin %}
|
||||||
{% if T.has_constant? "HELP_TEMPLATE" %}
|
{% if T.has_constant? "HELP_TEMPLATE" %}
|
||||||
@@ -162,8 +171,10 @@ module CliGen
|
|||||||
@flags.each(&.check!)
|
@flags.each(&.check!)
|
||||||
check_for_duplicates!(@flags)
|
check_for_duplicates!(@flags)
|
||||||
@commands.each(&.check!)
|
@commands.each(&.check!)
|
||||||
|
{% unless T == Nil %}
|
||||||
raise "ERROR : CommandNode({{T}})#check! : {{T}} has no subcommands and no #main defined" \
|
raise "ERROR : CommandNode({{T}})#check! : {{T}} has no subcommands and no #main defined" \
|
||||||
if subcommands.empty? && !{{T.has_method?(:main)}}
|
if subcommands.empty? && !{{T.has_method?(:main)}}
|
||||||
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
def process(args : Array(CliGen::Arg)) : Nil
|
def process(args : Array(CliGen::Arg)) : Nil
|
||||||
|
|||||||
+60
-10
@@ -1,6 +1,14 @@
|
|||||||
require "./arg"
|
require "./arg"
|
||||||
|
|
||||||
module CliGen
|
module CliGen
|
||||||
|
# To be able to store metadata for use in the help output
|
||||||
|
record FlagMeta,
|
||||||
|
type : String,
|
||||||
|
array : Bool,
|
||||||
|
format : String?,
|
||||||
|
default : String?,
|
||||||
|
options : Array(String)?
|
||||||
|
|
||||||
abstract class BaseFlag
|
abstract class BaseFlag
|
||||||
getter var : String
|
getter var : String
|
||||||
getter short : String?
|
getter short : String?
|
||||||
@@ -9,6 +17,7 @@ module CliGen
|
|||||||
getter env_var : String
|
getter env_var : String
|
||||||
getter description : String
|
getter description : String
|
||||||
getter delimiter : String
|
getter delimiter : String
|
||||||
|
getter meta : FlagMeta
|
||||||
|
|
||||||
def initialize(
|
def initialize(
|
||||||
@var : String,
|
@var : String,
|
||||||
@@ -16,7 +25,8 @@ module CliGen
|
|||||||
@long : String,
|
@long : String,
|
||||||
@env_var : String,
|
@env_var : String,
|
||||||
@description : String,
|
@description : String,
|
||||||
@delimiter : String
|
@delimiter : String,
|
||||||
|
@meta : FlagMeta
|
||||||
)
|
)
|
||||||
# if the user provides just a "--long" I want the @long_key to match it
|
# if the user provides just a "--long" I want the @long_key to match it
|
||||||
if @long =~ /\s|=/
|
if @long =~ /\s|=/
|
||||||
@@ -57,7 +67,21 @@ module CliGen
|
|||||||
@on_match : Proc(Nil)? = nil,
|
@on_match : Proc(Nil)? = nil,
|
||||||
@format : ::Regex? = nil
|
@format : ::Regex? = nil
|
||||||
)
|
)
|
||||||
super(var, short, long, env_var, description, delimiter)
|
{% unless T.has_method? :to_s %}
|
||||||
|
{% raise "ERROR : Flag({{T}}, long: #{long}) : Error your flag type must have a to_s method" %}
|
||||||
|
{% end %}
|
||||||
|
{% if T <= Array %}
|
||||||
|
{% elem = T.type_vars.first %}
|
||||||
|
{% raise "ERROR : Flag({{T}}, long: #{long}) : Error your flag subtype must have a to_s method" unless elem.has_method? :to_s %}
|
||||||
|
{% end %}
|
||||||
|
meta = FlagMeta.new(
|
||||||
|
type: {{T.stringify}},
|
||||||
|
array: {{ T < Array ? true : false }},
|
||||||
|
options: {% if T < Array %} @options.try(&.first.map(&.to_s)) {% else %} @options.try(&.map(&.to_s)) {% end %},
|
||||||
|
default: @default.try(&.to_s),
|
||||||
|
format: @format.try(&.source)
|
||||||
|
)
|
||||||
|
super(var, short, long, env_var, description, delimiter, meta)
|
||||||
end
|
end
|
||||||
|
|
||||||
def requires_arg? : Bool
|
def requires_arg? : Bool
|
||||||
@@ -79,16 +103,31 @@ module CliGen
|
|||||||
argv.each do |arg|
|
argv.each do |arg|
|
||||||
break if arg.flag?
|
break if arg.flag?
|
||||||
unless @format.nil?
|
unless @format.nil?
|
||||||
unless arg.value =~ @format
|
unless arg.value.includes?(@delimiter)
|
||||||
puts "DEBUG : Flag({{T}}, long: #{@long_key}) : #{arg.value} was not found to be matching the defined filter #{@format}. So breaking from parse loop" if ENV["DEBUG"]
|
unless arg.value =~ @format
|
||||||
break
|
puts "DEBUG : Flag({{T}}, long: #{@long_key}) : #{arg.value} was not found to be matching the defined filter #{@format}. So breaking from parse loop" if ENV["DEBUG"]?
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
{% if elem == Int32 %}
|
{% if elem == Int32 %}
|
||||||
abort "ERROR : Flag({{T}}) : Provided arguemnt(#{arg.value}) was not an integer" unless arg.int?
|
if arg.value.includes?(@delimiter)
|
||||||
(@value ||= [] of Int32) << arg.value.to_i
|
@value = (@value || [] of Int32) + arg.value.split(@delimiter).map(&.to_i32)
|
||||||
|
else
|
||||||
|
abort "ERROR : Flag({{T}}, long: #{@long_key}) : Provided arguemnt(#{arg.value}) was not an integer" unless arg.int?
|
||||||
|
(@value ||= [] of Int32) << arg.value.to_i
|
||||||
|
end
|
||||||
{% elsif elem == String %}
|
{% elsif elem == String %}
|
||||||
(@value ||= [] of String) << arg.value
|
if arg.value.includes?(@delimiter)
|
||||||
|
@value = (@value || [] of String) + arg.value.split(@delimiter).map{|v|
|
||||||
|
unless @format.nil?
|
||||||
|
abort "ERROR : Flag({{T}}, long: #{@long_key} ) : Provided arguemnt(#{v}) did not match a valid fromat \"#{@format.not_nil!.source}\"" unless v =~ @format
|
||||||
|
end
|
||||||
|
v
|
||||||
|
}
|
||||||
|
else
|
||||||
|
(@value ||= [] of String) << arg.value
|
||||||
|
end
|
||||||
{% elsif elem.class < CliGen::Coercable %}
|
{% elsif elem.class < CliGen::Coercable %}
|
||||||
if arg.value.includes?(@delimiter)
|
if arg.value.includes?(@delimiter)
|
||||||
@value = (@value || [] of {{elem}}) + arg.value.split(@delimiter).map{|i| {{elem}}.coerce(i)}
|
@value = (@value || [] of {{elem}}) + arg.value.split(@delimiter).map{|i| {{elem}}.coerce(i)}
|
||||||
@@ -101,12 +140,17 @@ module CliGen
|
|||||||
arg.processed
|
arg.processed
|
||||||
end
|
end
|
||||||
{% elsif T == Int32 %}
|
{% elsif T == Int32 %}
|
||||||
|
abort "ERROR : Flag(#{T}, long: #{@long_key}) : #{argv.first.value} is not an int" unless argv.first.int?
|
||||||
@value = argv.first.value.to_i
|
@value = argv.first.value.to_i
|
||||||
argv.first.processed
|
argv.first.processed
|
||||||
{% elsif T == Time %}
|
{% elsif T == Time %}
|
||||||
@value = parse_time(argv.first.value)
|
@value = parse_time(argv.first.value)
|
||||||
argv.first.processed
|
argv.first.processed
|
||||||
{% elsif T == String %} # String
|
{% elsif T == String %} # String
|
||||||
|
unless @format.nil?
|
||||||
|
abort "ERROR : Flag(#{T}, long: #{@long_key}) : #{argv.first.value} does not match a correct format #{@format.not_nil!.source}" unless argv.first.value =~ @format
|
||||||
|
end
|
||||||
|
|
||||||
@value = argv.first.value
|
@value = argv.first.value
|
||||||
argv.first.processed
|
argv.first.processed
|
||||||
{% elsif T.class < CliGen::Parsable %}
|
{% elsif T.class < CliGen::Parsable %}
|
||||||
@@ -161,11 +205,17 @@ module CliGen
|
|||||||
v = value!
|
v = value!
|
||||||
|
|
||||||
if opts = @options
|
if opts = @options
|
||||||
abort "#{CliGen::APPNAME}: '#{v}' is not a valid value for #{@long_key} (valid: #{opts.join(", ")})" unless opts.includes?(v)
|
{% if T < Array %}
|
||||||
|
v.each do |v2|
|
||||||
|
abort "#{CliGen::APPNAME}: '#{v2}' is not a valid value for #{@long_key} (valid: #{opts.join(", ")})" unless opts.first.includes?(v2)
|
||||||
|
end
|
||||||
|
{% else %}
|
||||||
|
abort "#{CliGen::APPNAME}: '#{v}' is not a valid value for #{@long_key} (valid: #{opts.join(", ")})" unless opts.includes?(v)
|
||||||
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
if check = @validate
|
if check = @validate
|
||||||
abort "#{CliGen::APPNAME}: validation failed for #{@long_key}" unless check.call(v)
|
abort "#{CliGen::APPNAME}: validation failed for #{@long_key} when testing #{v}" unless check.call(v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,15 @@ require "./flag"
|
|||||||
module CliGen
|
module CliGen
|
||||||
GLOBAL_FLAGS = [] of BaseFlag
|
GLOBAL_FLAGS = [] of BaseFlag
|
||||||
|
|
||||||
|
GLOBAL_FLAGS << Flag(Bool).new(
|
||||||
|
var: "",
|
||||||
|
short: "-v",
|
||||||
|
long: "--verbose",
|
||||||
|
env_var: "VERBOSE",
|
||||||
|
default: false,
|
||||||
|
description: "Enable verbose output from program & help output"
|
||||||
|
)
|
||||||
|
|
||||||
macro add_global_flag(type, long, description, env_var = nil, short = nil, validation = nil, &on_match)
|
macro add_global_flag(type, long, description, env_var = nil, short = nil, validation = nil, &on_match)
|
||||||
{% raise "ERROR : CliGen.add_global_flag : type must be a TypeNode" unless type.is_a? TypeNode %}
|
{% raise "ERROR : CliGen.add_global_flag : type must be a TypeNode" unless type.is_a? TypeNode %}
|
||||||
{% raise "ERROR : CliGen.add_global_flag : long must begin a StringLiteral" unless long.is_a? StringLiteral %}
|
{% raise "ERROR : CliGen.add_global_flag : long must begin a StringLiteral" unless long.is_a? StringLiteral %}
|
||||||
|
|||||||
@@ -9,10 +9,21 @@ Description: <%= @description %>
|
|||||||
Flags:
|
Flags:
|
||||||
---------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
<%- @flags.each do |flag| -%>
|
<%- @flags.each do |flag| -%>
|
||||||
<%- unless flag.short.nil? -%>
|
<%- if flag.short.nil? -%>
|
||||||
<%= "%-#{len}s %s" % ["#{flag.short.not_nil!.strip},#{flag.long.strip}", flag.description.strip] %>
|
<%- flags = [flag.long.strip] -%>
|
||||||
<%- else -%>
|
<%- else -%>
|
||||||
<%= "%-#{len}s %s" % [flag.long.strip, flag.description.strip] %>
|
<%- flags = [flag.short,flag.long.strip] -%>
|
||||||
|
<%- end -%>
|
||||||
|
<%= "%-#{len}s %s" % [flags.join(","), flag.description.strip] %><%= flag.meta.options.nil? ? "" : " (valid: #{flag.meta.options.not_nil!.join(&.to_s)})" %><%= flag.meta.default.nil? ? "" : " (default: #{flag.meta.default})" %>
|
||||||
|
<%- if verbose? -%>
|
||||||
|
<%= "%-#{len}s %s" % ["", "Type: #{flag.meta.type}"] %>
|
||||||
|
<%= "%-#{len}s %s" % ["", "ENV VAR: #{flag.env_var}"] %>
|
||||||
|
<%- unless flag.meta.format.nil? -%>
|
||||||
|
<%= "%-#{len}s %s" % ["", "Valid Format: #{flag.meta.format}"] %>
|
||||||
|
<%- end -%>
|
||||||
|
<%- if flag.meta.array -%>
|
||||||
|
<%= "%-#{len}s %s" % ["", "Delimiter: #{flag.delimiter}"] %>
|
||||||
|
<%- end -%>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
|||||||
Reference in New Issue
Block a user