Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/desvelao/lummander

Create a simple CLI with Lua.
https://github.com/desvelao/lummander

cli command-line lua

Last synced: about 2 months ago
JSON representation

Create a simple CLI with Lua.

Awesome Lists containing this project

README

        

# Lummander
Create a simple command-line interface (CLI) application with Lua. It's inspired by JavaScript's [commander](https://github.com/tj/commander.js/).

# Index
- Features
- Installation
- Usage
- Create an instance
- Add commands
- a) Add command using lummander instance methods
- Command schema
- Command option
- Command action
- b) Add commands from a directory
- Command file
- Command examples
- Parse input
- Lummander instance methods
- Logs methods
- Advanced methods
- Pcall
- Theme
- Add the CLI to the PATH

#

Features

- Define required positional arguments, optional or flagged options for a command.
- Add commands directly to lummander instance or from a folder.

*Require [LuaFileSystem](https://keplerproject.github.io/luafilesystem/)*.

#

Installation

- Using Luarocks:

```bash
luarocks install lummander
```

#

Usage

```lua
-- Require "lummander"
local Lummander = require "lummander"

-- Create a lummander instance
local cli = Lummander.new{
title = "My Custom App", -- title for CLI. Default: ""
tag = "myapp", -- CLI Command to execute your program. Default: "".
description = "My App description", -- CLI description. Default: ""
version = "0.1.1", -- CLI version. Default: "0.1.0"
author = "Myself", -- author. Default: ""
root_path = "/path/to/folder/contains/this/file", -- root_path. Default "". Concat this path to load commands of a subfolder
theme = "acid", -- Default = "default". "default" and "acid" are built-in themes
flag_prevent_help = false -- Prevent help message if not command found. Default: false
}

-- Add commands
cli:command("mycmd", "My command description")
:action(function(parsed, command, app)
print("You activated `mycmd` command")
end)

cli:command("sum ", "Sum 2 values")
:option(
"option1","o","Option1 description",nil,"normal","option_default_value")
:option(
"option2","p","Option2 description",nil,"normal","option2_default_value")
:action(function(parsed, command, app)
print("".. parsed.value1.. "+"..parsed.value2.." = " ..
tostring(tonumber(parsed.value1) + tonumber(parsed.value2)))
end)

-- Parse and execute the command wrote
cli:parse(arg) -- parse arg and execute if a command was written
```

#

Create an instance

```lua
local Lummander = require"lummander"

local cli = Lummander.new{
title = "CLI title",
tag = "myapp", -- define command to launch this script at terminal
description = "My App description", -- CLI description. Default: ""
version = "0.1.1", -- define cli version
author = "Myself", -- author. Default: ""
root_path = "/path/to/folder/contains/this/file", -- root_path. Default "". Concat this path to load commands of a subfolder
theme = "acid", -- Default = "default". "default" and "acid" are built-in themes
flag_prevent_help = false -- prevent show help when :parse() doesn't find a valid command to execute
}
```

#

Add commands

Ways to add commands:
a. Using lummander instance methods
b. Loading from a folder files what contain a command table defined in lua files.

##

a) Add command using lummander instance methods

Create a command with:

```lua
cli:command(schema, description, config)
-- or
cli:command(schema, config)
-- or
cli:command(config)
```

- schema: string - command schema
- description: string or table - command description
- config: table or nil - command config. Table with fields

*Note: if description is a table, then is treated like config.*

###

Command schema

It's a string what should have a main command word. It can have required positional arguments (<>) or optionals ([]).

Example: `mycmd [optional_positional_argument1]`

Arguments:
- - Required argument
- [argument_name] - Optional argument
- [argument_name...] - Optional arguments (array)
- [...argument_name] - Optional arguments (array)

## Command methods

- `cmd:option(...)`: add a flagged option.
- `cmd:action(fn)`: set a function to execute for this command.

See docs to see more methods.

##

Command option

Add options to a command with:
```lua
-- only long_name/long and short_name/short are required
cmd:option(long_name, short_name, description, transform, type, default)
-- or
cmd:option({long, short, description, transform, type, default})
```

- short_name: string - option short name
- long_name: string - option long name
- description: string - option description
- transform: function - transform value received for this option before execute action
- type: `normal` or `flag`. Default `normal`. `flag` is true or false. `normal` can accept a value
- default: option default value (setted by default to `parsed` table)

Example:
```lua
cli:command("mycmd","My cmd description")
:option("output", "o", "My flagged option description", function(value) return "./"..value..".txt" end, "normal", "my_default_value")

-- You can add multiple options
cli:command("mycmd","My cmd description")
:option("output", "o", "My flagged option description", function(value) return "./"..value..".txt" end, "normal", "my_default_value")
:option("mode", "m", "Option mode description")
:option({long = "confirm", short = "c", description = "No require confirm", type = "flag"}) -- addig with table as first argument
```

You can add so many flagged options like you want.

You can define too this config in a table when you create the command. The example from above would be:

```lua
cli:command("mycmd","My cmd description", {
options = {
{long = "output", short = "o", description = "My flagged option description", transform = function(value) return "./"..value..".txt" end, type = "normal", default = "my_defautl_value"}
}
})

cli:command("mycmd","My cmd description", {
options = {
{long = "output", short = "o", description = "My flagged option description", transform = function(value) return "./"..value..".txt" end, type = "normal", default = "my_defautl_value"},
{long = "mode", short = "m", description = "Option mode description"}
}
})

-- You can pass command config table in description argument and add to table the description field

cli:command("mycmd", {
description = "My cmd description", -- includes in command config argument
options = {
{long = "output", short = "o", description = "My flagged option description", transform = function(value) return "./"..value..".txt" end, type = "normal", default = "my_defautl_value"}
}
})
```

##

Command action

Set an action to execute when the command is activated.
```lua
cmd:action(fn)
```

- `fn` function - trigger a function when command is activated.

```lua
cmd:action(function(parsed, command, app)
-- parsed: table with command parsed. Include required positional arguments, optional positional arguments, and flagged options (with long name)
-- command: command itself
-- app: lummander instance
end)
```

You can print parsed table with `parsed:print()` to see values that contains.

##

b) Add commands from a directory

You can add commands from a folder with .lua files.

```lua
cli:commands_dir("folderpath")
```

###

Command file

The command file interface is:

```lua
-- folderpath/mycmd.lua
return {
schema = "mycmd [opt_arg]", -- Schema to parse. Required
description = "Command description", -- Command description
positional_args = { -- Set description or {description, default} for positional arguments
req_arg = "A description for a required argument",
opt_arg = {description = "A description for a optional argument", default = "default_positional_option_value"},
},
options = { -- Add flags arguments
{long = "yes", short = "y", description = "Accept", transform : function(param) end, type = "normal", default = "default_value"} -- same command:option("y","yes", "Accept", function(param) end, "normal", "default_value")
},
hide = false, -- hide from help command
main = true, -- do this command default action to CLI if true. Default = nil = false
action = function(parsed, command, app) -- same command:action(function)
parsed:print()
local sufix = ""
if(parsed.yes)then sufix = " -y" end
os.execute("npm init"..sufix)
end
}
```

*Note: only `schema` is required*

##

Command examples

```lua
-- Command with only a command word.
-- Argumments:
-- - cmd: time

cli:command("time", "Show time")
:action( -- Join a action to execute
function(parsed, command, app) -- app is lummander instance
print(os.date("Time is: %I:%M:%S"))
end
)

-- Command with only a command word and a required positional argument.
-- Argumments:
-- - cmd: hi
-- - req_arg1: name (closed in <> means is required)
cli:command("hi ", "Say hi to someone")
:action(
function(parsed, command, app)
-- parsed is a table that includes a field called "name" due to at command schema
-- is a required positional argument and is needed to trigger this function
-- parsed = { name }
print("Hi " .. parsed.name)
end
)

-- Command with only a command word, a required positional argument and an option.
-- Argumments:
-- - cmd: hi
-- - rea_arg1: name (closed in <> means is required)
-- - option: hello/h

cli:command("hi ", "Say hello/hi to someone")
:option("hello", "h","Say hello instead") -- include a option. Example: myapp hi MyName -h. Then parsed.hello = true
:action(
function(parsed, command, app)
-- parsed = {name, hello}
local saludation = "Hi"
if(parsed.hello) then saludation = "Hello" end
print(saludation .. " " .. parsed.name)
-- `hi Lummander` => Hi Lummander
-- `hi Lummander -h` => Hello Lummander
-- `hi Lummander --hello` => Hello Lummander
end
)

-- Command with a command word, 1 required positional argument, 1 optional positional and 1 option
-- Argumments:
-- - cmd: hi
-- - req_arg1: text (closed in <> means is required)
-- - opt_arg2: othertext (closed in [] means is optional)
-- - option: -o, --output

cli:command("save [othertext]", "Save a file")
:option("output", "o", "Output path")
:action(
function(parsed)
-- parsed = {text, othertext, output}
local default_path = "mypath/file.txt"
local filename = parsed.output or default_path
my_write_file_function(filename, parsed.text)
-- `save "My text"` => Save a file at default_path
-- `save "My text" -o otherpath/myotherfile.txt` => Save a file at "otherpath/myotherfile.txt"
-- `save "My text" --output otherpath/myotherfile.txt` => Save a file at "otherpath/myotherfile.txt"
end
)

cli:command("install [packs...]")
:option("dev", "d", "Set install mode", nil, "flag")
:action(function(parsed, command, app)
parsed.packs:for_each(function(pack) -- options like array is a table with special methods. See https://desvelao.github.io/f/classes/ftable.html
-- do something with each pack
end)
end)
```

##

3. Parse input

```lua
cli:parse(arg) -- Parse a table like-array (space/comilla separated arguments). `arg` variable in Lua is an array that contains arguments passed when it executed. This is REQUIRED. Execute a command if is found.

-- cli.parsed property is created after this
```

#

Lummander instance methods

Note: `cli` is lummander instance.

##

Logs methods

- `cli.log:info(...)`: info log
- `cli.log:warn(...)`: warning log
- `cli.log:error(...)`: error log

##

Advanced methods

- `cli:execute(cmd, [fn])`: like os.execute but returns text or callback function with value returned by cmd executed.

```lua
cli:execute("cd", function(value)
-- do something with value after terminal executution is finished
end)

-- same to:
local value = cli:execute("cd")
-- do something with value or after terminal executution is finished
```

- `cli:find_cmd(cmd_name)`: find a cli command by name

- `cli:action(cmd_name)`: set default action. String or Command. Default (help command)

- `cli:apply_theme(theme)`: apply a theme

- `cli.pcall(fn)`: execute a function as pcall()

- `cli.lfs`: access to LuaFileSystem

###

Lummander.pcall

- `pcall(fn)`: create a pcall instance with a function to check.
- `pcall:pass(fn)`: function to execute if pcall success.
- `pcall:fail(fn)`: function to execute if pcall failed. Call pcall:done().
- `pcall:done()`: execute the pcall

```lua
lum.pcall(function() -- check function to use in pcall
print("hi")
error("Error found on this function")
end)
:pass(function() -- execute if check function doesn't raise errors
-- code..
end):fail(function() -- execute if chech function raise some error
-- code...
end)
```

#

Theme

You can can create a custom theme for your terminal. Styles that you can apply are:

- text color: `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`
- background color: `bgblack`, `bgred`, `bggreen`, `bgyellow`, `bgblue`, `bgmagenta`, `bgcyan`, `bgwhite`
- other: `bold`, `underlined` and `reversed`

Use `cli:apply_theme(theme)` to load it.

Example:
```lua
-- mytheme.lua
return {
cli = {
title = "yellow",
text = "white",
category = "yellow"
},
command = {
definition = "green",
description = "white",
argument = "yellow",
option = "yellow",
category = "red"
},
primary = "green",
secondary = "red",
success = "green",
warning = "yellow",
error = "red"
}
```

*Note: if you dont define some style, this will be `white` by default.*

`cli.theme` print with theme color defined the text
```lua
cli.theme.cli.title(text)
cli.theme.cli.text(text)
cli.theme.cli.category(text)
cli.theme.command.definition(text)
cli.theme.command.description(text)
cli.theme.command.argument(text)
cli.theme.command.option(text)
cli.theme.command.category(text)
cli.theme.primary(text)
cli.theme.secondary(text)
cli.theme.sucess(text)
cli.theme.warning(text)
cli.theme.error(text)
```

#

Add the CLI to the PATH

## Windows
Create a `.bat` or `.cmd` file what contains:

```
lua "absolute_path_to_your_cli_script.lua" %*
```

The name of this file will be CLI command to init your CLI script.

*Note: `lua.exe` should be in a folder what is in os PATH variable, if not, add the folder path to os PATH variable.*

Example:
```
lua "C:/path_to_cli/mycli.lua" %*
```

# License
MIT