Modified a few things:
- Finished implementing relative date parsing and migrated date parsing from Flag(T) to it's own dedicated module. Additionally added a the ability to do "recursive" relative operations for time searching. - Additionally made a Struct & Enum for containing relative operations around dates. - Added additional checks in App & CommandNode(T) around checking env_vars of flags & checking for duplicate commands - Added a CommandMeta record to contain the classname of the CommandNode(T) - Seperated out all of the monolitic files (command_node.cr & file.cr). Moved the base classes and records into their own files in the dir with the same name of their generic counterparts. - Fixed a number of macro related bugs around Command.argument & CliGen.add_global_flag - My ass hurts from sitting here for hours and doing these changes and arguing with claude over what needs to be done. Fun tho.
This commit is contained in:
@@ -27,9 +27,26 @@ require "cligen"
|
||||
class Greet < CliGen::Command
|
||||
@[CliGen::Argument(short: "-n", long: "--name VALUE", description: "Name to greet")]
|
||||
@name : String = "world"
|
||||
|
||||
argument(otherval : String = "abc",
|
||||
long: "--other",
|
||||
short: "-o",
|
||||
options: %w[ abc def ghi ],
|
||||
description: "This provides a way of setting the second string taht is printed"
|
||||
)
|
||||
|
||||
argument(myvars : Array(String) = [ "a" ],
|
||||
short: "-m",
|
||||
options: %w[ a b c ],
|
||||
description: "Provide multiple things to be printed out in the main function"
|
||||
)
|
||||
|
||||
def main
|
||||
puts "Hello, #{@name}!"
|
||||
puts "1) Hello, #{@name}!"
|
||||
puts "2) #{@otherval}"
|
||||
@myvars.each_with_index do |var, index|
|
||||
puts "%d) %s" % [ 3 + index, var ]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,12 +54,84 @@ CliGen::App.process
|
||||
```
|
||||
|
||||
```
|
||||
$ myapp --name Alice
|
||||
Hello, Alice!
|
||||
$ myapp greet --name Alice -m a a -m a,a,b
|
||||
1) Hello, Alice!
|
||||
2) abc
|
||||
3) a
|
||||
4) a
|
||||
5) a
|
||||
6) a
|
||||
7) b
|
||||
```
|
||||
|
||||
Full API documentation and design notes are in [`design.adoc`](design.adoc).
|
||||
|
||||
## Architecture
|
||||
|
||||
As a short overview, this projects makes HEAVY use of Crystal macros to learn the shape of your project & command subclasses.
|
||||
|
||||
Subclassing to `CliGen::Command` injects macros into your class that provides you user friendly DSLs/Macros for defining arguments/flags & subcommands. This is later used in the library `src/cligen/app/generate.cr` to generate a object graph of your commands and all annotated "arguments/flags" and stores them in a tree from the App object itself.
|
||||
|
||||
This allows the project to "learn" your project & generate a command tree from the defined data.
|
||||
|
||||
The MAJORITY of stdlib types (Int*, Float*, String, Bool & Time) are all supported in-place (as these are the primary data-types you might try to comsume from the CLI. However, custom data types are supported provided you extend the class's metaclass with `CliGen::Coercable` && `CliGen::Parsable` modules and define the `self.parse_args(args : Array(CliGen::Arg)` and `self.coerce(arg : String)` class methods.
|
||||
|
||||
EX:
|
||||
```crystal
|
||||
module MyModule
|
||||
class MyData
|
||||
extend CliGen::Parsable
|
||||
extend CliGen::Coercable
|
||||
|
||||
@value : Int32
|
||||
|
||||
def initialize(value : String)
|
||||
@value = value.to_i32
|
||||
end
|
||||
|
||||
def self.parse_args(args : Array(CliGen::Arg))
|
||||
arg = args.first
|
||||
# Mark the argument as processed
|
||||
arg.processed
|
||||
new(arg.value)
|
||||
|
||||
end
|
||||
|
||||
def self.coerce(arg : String)
|
||||
new(arg)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
Coercable Method:
|
||||
-----------------
|
||||
```crystal
|
||||
def self.coerce(arg : String)
|
||||
new(arg)
|
||||
end
|
||||
```
|
||||
|
||||
The coerce method provides you the ability to parse a single string value into your class/datatype. This is generally only used when parsing from ENV VAR and when being used by parsing from an Array(T) type.
|
||||
|
||||
This should only be used in the case you need a simple datatype that can be learned from a single string.
|
||||
|
||||
Parsable Method:
|
||||
----------------
|
||||
```crystal
|
||||
def self.parse_args(args : Array(CliGen::Arg))
|
||||
arg = args.first
|
||||
# Mark the argument as processed (required)
|
||||
arg.processed
|
||||
new(arg.value)
|
||||
end
|
||||
```
|
||||
|
||||
This method is used the most and is used for when using the bare class as the generic type in the Flag(T). With this the Flag(T) will collect all provided arguments (cli arguments that weren't determined to be flags or subcommands) and pass them to your parse_args method so that you can parse them how you see fit and determine if the args provided by the user are enough and to be able to raise if data is not provided correctly/in the right format.
|
||||
|
||||
This gives the framework a way to allow you to extend the parser in your own custom way to allow for a "custom" format to be procesed. However, it's very strict and you MUST properly mark the arguments as processed so that the mainloop won't double-process arguments passed to your custom parser. However, this won't happen as in the Flag(T) I am doing a check to ensure that args were processed after passing it to your code.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user