Proper publication
This commit is contained in:
commit
751cd63bc1
7
LICENSE.txt
Normal file
7
LICENSE.txt
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright 2023 Benjamin Dasnois
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
78
README.md
Normal file
78
README.md
Normal file
@ -0,0 +1,78 @@
|
||||
# Introduction
|
||||
This library helps you create command line utilities by providing a command line parser and handler.
|
||||
It takes inspiration from [tink_cli](https://www.github.com/haxetink/tink_cli) but does not provide prompts nor (currently) use building macros.
|
||||
|
||||
## Why re-build with less features?
|
||||
Simply because I tried adding tink_cli to one of my projects and it started to severely impair building and VS Code integration.
|
||||
It may not really have been tink_cli's bad but it felt to me like it was too much overhead for what I needed.
|
||||
|
||||
# How to use
|
||||
Your users invoke your application and specify an action to run.
|
||||
You simply write a handler class and mark functions that should serve as action and the one that should serve as the default.
|
||||
Use `@defaultCommand` and `@command` annotation to do so :
|
||||
|
||||
```haxe
|
||||
class CliManager {
|
||||
public function new() {
|
||||
}
|
||||
|
||||
@defaultCommand
|
||||
public function startServer() {
|
||||
}
|
||||
|
||||
@command
|
||||
public function addUser() {
|
||||
}
|
||||
|
||||
@command
|
||||
public function addOrganisation() {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Once you've done that, call `epikowa.cli.Cli.parse`:
|
||||
|
||||
```haxe
|
||||
class Main {
|
||||
static function main() {
|
||||
Cli.parse(Sys.args(), new CliManager());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Your users can then call:
|
||||
|
||||
```bash
|
||||
% yourapp # Runs CliManager.startServer
|
||||
% yourapp addUser # Run CliManager.addUser
|
||||
% yourapp addOrganisation # CliManager.addOrganisation
|
||||
```
|
||||
|
||||
## Flags and parameters
|
||||
By setting a flag on the command line, your user can set a value on your handler before the action is executed.
|
||||
For example, if you have the following:
|
||||
|
||||
```haxe
|
||||
class CliManager {
|
||||
public function new() {
|
||||
}
|
||||
|
||||
@flag
|
||||
var username:String;
|
||||
|
||||
@defaultCommand
|
||||
public function sayHello() {
|
||||
Sys.println('Hello ${username}');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
you user may run:
|
||||
|
||||
```sh
|
||||
% yourapp --username Benjamin
|
||||
```
|
||||
|
||||
and sayHello will be called with username set to `Benjamin`.
|
||||
|
||||
___At the moment, only strings are supported for flag's value. They have to be provided. No shorthands or aliases are supported at the moment.___
|
||||
13
haxelib.json
Normal file
13
haxelib.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "epikowa_cli",
|
||||
"url" : "https://gitlab2.latearrivalstud.io/epikowa/epikowa_cli",
|
||||
"license": "CECILL-B",
|
||||
"tags": ["cli"],
|
||||
"description": "Parse and handle CLI commands.",
|
||||
"version": "0.5.0",
|
||||
"classPath": "src/",
|
||||
"releasenote": "Initial release",
|
||||
"contributors": ["Benjamin Dasnois"],
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
||||
108
src/epikowa/cli/Cli.hx
Normal file
108
src/epikowa/cli/Cli.hx
Normal file
@ -0,0 +1,108 @@
|
||||
package epikowa.cli;
|
||||
|
||||
import haxe.macro.PositionTools;
|
||||
import haxe.macro.Expr.Position;
|
||||
import haxe.rtti.Meta;
|
||||
|
||||
typedef Error = haxe.macro.Expr.Error;
|
||||
/**
|
||||
This class offers CLI tools such as command-line parsing & terminal based UIs
|
||||
**/
|
||||
@:nullSafety(Strict)
|
||||
class Cli {
|
||||
static var noOp:Void->Void = () -> {
|
||||
};
|
||||
|
||||
/**
|
||||
Parses a command and runs associated functions.
|
||||
**/
|
||||
public static function parse(params:Array<String>, cliHandler:Any) {
|
||||
var action:Null<String> = null;
|
||||
var params = Lambda.array(params);
|
||||
|
||||
var handlerClass = getHandlerClass(cliHandler);
|
||||
while (params.length > 0) {
|
||||
var param = params.shift();
|
||||
if (param == null) {
|
||||
throw new Error( 'Param can\'t be null', PositionTools.here());
|
||||
}
|
||||
if (param.indexOf('-') == 0) {
|
||||
handleFlag(param, params, cliHandler);
|
||||
} else {
|
||||
if (action != null) {
|
||||
throw new Error('Only one action should be provided', PositionTools.here());
|
||||
}
|
||||
|
||||
action = param;
|
||||
}
|
||||
}
|
||||
var meta = Meta.getFields(handlerClass);
|
||||
trace(meta);
|
||||
if (action == null) {
|
||||
action = findDefaultCommand(meta);
|
||||
}
|
||||
|
||||
if (action == null) {
|
||||
throw new Error('No action provided and no default command set', PositionTools.here());
|
||||
}
|
||||
|
||||
if (cliHandler == null) {
|
||||
throw new Error('cliHandler must not be null', PositionTools.here());
|
||||
}
|
||||
|
||||
if (!(Reflect.hasField(meta, action) && (Reflect.hasField(Reflect.field(meta, action), 'command') || Reflect.hasField(Reflect.field(meta, action), 'defaultCommand')))) {
|
||||
throw new Error('this action does not exist', PositionTools.here());
|
||||
}
|
||||
|
||||
Reflect.callMethod(cliHandler, Reflect.field(cliHandler, action ?? '') ?? noOp, []);
|
||||
}
|
||||
|
||||
static function getFlags(meta:Dynamic<Dynamic<Array<Dynamic>>>) {
|
||||
var flags:Array<String> = [];
|
||||
for (fieldName in Reflect.fields(meta)) {
|
||||
var field = Reflect.field(meta, fieldName);
|
||||
if (Reflect.hasField(field, 'flag')) {
|
||||
flags.push(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
static function handleFlag(param:String, params:Array<String>, cliHandler:Any) {
|
||||
if (param.indexOf('--') == 0) {
|
||||
trace('--field ${param}');
|
||||
var paramName = param.substr(2);
|
||||
trace(paramName);
|
||||
Reflect.setProperty(cliHandler, paramName, params.shift());
|
||||
} else if (param.indexOf('-') == 0) {
|
||||
trace('-field ${param}');
|
||||
throw new Error('shorthand params are not supported yet', PositionTools.here());
|
||||
} else {
|
||||
trace('field ${param}');
|
||||
throw new Error('param has an unexpected value', PositionTools.here());
|
||||
}
|
||||
}
|
||||
|
||||
static function getHandlerClass(cliHandler:Any) {
|
||||
var handlerClass:Null<Class<Any>> = Type.getClass(cliHandler ?? new Cli());
|
||||
if (handlerClass == null || handlerClass == Cli) {
|
||||
throw new Error('cliHandler has to be an instance of a class', PositionTools.here());
|
||||
}
|
||||
|
||||
return handlerClass;
|
||||
}
|
||||
|
||||
static function findDefaultCommand(meta:Dynamic<Dynamic<Array<Dynamic>>>):Null<String> {
|
||||
for (fieldName in Reflect.fields(meta)) {
|
||||
var field = Reflect.field(meta, fieldName);
|
||||
if (Reflect.hasField(field, 'defaultCommand')) {
|
||||
return fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function new() {}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user