Skip to content

ColinKennedy/mega.cmdparse

Repository files navigation

mega.cmdparse

A Python argparse-inspired command mode parser for Neovim.

This library is for people who have to define a command like :Foo and it takes arguments. This library supports customizable arguments and subcommands so no more needing to define :FooToggle :FooOpen /path/to/file.txt :FooClose 2, etc commands. You can easily create one command with completely separate interfaces like :Foo toggle, :Foo open /path/to/file.txt, and :Foo close --buffer=2.

There's a lot of features. Whatever you need, mega.cmdparse has you covered!

Build Status unittests documentation luacheck llscheck stylua urlchecker
License License-MIT
Social RSS

⚑️ Features

⭐ Demos

Builtin Auto-Complete

builtin_auto_complete.mp4

Auto-Generated --help Parameter

If you want details on what to type, add -h or --help to any command and the automated help message will show.

automated_help_messages.mp4

A summarized version may also show if you make a mistake in your input.

parser_usage_summaries.mp4

Automated Parameter Validation

Parameters know how many arguments they need and in what order.

parameter_validation.mp4

2 Supported Flag Formats

--foo bar and --foo=bar are both supported styles.

equals_or_not_both_work.mp4

🌟 Examples

Simple Hello, World!

Hello, World! Parser
local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Hello, World!"})
parser:set_execute(function(data) print("Hello, World!") end)
cmdparse.create_user_command(parser)

Run: :Test

Automated Value Type Conversions

Automated value type conversions
local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Automated value type conversions" })
parser:add_parameter({ name = "thing", type = tonumber, help = "Test." })
parser:add_parameter({ name = "another", type = "number", help = "Test." })
parser:set_execute(function(data)
    print(string.format('Thing: "%d"', data.namespace.thing + 10))
    print(string.format('Another: "%d"', data.namespace.another + 10))
end)

cmdparse.create_user_command(parser)

Run: :Test 10 -123

Multi-Argument-Per-Parameter

Multi-argument-per-parameter

In this example, the "thing" parameter takes exactly 2 arguments, indicated by nargs=2.

  • nargs="*" = 0-or-more
  • nargs="+" = 1-or-more
local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Multi-argument-per-parameter" })
parser:add_parameter({ name = "thing", nargs=2, type=tonumber, help = "Test." })
parser:set_execute(function(data)
    local values = data.namespace.thing
    local first = values[1]
    local second = values[2]
    local total = first + second

    print(string.format('Thing: "%f + %f = %f"', first, second, total))
end)

cmdparse.create_user_command(parser)

Run: :Test 123 54545.1231

Position, Flag, And Named Arguments

Position, flag, and named arguments. e.g. `foo bar --fizz=buzz -dbz`
local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Position, flag, and named arguments support." })
parser:add_parameter({ name = "items", nargs="*", help="non-flag arguments." })
parser:add_parameter({ name = "--fizz", help="A word." })
parser:add_parameter({ name = "-d", action="store_true", help="Delta single-word." })
parser:add_parameter({ names = {"--beta", "-b"}, action="store_true", help="Beta single-word." })
parser:add_parameter({ name = "-z", action="store_true", help="Zulu single-word." })

parser:set_execute(function(data)
    local namespace = data.namespace
    local items = namespace.items
    print(vim.fn.join(vim.fn.sort(items), ", "))

    print(string.format('-d: %s, -b: %s, -z: %s', namespace.d, namespace.beta, namespace.z))
end)

cmdparse.create_user_command(parser)

Run: :Test foo bar --fizz=buzz -dbz

Supports Required / Optional Arguments

Supports Required / Optional Arguments

By default, flag / named arguments like --foo or --foo=bar are optional. By default, position arguments like thing are required.

But you can explicitly make flag / named arguments required or position arguments optional, using required=true and required=false.

local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Unicode Parameters." })
parser:add_parameter({ name = "required_thing", help = "Test." })
parser:add_parameter({ name = "optional_thing", required=false, help = "Test." })
parser:add_parameter({ name = "--optional-flag", help = "Test." })
parser:add_parameter({ name = "--required-flag", required=true, help = "Test." })

parser:set_execute(function(data)
    print(vim.inspect(data.namespace))
end)

cmdparse.create_user_command(parser)

Run: :Test foo bar --required-flag=aaa

Nested Subparsers

Nested Subparsers
local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Nested Subparsers" })
local top_subparsers = parser:add_subparsers({ destination = "commands" })
local view = top_subparsers:add_parser({ name = "view", help = "View some data." })
local view_subparsers = view:add_subparsers({ destination = "view_commands" })

local log = view_subparsers:add_parser({ name = "log" })
log:add_parameter({ name = "path", help = "Open a log path file." })
log:add_parameter({ name = "--relative", action="store_true", help = "A relative log path." })
log:set_execute(function(data)
    print(string.format('Opening "%s" log path.', data.namespace.path))
end)

cmdparse.create_user_command(parser)

Run: :Test view log /some/path.txt

Static Auto-Complete Values

Static Auto-Complete Values
local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Static Auto-Complete Values."})
parser:add_parameter({ name = "thing", choices={ "aaa", "apple", "apply" }, help="Test word."})
parser:set_execute(function(data) print(data.namespace.thing) end)
cmdparse.create_user_command(parser)

Run: :Test apply

Dynamic Parsers

Dynamic Auto-Complete Values
local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Dynamic Auto-Complete Values."})
local choices = function(data)
    local output = {}
    local value = data.value or 0

    for index = 1, 5 do
        table.insert(output, "text " .. tostring(value + index))
    end

    return output
end
parser:add_parameter({ name = "--thing", choices=choices, help="Test word."})
parser:set_execute(
    function(data) print(data.namespace.thing) end,
)
cmdparse.create_user_command(parser)

Run: :Test --thing=4

Dynamic Plug-ins

Dynamic Plug-ins

Subparsers are not static, you can create dynamic subparsers with dynamic names and dynamic contents if you'd like. This makes mega.cmdparse great for writing a plugin that supports CLI hooks, like how telescope.nvim behaves.

---@return mega.cmdparse.ParameterParser # Some example parser.
local function make_example_plugin_a()
    local parser = cmdparse.ParameterParser.new({ name = "plugin-a", help = "Test plugin-a." })
    parser:add_parameter({ name = "--foo", action="store_true", help="A required value for plugin-a." })

    parser:set_execute(function(data)
        print("Running plugin-a")
    end)

    return parser
end

---@return mega.cmdparse.ParameterParser # Another example parser.
local function make_example_plugin_b()
    local parser = cmdparse.ParameterParser.new({ name = "plugin-b", help = "Test plugin-b." })
    parser:add_parameter({ name = "foo", help="A required value for plugin-b." })

    parser:set_execute(function(data)
        print("Running plugin-b")
    end)

    return parser
end

---@return mega.cmdparse.ParameterParser # A parser whose auto-complete and executer uses auto-found plugins.
local function create_parser()
    local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Test." })
    local subparsers = parser:add_subparsers({ destination = "commands", help = "All main commands." })

    -- NOTE: These functions would normally be "automatically discovered"
    -- somehow, not hard-coded. But the purpose is the same, it's to add some
    -- name and callable function so we can refer to it later in the parser.
    --
    subparsers:add_parser(make_example_plugin_a())
    subparsers:add_parser(make_example_plugin_b())

    return parser
end

local parser = create_parser()
cmdparse.create_user_command(parser)

Run: Test plugin-a --foo Run: Test plugin-b 12345

Customizable / Automated --help Flag

Customizable / Automated `--help` flag

The help message is automatically generated but you can influence the output a bit, using value_hint.

For example this code below:

local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Position, flag, and named arguments support." })
parser:add_parameter({ name = "items", nargs="*", help="non-flag arguments." })
parser:add_parameter({ name = "--fizz", nargs="+", help="A word." })
parser:add_parameter({ name = "-b", action="store_true", help="Zulu single-word." })

parser:set_execute(function(data)
    print("Ran it")
end)

cmdparse.create_user_command(parser)

Creates this help message:

Usage: Test [ITEMS ...] [--fizz FIZZ [FIZZ ...]] [-b] [--help]

Positional Arguments:
    [ITEMS ...]    non-flag arguments.

Options:
    --fizz FIZZ [FIZZ ...]    A word.
    -b    Zulu single-word.
    --help -h    Show this help message and exit.

If you don't like the auto-generated value text, you can change it. For example

parser:add_parameter({ name = "--fizz", nargs="+", help="A word." })

can be changed to parser:add_parameter({ name = "--fizz", nargs="+", value_hint="/path/to/file.txt", help="A word." })

And the help message becomes

--fizz /path/to/file.txt [/path/to/file.txt ...] A word.

Non-Standard Arguments

Non-standard arguments. e.g. `--foo`, `++bar`, `-f`, etc

The difference between a position parameter and a flag / named parameter is just the prefix. Position parameters must start with alphanumeric text. But this means that anything else can be a flag. e.g. ++foo is a valid flag name and so is --bar. It's all allowed.

local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Position, flag, and named arguments support." })
parser:add_parameter({ name = "--fizz", action="store_true", help="A word." })
parser:add_parameter({ name = "++buzz", help="Some argument." })

parser:set_execute(function(data)
    print(string.format('--fizz: %s', data.namespace.fizz))
    print(string.format('++buzz: "%s"', data.namespace.buzz))
end)

cmdparse.create_user_command(parser)

Run: :Test --fizz ++buzz "some text here"

Unicode Parameters

Unicode Parameters

You can use unicode for position / flag / named parameters if you want to.

local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Unicode Parameters." })
parser:add_parameter({ name = "π’»β“‘π“Šπ’ΎπŸ…ƒπŸ†‚", nargs="+", help = "Test." })
parser:add_parameter({ name = "--😊", help = "Test." })

parser:set_execute(function(data)
    print(vim.fn.join(data.namespace["π’»β“‘π“Šπ’ΎπŸ…ƒπŸ†‚"], ", "))
    print(data.namespace["--😊"])
end)

cmdparse.create_user_command(parser)

Run: :Test apple πŸ„±πŸ„°πŸ„½πŸ„°πŸ„½πŸ„° --😊=ttt

🧰 API

Most people will use mega.cmdparse to create Neovim user commands but if you want to use the Lua API directly, here are the most common cases.

get_completions

You can query the available auto-complete values whenever you want.

Expand to show more
local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new(
    { name = "Test", help = "Unicode Parameters." }
)
parser:add_parameter(
    { name = "--foo", choices = {"apple", "apply", "banana"}, help = "Test." }
)

print(vim.inspect(parser:get_completion("-")))
print(vim.inspect(parser:get_completion("--")))
print(vim.inspect(parser:get_completion("--f")))
print(vim.inspect(parser:get_completion("--fo")))
-- Result: {"--foo="}

print(vim.inspect(parser:get_completion("--foo=")))
-- Result: { "--foo=apple", "--foo=apply", "--foo=banana" }

print(vim.inspect(parser:get_completion("--foo=appl")))
-- Result: { "--foo=apple", "--foo=apply" }

print(vim.inspect(parser:get_completion("--foo appl")))
-- Result: { "apple", "apply" }

This also supports a cursor column position (starting at 1-or-more).

print(vim.inspect(parser:get_completion("--foo=appl", 4)))
-- Result: { "--foo=" }

parse_arguments

You can compute the final values with parse_arguments.

Expand to show more
local cmdparse = require("mega.cmdparse")

local parser = cmdparse.ParameterParser.new({ name = "Test", help = "Unicode Parameters." })
parser:add_parameter({ name = "--foo", choices = {"apple", "apply", "banana"}, help = "Test." })
print(vim.inspect(parser:parse_arguments("--foo=apple")))
-- Result: { foo = "apple" }

πŸ“‹ Installation

{
    "ColinKennedy/mega.cmdparse",
    dependencies = { "ColinKennedy/mega.logging" },
    version = "v1.*",
}

βš™ Configuration

(These are default values)

{
    "ColinKennedy/mega.cmdparse",
    config = function()
        vim.g.cmdparse_configuration = {
            cmdparse = {
                auto_complete = { display = { help_flag = true } },  -- If `false`, don't show the `--help` flag anywhere.
            },
            logging = {
                level = "info", -- "trace" | "debug" | "info" | "warning" | "error" | "fatal"
                use_console = false,  -- Print to Neovim as the user is working
                use_file = false, -- Write to-disk as loggers run
            },
        }
    end
}

βœ… Tests

Initialization

Run this line once before calling any busted command

eval $(luarocks path --lua-version 5.1 --bin)

Running

Run all tests

luarocks test --test-type busted
# Or manually
busted .
# Or with Make
make test

Run test based on tags

busted . --tags=simple

πŸ‘‚ Tracking Updates

See doc/news.txt for updates.

You can watch this plugin for changes by adding this URL to your RSS feed:

https://github.com/ColinKennedy/mega.cmdparse/commits/main/doc/news.txt.atom

About

A Neovim command-mode parser. Similar to Python's argparse module

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •