object_rework: working MVP with runtime CommandNode tree
- BaseCommandNode (abstract) / CommandNode(T) split for heterogeneous tree - Flag(T) with compile-time type branching, Parsable/Coercable modules - Arg double-process invariant enforcement - App.generate macro builds CommandNode tree from Command subclasses - Command#initialize generated via macro finished, populates ivars from handler - Reserved -h/--help enforcement in Flag#check! - MatchType::Help added, SubCommand dispatch fixed - design.adoc: added Planned Features (markdown docs, bash completion) - Makefile: tabs, .DEFAULT_GOAL, doc_show target Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+67
@@ -74,3 +74,70 @@ The examples like above provide a "DSL-esk" way of defining:
|
|||||||
==== How it works
|
==== How it works
|
||||||
|
|
||||||
Using crystal macros, you define the shape (arguments/flags, selections, work functions/subcommands, etc
|
Using crystal macros, you define the shape (arguments/flags, selections, work functions/subcommands, etc
|
||||||
|
|
||||||
|
== Planned Features
|
||||||
|
|
||||||
|
=== Markdown Documentation Generation
|
||||||
|
|
||||||
|
Since all command metadata is present in annotations at compile time (`@[CliGen::CommandInfo]`, `@[CliGen::SubCommand]`, `@[CliGen::Argument]`, `@[CliGen::Selection]`), the framework can walk the same structures that `generate.cr` already walks and render them into a Markdown document instead of a `CommandNode` tree.
|
||||||
|
|
||||||
|
The generation would be driven by a `macro finished` block (similar to `generate.cr`) that emits a `self.generate_docs` class method on `App`. This method walks every `Command` subclass and its annotations to produce a structured document.
|
||||||
|
|
||||||
|
Proposed output structure:
|
||||||
|
|
||||||
|
----
|
||||||
|
# <AppName>
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### mycommand
|
||||||
|
<CommandInfo description>
|
||||||
|
|
||||||
|
#### Flags
|
||||||
|
| Flag | Short | Type | Default | Description |
|
||||||
|
|------|-------|------|---------|-------------|
|
||||||
|
| --myvar VAR | -m | Int32 | 23 | ... |
|
||||||
|
|
||||||
|
#### Subcommands
|
||||||
|
- `do_thing` — <description>
|
||||||
|
- Examples: ...
|
||||||
|
----
|
||||||
|
|
||||||
|
Implementation notes:
|
||||||
|
|
||||||
|
* Driven by a `--generate-docs` global flag or a dedicated class method
|
||||||
|
* ECR templates (already pulled in) are the natural rendering mechanism
|
||||||
|
* The same annotation data powers both runtime help output and the doc generator, keeping them in sync automatically
|
||||||
|
|
||||||
|
=== Bash Autocompletion Generation
|
||||||
|
|
||||||
|
Since all command names and flag names are known at compile time, a complete bash completion script can be generated statically — no runtime `--completions` endpoint needed.
|
||||||
|
|
||||||
|
The approach is a hidden `--generate-completion bash` flag (potentially extended to `zsh`/`fish` later) that prints a ready-to-install completion script to stdout.
|
||||||
|
|
||||||
|
Proposed completion script shape:
|
||||||
|
|
||||||
|
[source,bash]
|
||||||
|
----
|
||||||
|
_myapp() {
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
local prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
|
||||||
|
case "${COMP_WORDS[1]}" in
|
||||||
|
mycommand)
|
||||||
|
COMPREPLY=($(compgen -W "--myvar -m --format -f" -- "$cur"))
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COMPREPLY=($(compgen -W "mycommand myothercommand" -- "$cur"))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
complete -F _myapp myapp
|
||||||
|
----
|
||||||
|
|
||||||
|
Implementation notes:
|
||||||
|
|
||||||
|
* Script body generated at compile time via a `macro finished` walk of `Command.subclasses`
|
||||||
|
* `@[CliGen::Selection]` options (`%w[json yaml ecr]`) can be included as valid completions for their flag
|
||||||
|
* Install path: `myapp --generate-completion bash > ~/.bash_completion.d/myapp` or printed with instructions
|
||||||
|
* Same annotation data used by the doc generator, so both stay in sync with the command definition
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ require "./app/generate"
|
|||||||
module CliGen
|
module CliGen
|
||||||
# Root entry point. Holds a flattened copy of all flags from every command
|
# Root entry point. Holds a flattened copy of all flags from every command
|
||||||
# for global-flag matching, then hands off to the matched child CommandNode.
|
# for global-flag matching, then hands off to the matched child CommandNode.
|
||||||
class App < CliGen::BaseCommandNode
|
class App < CliGen::CommandNode(Nil)
|
||||||
@@instance : self?
|
@@instance : self?
|
||||||
|
|
||||||
def initialize(@name, flags : Array(BaseFlag), commands : Array(BaseCommandNode), pre_run_commands : Array(RunCommand), post_run_commands : Array(RunCommand))
|
def initialize(@name, flags : Array(BaseFlag), commands : Array(BaseCommandNode), pre_run_commands : Array(RunCommand), post_run_commands : Array(RunCommand))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ module CliGen
|
|||||||
app_flags = [] of BaseFlag
|
app_flags = [] of BaseFlag
|
||||||
app_commands = [] of BaseCommandNode
|
app_commands = [] of BaseCommandNode
|
||||||
|
|
||||||
|
{% verbatim do %}
|
||||||
{% for cmd in CliGen::Command.subclasses %}
|
{% for cmd in CliGen::Command.subclasses %}
|
||||||
{% cmd_info = cmd.annotation(CliGen::CommandInfo) %}
|
{% cmd_info = cmd.annotation(CliGen::CommandInfo) %}
|
||||||
{% raise "ERROR : CliGen::App.generate : No CliGen::CommandInfo annotation made for #{cmd.name}" unless cmd_info %}
|
{% raise "ERROR : CliGen::App.generate : No CliGen::CommandInfo annotation made for #{cmd.name}" unless cmd_info %}
|
||||||
@@ -23,12 +24,13 @@ module CliGen
|
|||||||
var: {{ var.name.stringify }},
|
var: {{ var.name.stringify }},
|
||||||
short: {% if anno[:short] %} {{anno[:short]}} {% else %} nil {% end %},
|
short: {% if anno[:short] %} {{anno[:short]}} {% else %} nil {% end %},
|
||||||
long: {{ anno[:long] }},
|
long: {{ anno[:long] }},
|
||||||
env_var: {% if anno[:env_var] %} {{anno[:env_var]}} {% else %} {{"#{cmd.name.upcase}_#{var.name.upcase}"}} {% end %},
|
env_var: {% if anno[:env_var] %} {{anno[:env_var]}} {% else %} {{"#{cmd.name.split("::").last.upcase.id}_#{var.name.upcase}"}} {% end %},
|
||||||
description: {{ anno[:description] }},
|
description: {{ anno[:description] }},
|
||||||
default: {% if anno[:default] %} {{anno[:default]}} {% else %} nil {% end %},
|
default: {% unless var.default_value.nil? %} {{var.default_value}} {% else %} nil {% end %},
|
||||||
validate: {% if anno[:validate] %} {{anno[:validate]}} {% else %} nil {% end %},
|
validate: {% if anno[:validate] %} {{anno[:validate]}} {% 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 %}
|
||||||
)
|
)
|
||||||
|
{% debug %}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
# Keep a copy of every flag on the root for global matching
|
# Keep a copy of every flag on the root for global matching
|
||||||
@@ -42,10 +44,12 @@ module CliGen
|
|||||||
post_run_commands: cmd_post_run_cmds,
|
post_run_commands: cmd_post_run_cmds,
|
||||||
description: {{cmd_info[:description]}}
|
description: {{cmd_info[:description]}}
|
||||||
)
|
)
|
||||||
|
{% debug %}
|
||||||
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
new(
|
new(
|
||||||
name: ::PROGRAM_NAME,
|
name: File.basename(::PROGRAM_NAME),
|
||||||
flags: app_flags,
|
flags: app_flags,
|
||||||
commands: app_commands,
|
commands: app_commands,
|
||||||
pre_run_commands: [] of Proc(Nil),
|
pre_run_commands: [] of Proc(Nil),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ require "./command/subcommand"
|
|||||||
module CliGen
|
module CliGen
|
||||||
class Command
|
class Command
|
||||||
macro inherited
|
macro inherited
|
||||||
|
{% verbatim do %}
|
||||||
macro finished
|
macro finished
|
||||||
def initialize(*, handler : CliGen::BaseCommandNode)
|
def initialize(*, handler : CliGen::BaseCommandNode)
|
||||||
{% verbatim do %}
|
{% verbatim do %}
|
||||||
@@ -20,16 +21,17 @@ module CliGen
|
|||||||
if flg = handler.flags.find{|f| f.var == {{var.name.stringify}} && f.long == {{anno[:long]}}}
|
if flg = handler.flags.find{|f| f.var == {{var.name.stringify}} && f.long == {{anno[:long]}}}
|
||||||
@{{var.id}} = flg.as(CliGen::Flag({{var.type}})).value!
|
@{{var.id}} = flg.as(CliGen::Flag({{var.type}})).value!
|
||||||
else
|
else
|
||||||
raise "ERROR : {{@type.name}}#{{@def.name}} : No flag found for \"{{var.name}}\"?"
|
raise "ERROR : {{@type.name}}\#{{@def.name}} : No flag found for \"{{var.name}}\"?"
|
||||||
end
|
end
|
||||||
{% else %}
|
{% 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 %}
|
{% 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}}
|
@{{var.id}} = {{var.default_value}}
|
||||||
{% end %}
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
{% end %}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
module CliGen
|
module CliGen
|
||||||
class Command
|
class Command
|
||||||
macro argument(variable, short, long, description, validation = nil)
|
macro argument(variable, long, description, short = nil, validation = nil, on_match = nil)
|
||||||
{% raise "ERROR : CliGen::Command.argument : First argument (#{variable}) must be a TypeDeclaration (ex: '<var> : <type> [= val]')" unless variable.is_a? TypeDeclaration %}
|
{% raise "ERROR : CliGen::Command.argument : First argument (#{variable}) must be a TypeDeclaration (ex: '<var> : <type> [= val]')" unless variable.is_a? TypeDeclaration %}
|
||||||
{% name = variable.name %}
|
{% name = variable.var %}
|
||||||
{% type = variable.type %}
|
{% type = variable.type %}
|
||||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided short must be a string" unless short.is_a? StringLiteral || string == nil %}
|
{% 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}) : 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 %}
|
{% raise "ERROR : CliGen::Command.argument(#{name}) : You must provide a short or long" unless long || short %}
|
||||||
{% raise "ERROR : CliGen::Command.argument(#{name}) : You must provide a description" unless description %}
|
{% 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 %}
|
{% raise "ERROR : CliGen::Command.argument(#{name}) : Provided description must be a String" unless description.is_a? StringLiteral %}
|
||||||
|
{% unless on_match.nil? %}
|
||||||
|
{% puts on_match %}
|
||||||
|
{% 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 %}
|
||||||
|
{% end %}
|
||||||
{% unless validation.nil? %}
|
{% unless validation.nil? %}
|
||||||
{% 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 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 == Bool %}
|
||||||
@@ -23,7 +30,7 @@ module CliGen
|
|||||||
@[CliGen::Argument(short: {{short}}, long: {{long}}, description: {{description}}, validation: {{validation}}, on_match: {{on_match}})]
|
@[CliGen::Argument(short: {{short}}, long: {{long}}, description: {{description}}, validation: {{validation}}, on_match: {{on_match}})]
|
||||||
@{{variable}}
|
@{{variable}}
|
||||||
|
|
||||||
def {{variable.name}}= (value : {{type}})
|
def {{variable.var}}= (value : {{type}})
|
||||||
{% unless validation.nil? %}
|
{% unless validation.nil? %}
|
||||||
raise "ERROR : #{@type.name}##{@def.name} : Provided value #{value} is not passing validation" unless {{validation}}.call(value)
|
raise "ERROR : #{@type.name}##{@def.name} : Provided value #{value} is not passing validation" unless {{validation}}.call(value)
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module CliGen
|
module CliGen
|
||||||
class Command
|
class Command
|
||||||
macro subcommand(func, description, examples = nil, &block)
|
macro subcommand(func, description, examples = nil, &block)
|
||||||
{% raise "ERROR : CliGen::Command.subcommand : First argument must be a TypeDeclaration (ex: '<var> : <type>')" unless variable.is_a? TypeDeclaration %}
|
{% raise "ERROR : CliGen::Command.subcommand : First argument must be a TypeDeclaration, or Call (ex: '<var> : <type>' or <var>)" unless func.is_a? TypeDeclaration || func.is_a? Call %}
|
||||||
{% raise "ERROR : CliGen::Command.subcommand : You must provide a description" unless description %}
|
{% raise "ERROR : CliGen::Command.subcommand : You must provide a description" unless description %}
|
||||||
{% raise "ERROR : CliGen::Command.subcommand : Provided description must be a String" unless description.is_a? StringLiteral %}
|
{% raise "ERROR : CliGen::Command.subcommand : Provided description must be a String" unless description.is_a? StringLiteral %}
|
||||||
{% unless examples.nil? %}
|
{% unless examples.nil? %}
|
||||||
|
|||||||
+24
-10
@@ -1,4 +1,5 @@
|
|||||||
require "./global_flag"
|
require "./global_flag"
|
||||||
|
require "./match_type"
|
||||||
require "./flag"
|
require "./flag"
|
||||||
require "./arg"
|
require "./arg"
|
||||||
require "ecr"
|
require "ecr"
|
||||||
@@ -29,7 +30,7 @@ module CliGen
|
|||||||
@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)
|
@flags = (flags + CliGen::GLOBAL_FLAGS + @commands.flat_map(&.flags)).uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
def help : String
|
def help : String
|
||||||
@@ -112,17 +113,18 @@ module CliGen
|
|||||||
|
|
||||||
# Converts String array to Arg array and hands off to the typed process method
|
# Converts String array to Arg array and hands off to the typed process method
|
||||||
def process(args : Array(String)) : Nil
|
def process(args : Array(String)) : Nil
|
||||||
new_args = args.each_with_index.map { |arg, i| Arg.new(value: arg, index: i) }.to_a
|
new_args = args.each_with_index.map { |arg, i| CliGen::Arg.new(value: arg, index: i) }.to_a
|
||||||
process(new_args)
|
process(new_args)
|
||||||
end
|
end
|
||||||
|
|
||||||
abstract def subcommands : Array(SubCommandInfo)
|
abstract def subcommands : Array(SubCommandInfo)
|
||||||
abstract def check! : Nil
|
abstract def check! : Nil
|
||||||
abstract def process(args : Array(Arg)) : Nil
|
abstract def process(args : Array(CliGen::Arg)) : Nil
|
||||||
end
|
end
|
||||||
|
|
||||||
class CommandNode(T) < BaseCommandNode
|
class CommandNode(T) < BaseCommandNode
|
||||||
def subcommands : Array(SubCommandInfo)
|
def subcommands : Array(SubCommandInfo)
|
||||||
|
{% begin %}
|
||||||
{% subcmds = T.methods.select(&.annotation(CliGen::SubCommand)) %}
|
{% subcmds = T.methods.select(&.annotation(CliGen::SubCommand)) %}
|
||||||
{% if subcmds.empty? %}
|
{% if subcmds.empty? %}
|
||||||
[] of SubCommandInfo
|
[] of SubCommandInfo
|
||||||
@@ -138,6 +140,7 @@ module CliGen
|
|||||||
{% end %}
|
{% end %}
|
||||||
]
|
]
|
||||||
{% end %}
|
{% end %}
|
||||||
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
def check! : Nil
|
def check! : Nil
|
||||||
@@ -148,7 +151,7 @@ module CliGen
|
|||||||
if subcommands.empty? && !{{T.has_method?(:main)}}
|
if subcommands.empty? && !{{T.has_method?(:main)}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def process(args : Array(Arg)) : Nil
|
def process(args : Array(CliGen::Arg)) : Nil
|
||||||
check!
|
check!
|
||||||
passed_execution = false
|
passed_execution = false
|
||||||
matched_subcommand : String? = nil
|
matched_subcommand : String? = nil
|
||||||
@@ -162,7 +165,16 @@ module CliGen
|
|||||||
case match = find_match(arg.value)
|
case match = find_match(arg.value)
|
||||||
when BaseCommandNode
|
when BaseCommandNode
|
||||||
# Hand off remainder to the child; we're done at this level
|
# Hand off remainder to the child; we're done at this level
|
||||||
match.process(args.reject(&.processed?))
|
{% begin %}
|
||||||
|
case match
|
||||||
|
{% for cls in CliGen::Command.subclasses %}
|
||||||
|
when CliGen::CommandNode({{cls.name}})
|
||||||
|
match.as(CommandNode({{cls}})).process(args.reject(&.processed?))
|
||||||
|
{% end %}
|
||||||
|
else
|
||||||
|
raise "ERROR : Couldn't find thing"
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
passed_execution = true
|
passed_execution = true
|
||||||
|
|
||||||
when BaseFlag
|
when BaseFlag
|
||||||
@@ -183,9 +195,9 @@ module CliGen
|
|||||||
if regex_match = CliGen::Regex::FLAG_WITH_ARG.match(arg.value)
|
if regex_match = CliGen::Regex::FLAG_WITH_ARG.match(arg.value)
|
||||||
case flag_match = find_match(regex_match["flag"])
|
case flag_match = find_match(regex_match["flag"])
|
||||||
when BaseFlag
|
when BaseFlag
|
||||||
flag_match.process([Arg.new(value: regex_match["arg"], index: arg.index)])
|
flag_match.process([CliGen::Arg.new(value: regex_match["arg"], index: arg.index)])
|
||||||
else
|
else
|
||||||
raise "ERROR : CommandNode(#{@name}).run : No flag matched '#{regex_match["flag"]}'"
|
raise "ERROR : CommandNode(#{@name}).process : No flag matched '#{regex_match["flag"]}'"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise "Oh good, you broke regex. How the hell did it match in find_match but not above? What the hell is going on"
|
raise "Oh good, you broke regex. How the hell did it match in find_match but not above? What the hell is going on"
|
||||||
@@ -201,19 +213,20 @@ module CliGen
|
|||||||
abort "#{CliGen::APPNAME}: cannot bundle flag that requires an argument: #{flag}" if match.requires_arg?
|
abort "#{CliGen::APPNAME}: cannot bundle flag that requires an argument: #{flag}" if match.requires_arg?
|
||||||
match.process
|
match.process
|
||||||
when MatchType::NoMatch
|
when MatchType::NoMatch
|
||||||
raise "ERROR : CommandNode(#{@name}).run : No match for '#{flag}'"
|
raise "ERROR : CommandNode(#{@name}).process : No flag match for '#{flag}'"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
when MatchType::NoMatch
|
when MatchType::NoMatch
|
||||||
raise "ERROR : CommandNode(#{@name}).run : No match for '#{arg.value}'"
|
raise "ERROR : CommandNode(#{@name}).process : No command, subcommand or flag match for '#{arg.value}'"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@post_run_commands.each(&.call)
|
@post_run_commands.each(&.call)
|
||||||
|
|
||||||
|
{% unless T == Nil %}
|
||||||
unless passed_execution
|
unless passed_execution
|
||||||
cls = T.new(self)
|
cls = T.new(handler: self.as(CliGen::BaseCommandNode))
|
||||||
{% subcmds = T.methods.select(&.annotation(CliGen::SubCommand)) %}
|
{% subcmds = T.methods.select(&.annotation(CliGen::SubCommand)) %}
|
||||||
{% begin %}
|
{% begin %}
|
||||||
case matched_subcommand
|
case matched_subcommand
|
||||||
@@ -230,6 +243,7 @@ module CliGen
|
|||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
|
{% end %}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+10
-3
@@ -17,7 +17,7 @@ module CliGen
|
|||||||
@description : String
|
@description : String
|
||||||
)
|
)
|
||||||
# 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.includes(" ")
|
if @long.includes?(" ")
|
||||||
@long_key = @long.split(" ").first
|
@long_key = @long.split(" ").first
|
||||||
else
|
else
|
||||||
@long_key = @long
|
@long_key = @long
|
||||||
@@ -155,14 +155,21 @@ module CliGen
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def check!
|
def check! : Nil
|
||||||
raise "ERROR : Flag({{T}}, long: #{@long})#check! : -h is reserved for internal help usage" if @short == "-h"
|
raise "ERROR : Flag({{T}}, long: #{@long})#check! : -h is reserved for internal help usage" if @short == "-h"
|
||||||
raise "ERROR : Flag({{T}}, long: #{@long})#check! : --help is reserved for internal help usage" if @long_key == "--help"
|
raise "ERROR : Flag({{T}}, long: #{@long})#check! : --help is reserved for internal help usage" if @long_key == "--help"
|
||||||
end
|
end
|
||||||
|
|
||||||
private def coerce(raw : String) : T
|
private def coerce(raw : String) : T
|
||||||
{% if T == Bool %}
|
{% if T == Bool %}
|
||||||
raw == "true" || raw == "1"
|
case raw
|
||||||
|
when "t","true","1"
|
||||||
|
true
|
||||||
|
when "f","false","0"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
raise "ERROR"
|
||||||
|
end
|
||||||
{% elsif T == Int32 %}
|
{% elsif T == Int32 %}
|
||||||
raw.to_i
|
raw.to_i
|
||||||
{% elsif T == Time %}
|
{% elsif T == Time %}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Command: <% @name %>
|
Command: <%= @name %>
|
||||||
<%- unless @description.nil? -%>
|
<%- unless @description.nil? -%>
|
||||||
Description: <%= @description %>
|
Description: <%= @description %>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
@@ -7,9 +7,9 @@ Flags:
|
|||||||
---------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
<%- @flags.each do |flag| -%>
|
<%- @flags.each do |flag| -%>
|
||||||
<%- unless flag.short.nil? -%>
|
<%- unless flag.short.nil? -%>
|
||||||
<%= "%-10s %s" % ["#{flag.short},#{flag.long}", flag.description] %>
|
<%= "%-15s %s" % ["#{flag.short.not_nil!.strip},#{flag.long.strip}", flag.description.strip] %>
|
||||||
<%- else -%>
|
<%- else -%>
|
||||||
<%= "%-10s %s" % [flag.long, flag.description] %>
|
<%= "%-15s %s" % [flag.long.strip, flag.description.strip] %>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
@@ -25,7 +25,7 @@ Other Commands
|
|||||||
SubCommands of <%= @name %>:
|
SubCommands of <%= @name %>:
|
||||||
---------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
<%- subcommands.each do |cmd| -%>
|
<%- subcommands.each do |cmd| -%>
|
||||||
<%= "%-10 %s" % [cmd.name, cmd.description] %>
|
<%= "%-10s %s" % [cmd.name, cmd.description] %>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
|
||||||
<%- cmds = subcommands.select{|c| ! c.examples.nil? } -%>
|
<%- cmds = subcommands.select{|c| ! c.examples.nil? } -%>
|
||||||
|
|||||||
Reference in New Issue
Block a user