jwaldrip / admiral.cr Goto Github PK
View Code? Open in Web Editor NEWA robust DSL for writing command line interfaces written in Crystal.
Home Page: https://jwaldrip.github.com/admiral.cr
License: MIT License
A robust DSL for writing command line interfaces written in Crystal.
Home Page: https://jwaldrip.github.com/admiral.cr
License: MIT License
Hi,
currently, the help of the subcommands doesn't show the global flags/arguments of the parent.
Taking the following example:
equire "admiral"
class ParentCmd < Admiral::Command
class ChildCmd < Admiral::Command
define_help description: "Execute a subcommand."
define_argument childarg : String, description: "Arg for child", required: true
def run
puts "in ChildCmd with #{arguments.childarg}"
end
end
register_sub_command child : ChildCmd, "Child Command"
define_flag parentflag : Bool, description: "Parent flag", default: false, short: p
define_help description: "Test subcommands help with global flag"
def run
puts help
end
end
ParentCmd.run
I would expect the parentflag
to show up in the ChildCmd
help page:
$ ./testsub --help
Usage:
./testsub [flags...] [arg...]
Test subcommands help with global flag
Flags:
--help # Displays help for the current command.
--parentflag, -p # Parent flag
Subcommands:
child # Execute a subcommand.
$ /testsub child --help
Usage:
./testsub child [flags...] <childarg> [arg...]
Execute a subcommand.
Flags:
--help # Displays help for the current command.
Arguments:
childarg (required) # Arg for child
This issue was created as an upgrade status for Crystal 0.27.0
Hi @jwaldrip,
With 1.8.0
flags should be defined after the called command.
I mean that this command is working
bin/droplet create -l ruby -f sinatra
but this does not
bin/droplet -l ruby -f sinatra create
in https://github.com/waghanza/http-benchmark/blob/cloudify/tools/providers/digitalocean.cr#L31
Was is a design choice ?
Regards,
When command or subcommand is unknown the default behavior is to print help and exit 0
:
Usage:
/home/nonroot/.asdf/installs/squarectl/1.4.0/bin/squarectl compose [flags...] [arg...]
Run Docker Compose commands
Flags:
--config, -c (default: "squarectl.yml") # Path to config file
--help # Displays help for the current command.
Subcommands:
build # Run docker-compose build
clean # Run docker-compose clean
config # Run docker-compose config
down # Run docker-compose down
exec # Run docker-compose exec
ps # Run docker-compose ps
setup # Run docker-compose setup
start # Run docker-compose start
stop # Run docker-compose stop
top # Run docker-compose top
up # Run docker-compose up
* Done!
Job succeeded
It would be nice to notify user about the missing command, something like Unknow "squarectl compose push" command
and to exit 1
,
Thank you!
Note: this issue has been found while working on https://github.com/jbox-web/squarectl
Doing this in the code:
define_flag context : String,
description: "",
default: Dir.current,
short: x
Shows up in the terminal like this:
--context, -x (default: Dir.current)
class Converter < Admiral::Command
define_flag input : String,
description: "Whatever",
default: "/dev/stdin",
long: input,
short: i,
required: true
def run
puts "Input is #{flags.input}"
end
end
-i=foo
--input=foo
-i foo
--input foo
-ifoo
Required arguments don't raise an error when no argument is passed.
Add the following line at the bottom of examples/complex.cr
to reproduce this bug.
Complex.run ""
There is no error even though the argument required
, which is required
, is not passed. The argument is defined below.
define_argument required : Int32, required: true
This is my shards.yaml
.
name: kato
version: 0.1.0
authors:
- Ravern Koh <[email protected]>
description: |
Toy virtual machine written in Crystal
targets:
kato:
main: src/kato/cli.cr
dependencies:
admiral:
github: jwaldrip/admiral.cr
development_dependencies:
cake:
git: https://github.com/ravernkoh/cake.git
branch: master
license: GPL-3.0
Edit: Made a mistake in the example to reproduce, was originally Complex.run —required woah”
.
Hello,
I'm unhappy about how to keep active some "global" flags when using subcommands
This is an example where a --verbose
flag is setup on top of every subcommands:
require "admiral"
module Log
class_property verbose = false
end
class HelloWorld < Admiral::Command
def run
Log.verbose = self.flags.verbose
puts "Hello World"
end
class SubCommand < Admiral::Command
def run
Log.verbose = self.parent.as(HelloWorld).flags.verbose
puts "SubCommand"
end
end
define_flag verbose : Bool, default: false, short: v
register_sub_command sub_command, type: SubCommand
end
HelloWorld.run
My main problem is I need to repeat the code Log.verbose = self.parent.as(HelloWorld).flags.verbose
on every run
for each subcommands or the flag will be ignored.
Would it be great if we can put a block on define_flag
like this:
define_flag(verbose : Bool, default: false, short: v){ |flag| Log.verbose = flag }
This code would be triggered even if run
of the command is not called due to subcommand
It would be great to have a simple way to handle if no argument is provided.
Ex:
define_argument name : String,
description: "The name of the new project",
required: false,
empty: "Please provide the name"
I wrote a small application and used the define_version to display a literal string of the version. Since I built the app with crystal init app, there is already a module in version.cr that contains the version number. I tried this and it actually worked:
define_version "app #{App::VERSION}"
also:
define_version <<-VERSION
app #{App::VERSION}
Copyright ...
License ...
VERSION
Is this something you would consider including in the README? For me this means having only one place to bump versions.
Great shard. Thanks
in macro 'define_version' admiral/src/admiral/command/version.cr:19, line 5:
1.
2. define_flag __version__ : Bool, long: version, short: nil
3. protected def __run__ : Nil
4. if flags.__version__
> 5. puts version
6. exit
7. else
8. previous_def
9. end
10. end
11.
12.
13. def version
14. "0.0.1"
15. end
16.
instantiating 'puts(String)'
in lib/admiral/src/admiral/command.cr:79: undefined method 'puts' for Bool (compile-time type is (Bool | IO::FileDescriptor))
io.puts(*args)
^~~~
================================================================================
Bool trace:
lib/admiral/src/admiral/command.cr:77
case (io = @output_io)
^~
lib/admiral/src/admiral/command.cr:77
case (io = @output_io)
^~~~~~~~~~
lib/admiral/src/admiral/command.cr:8
@output_io : IO::FileDescriptor | Bool = STDOUT
^~~~~~~~~~
Hey,
If one defines some flags and arguments, like:
define_flag a
define_flag b
define_argument c
Then upon calling:
cmd subcmd -a one -b two three four
It would be great if there was a way to automatically remove parsed parts out of ARGV or original array, so that only "four" remains in it.
Currently, it appears as if original contents are never modified.
Couldn't get this to work, what am I missing?
# hello.cr
require "admiral"
class Hello < Admiral::Command
class Planetary < Admiral::Command
def run
puts "Hello World"
end
end
class Municipality < Admiral::Command
define_help description: "City"
class Zip < Admiral::Command
def run
puts "Hello City, Zip!"
end
end
def run
puts help
end
register_sub_command zip : Zip, description: "Nested subcommand"
end
register_sub_command planet : Planetary, description: ""
register_sub_command city : Municipality, description: ""
define_help description: "Testing subcommands"
def run
puts help
end
end
Hello.run
In words, the expectation was to see:
./hello city zip
# Hello City, Zip!
But it never hits the Zip class.
This is probably related to #15 .
Example code below:
require "admiral"
class NegativeNumber < Admiral::Command
define_flag typed_int : Int32, required: true
define_argument typed_arg : Int32, required: true
def run
puts flags.typed_int
puts arguments.typed_arg
end
end
puts "Negative flag:"
NegativeNumber.run "2 --typed-int -1" # negative number works for flags
puts "Negative argument:"
NegativeNumber.run "-2 --typed-int 1" # negative number produces a traceback for arguments
The typed_arg
argument is defined as Int32, which can be positive or negative. But if I try to input a negative argument to it, a traceback is shown.
The output of the above code:
Negative flag:
-1
2
Negative argument:
1
Unhandled exception: Nil assertion failed (NilAssertionError)
from /usr/lib/crystal/nil.cr:106:5 in 'not_nil!'
from src/negative.cr:5:3 in 'typed_arg'
from src/negative.cr:9:5 in 'run'
from src/negative.cr:3:1 in 'parse_and_run'
from src/negative.cr:3:1 in 'run'
from src/negative.cr:16:1 in '__crystal_main'
from /usr/lib/crystal/crystal/main.cr:97:5 in 'main_user_code'
from /usr/lib/crystal/crystal/main.cr:86:7 in 'main'
from /usr/lib/crystal/crystal/main.cr:106:3 in 'main'
from __libc_start_main
from _start
from ???
require "admiral"
class Main < Admiral::Command
define_flag bloop : Array(String),
default: [] of String,
required: false
def run
puts flags.bloop
end
end
Main.run
> bin/cli_test
Flag required: --bloop
Am I doing something wrong?
Actually this seems a bit odd. The default must be nil
or not a boolean
?
https://github.com/jwaldrip/admiral.cr/blob/master/src/admiral/command/flag.cr#L288
edit:
There also seems to be a related issue here where the type specification is change to type | nil
unless the type is set to required:
https://github.com/jwaldrip/admiral.cr/blob/master/src/admiral/command/flag.cr#L321
So even when the first issue is fixed, the type of bloop is Array(String) | Nil
instead of the requested Array(String)
I just tried to create a flag with a long version that didn't match the regex checked here, and the error message references a variable that isn't defined:
define_flag skip_install : Bool,
^~~~~~~~~~~
in lib/admiral/src/admiral/command/flag.cr:285: undefined macro variable 'long_reg'
raise "The long flag #{@type}(#{long}) must match the regex: #{long_reg}"
^~~~~~~~
I was still able to figure out what needed to be fixed in my code, but thought you would want to know.
When a command has required arguments and help defined, it is not possible to get help without also specifying the required arguments at the command line. This is not an expected behaviour in my opinion.
# test.cr
require "admiral"
class TestCommand < Admiral::Command
define_help description: "does foobar"
define_argument foo : String, required: true
define_argument bar : String, required: true
def run
end
end
TestCommand.run
$ ./crystal build test.cr
$ ./test --help
Missing required attribute: <foo>
$ ./test one --help
Missing required attribute: <bar>
$ ./test one two --help
Usage:
./test [flags...] <foo> <bar> [arg...]
does foobar
Flags:
--help # Displays help for the current command.
Arguments:
foo (required)
bar (required)
So, I want one of two flags to be required, not both, not none, but this or the other
flag1
flag2
raise unless flag1 || flag2
This is what I use now, but it lacks "promise" that one will be required.
define_help description: "Generate SQL files to load content data to DB"
define_flag account_id : Int32, required: true, description: "Account ID to process"
Calling with --help gives:
Flag: --account-id is required (Admiral::Error)
0x10fdec935: *CallStack::unwind:Array(Pointer(Void)) at ??
0x10fdec8d1: *CallStack#initialize:Array(Pointer(Void)) at ??
0x10fdec8a8: *CallStack::new:CallStack at ??
0x10fde93d1: *raise<Admiral::Error>:NoReturn at ??
Setting required: false solves the issue, but requires extra manual check on command run.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.