module CliGen
Defined in:
cligen.crcligen/annotations.cr
cligen/app.cr
cligen/app/generate.cr
cligen/arg.cr
cligen/command.cr
cligen/command/argument.cr
cligen/command/define_command_initializer.cr
cligen/command/define_singleton_init.cr
cligen/command/generate_gather_handler.cr
cligen/command/generate_register_command.cr
cligen/command/help_template.cr
cligen/command/resolve_value.cr
cligen/command/subcommand.cr
cligen/command/validate_command_tree.cr
cligen/command_node.cr
cligen/command_node/base.cr
cligen/command_node/command_meta.cr
cligen/command_node/subcommand_meta.cr
cligen/exceptions.cr
cligen/flag.cr
cligen/flag/base.cr
cligen/flag/meta.cr
cligen/global_flag.cr
cligen/global_flag/add_global_flag.cr
cligen/match_type.cr
Constant Summary
-
APPNAME =
File.basename(PROGRAM_NAME) -
GLOBAL_FLAGS =
[] of BaseFlag -
MAX_COMMAND_DEPTH =
32 -
CliGen::MAX_COMMAND_DEPTH
This exists to prevent the user from defining a command tree that extends past the compile-time configured max via the CliGen::MAX_COMMAND_DEPTH constant.
The reason this is a thing is because crystal macros don't allow for unbounded while's/until's in macros, meaning it always has to be deterministic. SO to deal with this and still allow for subcommand defining you need either go with the default (32 command depth) or define your own larger max (understand this will affect compile-time due to this directly affecting loops in the Command macros).
So to still support this I had to make bounded for-loops usng
{% for i in (1..CliGen::MAX_COMMAND_DEPTH) %} ...do checks... {% end } -
VERSION =
"0.2.0"
Macro Summary
-
add_global_flag(type, *, long, description, env_var = "", short = nil, validation = nil, default = nil, on_match = nil, options = nil, format = nil, internal = false)
This macro provides a user-friendly way to define a global flag for your project.
- override_help_template(filepath)
Macro Detail
This macro provides a user-friendly way to define a global flag for your project.
What does this do?
This macro is used to help define & check a global flag to be used in the all levels of commands.
When provided it will parse your values & serialize them into a Flag(T)
object & insert it in the CliGen::GLOBAL_FLAGS array after checking if
a flag using it's --long is already in use. In the case that that long
is already used it will raise at runtime and you'll need to choose another
long.
Arguments
type: TypeNode
Required: true
This is the type of the flag (Bool, Int32, String, etc).
long: StringLiteral
Required: true
This is the long form of the flag that will be matched at the command-line
description: StringLiteral
Required: true
This is the full length description of the flag that will be presented in the help text provided to the user.
env_var: StringLiteral
Required: false
This is an ENV VAR that can be used to set this value without providing an
argument via the CLI. By default it will (unless explicitly disbled by
passing env_var: nil as an argument to disable the env_var entirely)
will parse your long flag and set the ENV VAR to the un "--" portion of it
Warning: Incompatible ENV VAR formatting
When providing ENV VARs manually you cannot provide any whitespace or "-" characters internally to it. As thse are both incompatible with ENV VARs.
If you provide an ENV VAR with these the framework will raise at compile-time and tell you to change them.
Note: Auto Generates ENV VAR from flag long
If you did not provide a ENV VAR manually (or disable it via setting it to nil), the macro will use the long flag to create a ENV VAR that can be matched. In this case if the flag has any internal "-" chars they will be replaced with "_" so "--long--flag--name"/"--long-flag-name" -> "LONG_FLAG_NAME".
When you provide a long: with a trailing ARGUMENT (ex: "--item ITEM", "--item=ITEM") the flag will first be split on the whitespace or "=" prior to being used for the ENV_VAR.
short: StringLiteral
Required: false
This is the short form of a flag ("--filename" -> "-f") that can be matched during parsing.
Note: Alphabetic characters only
Unlike some other frameworks that might support numeric flags, due to the issues around supporting them & being able to discern if these are arguments (-1/signed int's) or short flags ("--one" -> "-1"), I've determined that I will not be supporting numeric flags as this causes a number of complications/complexities around ARGV parsing.
default: T
Required: ?false?
This is the default value of the flag (String -> "abc", Int32 -> 0, etc) that will be returned if no direct (via parsing CLI args) or indirect (by parsing ENV VAR values) arguments are provided.
While not technically required, it's advised to always set a default when creating flags as if you don't and nothing is parsed/provided when Flag(T)#value! is called it will raise a CliGen::MissingRequiredFlagError exception at the call site.
options: ArrayLiteral(T)|Call
Required: false
This argument sets a static list of accepted arguments to a specific subset of values.
EX: Output format
CliGen.add_global_flag(String,
default: "ecr",
short: "-f",
long: "--format",
description: "Provide the preferred output format",
options: %w[ json yaml ecr ]
)
Note: Support for runtime resolution
While the primary value of this is static arrays of values, you can also delegate the discovery of values to a global method or helper method in your codebase.
HOWEVER, when doing so ALWAYS ensure that you are providing a full path to your method, as the the macro has no way of determining relative paths in your modules. While, provided you are doing this in the same context as the method you are running, this shouldn't be an issue, however best practices dictate you provide a full path just to be careful.
EX: Delegated resolution
module ABC
def self.items
%w[ a b c d e f g taco ]
end
end
CliGen.add_global_flag(String,
default: "a",
short: "-i",
long: "--item",
description: "Provide an item to print",
options: ::ABC.items
)
format: RegexLiteral
Required: false
This exists to handle (for String & Custom Data Types) filtering & checking that an argument being provided by a user is being given in a specific format.
This is something you use when you're only wanting to validate formatting, if you plan to do more specific/extensive validation you should use the validation: field.
EX: Hostname matching
CliGen.add_global_flag(Array(String),
default: [] of String,
short: "-H",
long: "--hostname",
description: "Provide a hostname to do remote work on",
format: /^[a-zA-Z]{3}[0-9]+node[0-9]$/
)
validation: ProcLiteral(T, Bool)
Required: false
Here you can provide a ad-hoc proc for doing validations of a provided
argument that can't easily be done by providing a static options: value.
Note: Explicit input & return type requirement
The explicit input : T & return : Bool turn types are required as the
macros I setup are trying to enforce that both the input & return types
are explicity to avoid truthy & falsey semantics.
EX: checking int range
CliGen.add_global_flag(Int32,
short: "-p",
long: "--port",
description: "Provide a single port to test against",
validation: ->(port : Int32) : Bool do
(UInt16::MIN..UInt16::MAX).includes?(port)
end
)
EX: file existance check
CliGen.add_global_flag(String,
short: "-i",
long: "--filename",
description: "Provide a file that will serve as the input for this program",
validation: ->(file : String) : Bool do
if File.exists?(file)
true
else
STDERR.puts "ERROR : --filename : Provided file (#{file}) does not exist"
false
end
end
)
on_match: ProcLiteral(T, Nil)
Required: false
This option is where you provide the proc for handling ad-hoc
EX: Configuring the stdlib log level
CliGen.add_global_flag(String,
long: "--log-level LEVEL",
short: "-l",
description: "Set the current log level of the stdlib Log library",
options: %w[ trace debug notice info warn error fatal ],
on_match: ->(level : String) do
::Log.setup(level: ::Log::Severity.parse(level))
end
)
EX: Collecting arguments in a global array
module MyModule
MY_ARRAY = [] of String
CliGen.add_global_flag(String,
long: "--filename FILE",
short: "-i",
description: "Provide a single file to check against (repeatable)",
validation: ->(file : String) : Bool do
if File.exists?(file)
true
else
STDERR.puts "ERROR : --filename : #{file} does not exist"
false
end
end,
on_match: ->(file : String) do
::MyModule::MY_ARRAY << file
end
)
end
For more detailed documentation please visit the wiki in the repo. All topics are covered there in much greater detail than inline documentation here