commit 2fcdeb0128933ac28baeb645eb691c559560920e
parent d7e0d46ffa8f9f1eaf27f8b7ee32ef9a21cb9b84
Author: Lewis Russell <lewis6991@gmail.com>
Date: Thu, 17 Apr 2025 11:41:38 +0100
refactor(lsp): gen_lsp
- Simplify usage:
- Instead of `nvim -l src/gen/gen_lsp.lua gen` now just
run `./src/gen/gen_lsp.lua`
- Removed `--methods` and `--capabilities` options.
- Improved rendering code in various areas.
Diffstat:
3 files changed, 249 insertions(+), 293 deletions(-)
diff --git a/MAINTAIN.md b/MAINTAIN.md
@@ -152,7 +152,7 @@ These dependencies are "vendored" (inlined), we must update the sources manually
* `src/nvim/tui/terminfo_defs.h`: terminfo definitions
* Run `scripts/update_terminfo.sh` to update these definitions.
* `runtime/lua/vim/lsp/_meta/protocol.lua`: LSP specification
- * Run `scripts/gen_lsp.lua` to update.
+ * Run `src/gen/gen_lsp.lua` to update.
* `runtime/lua/vim/_meta/lpeg.lua`: LPeg definitions.
* Refer to [`LuaCATS/lpeg`](https://github.com/LuaCATS/lpeg) for updates.
* Update the git SHA revision from which the documentation was taken.
diff --git a/runtime/lua/vim/lsp/_meta/protocol.lua b/runtime/lua/vim/lsp/_meta/protocol.lua
@@ -1,11 +1,11 @@
--[[
-THIS FILE IS GENERATED by scr/gen/gen_lsp.lua
+THIS FILE IS GENERATED by src/gen/gen_lsp.lua
DO NOT EDIT MANUALLY
Based on LSP protocol 3.18
Regenerate:
-nvim -l scr/gen/gen_lsp.lua gen --version 3.18
+nvim -l src/gen/gen_lsp.lua --version 3.18
--]]
---@meta
diff --git a/src/gen/gen_lsp.lua b/src/gen/gen_lsp.lua
@@ -1,28 +1,19 @@
+#!/usr/bin/env -S nvim -l
-- Generates lua-ls annotations for lsp.
local USAGE = [[
Generates lua-ls annotations for lsp.
-USAGE:
-nvim -l src/gen/gen_lsp.lua gen # by default, this will overwrite runtime/lua/vim/lsp/_meta/protocol.lua
-nvim -l src/gen/gen_lsp.lua gen --version 3.18 --out runtime/lua/vim/lsp/_meta/protocol.lua
-nvim -l src/gen/gen_lsp.lua gen --version 3.18 --methods --capabilities
-]]
-
-local DEFAULT_LSP_VERSION = '3.18'
+Also updates types in runtime/lua/vim/lsp/protocol.lua
-local M = {}
+Usage:
+ src/gen/gen_lsp.lua [options]
-local function tofile(fname, text)
- local f = io.open(fname, 'w')
- if not f then
- error(('failed to write: %s'):format(f))
- else
- print(('Written to: %s'):format(fname))
- f:write(text)
- f:close()
- end
-end
+Options:
+ --version <version> LSP version to use (default: 3.18)
+ --out <out> Output file (default: runtime/lua/vim/lsp/_meta/protocol.lua)
+ --help Print this help message
+]]
--- The LSP protocol JSON data (it's partial, non-exhaustive).
--- https://raw.githubusercontent.com/microsoft/language-server-protocol/gh-pages/_specifications/lsp/3.18/metaModel/metaModel.schema.json
@@ -51,6 +42,66 @@ end
--- @field partialResult? any
--- @field result any
+--- @class vim._gen_lsp.Structure translated to @class
+--- @field deprecated? string
+--- @field documentation? string
+--- @field extends? { kind: string, name: string }[]
+--- @field mixins? { kind: string, name: string }[]
+--- @field name string
+--- @field properties? vim._gen_lsp.Property[] members, translated to @field
+--- @field proposed? boolean
+--- @field since? string
+
+--- @class vim._gen_lsp.StructureLiteral translated to anonymous @class.
+--- @field deprecated? string
+--- @field description? string
+--- @field properties vim._gen_lsp.Property[]
+--- @field proposed? boolean
+--- @field since? string
+
+--- @class vim._gen_lsp.Property translated to @field
+--- @field deprecated? string
+--- @field documentation? string
+--- @field name string
+--- @field optional? boolean
+--- @field proposed? boolean
+--- @field since? string
+--- @field type { kind: string, name: string }
+
+--- @class vim._gen_lsp.Enumeration translated to @enum
+--- @field deprecated string?
+--- @field documentation string?
+--- @field name string?
+--- @field proposed boolean?
+--- @field since string?
+--- @field suportsCustomValues boolean?
+--- @field values { name: string, value: string, documentation?: string, since?: string }[]
+
+--- @class vim._gen_lsp.TypeAlias translated to @alias
+--- @field deprecated? string?
+--- @field documentation? string
+--- @field name string
+--- @field proposed? boolean
+--- @field since? string
+--- @field type vim._gen_lsp.Type
+
+--- @class vim._gen_lsp.Type
+--- @field kind string a common field for all Types.
+--- @field name? string for ReferenceType, BaseType
+--- @field element? any for ArrayType
+--- @field items? vim._gen_lsp.Type[] for OrType, AndType
+--- @field key? vim._gen_lsp.Type for MapType
+--- @field value? string|vim._gen_lsp.Type for StringLiteralType, MapType, StructureLiteralType
+
+--- @param fname string
+--- @param text string
+local function tofile(fname, text)
+ local f = assert(io.open(fname, 'w'), ('failed to open: %s'):format(fname))
+ f:write(text)
+ f:close()
+ print('Written to:', fname)
+end
+
---@param opt vim._gen_lsp.opt
---@return vim._gen_lsp.Protocol
local function read_json(opt)
@@ -68,28 +119,24 @@ local function read_json(opt)
return vim.json.decode(res.stdout)
end
--- Gets the Lua symbol for a given fully-qualified LSP method name.
+--- Gets the Lua symbol for a given fully-qualified LSP method name.
+--- @param s string
+--- @return string
local function to_luaname(s)
-- "$/" prefix is special: https://microsoft.github.io/language-server-protocol/specification/#dollarRequests
- return s:gsub('^%$', 'dollar'):gsub('/', '_')
+ return (s:gsub('^%$', 'dollar'):gsub('/', '_'))
end
----@param protocol vim._gen_lsp.Protocol
----@param gen_methods boolean
----@param gen_capabilities boolean
-local function write_to_protocol(protocol, gen_methods, gen_capabilities)
- if not gen_methods and not gen_capabilities then
- return
- end
-
- local indent = (' '):rep(2)
-
- local function compare_method(a, b)
- return to_luaname(a.method) < to_luaname(b.method)
- end
+--- @param a vim._gen_lsp.Notification
+--- @param b vim._gen_lsp.Notification
+--- @return boolean
+local function compare_method(a, b)
+ return to_luaname(a.method) < to_luaname(b.method)
+end
- ---@type (vim._gen_lsp.Request|vim._gen_lsp.Notification)[]
- local all = {}
+---@param protocol vim._gen_lsp.Protocol
+local function write_to_vim_protocol(protocol)
+ local all = {} --- @type (vim._gen_lsp.Request|vim._gen_lsp.Notification)[]
vim.list_extend(all, protocol.notifications)
vim.list_extend(all, protocol.requests)
@@ -99,7 +146,7 @@ local function write_to_protocol(protocol, gen_methods, gen_capabilities)
local output = { '-- Generated by gen_lsp.lua, keep at end of file.' }
- if gen_methods then
+ do -- methods
for _, dir in ipairs({ 'clientToServer', 'serverToClient' }) do
local dir1 = dir:sub(1, 1):upper() .. dir:sub(2)
local alias = ('vim.lsp.protocol.Method.%s'):format(dir1)
@@ -141,16 +188,16 @@ local function write_to_protocol(protocol, gen_methods, gen_capabilities)
if item.documentation then
local document = vim.split(item.documentation, '\n?\n', { trimempty = true })
for _, docstring in ipairs(document) do
- output[#output + 1] = indent .. '--- ' .. docstring
+ output[#output + 1] = ' --- ' .. docstring
end
end
- output[#output + 1] = ("%s%s = '%s',"):format(indent, to_luaname(item.method), item.method)
+ output[#output + 1] = (" %s = '%s',"):format(to_luaname(item.method), item.method)
end
end
output[#output + 1] = '}'
end
- if gen_capabilities then
+ do -- capabilities
vim.list_extend(output, {
'',
'-- stylua: ignore start',
@@ -161,18 +208,9 @@ local function write_to_protocol(protocol, gen_methods, gen_capabilities)
for _, item in ipairs(all) do
if item.serverCapability then
- output[#output + 1] = ("%s['%s'] = { %s },"):format(
- indent,
+ output[#output + 1] = (" ['%s'] = { %s },"):format(
item.method,
- table.concat(
- vim
- .iter(vim.split(item.serverCapability, '.', { plain = true }))
- :map(function(segment)
- return "'" .. segment .. "'"
- end)
- :totable(),
- ', '
- )
+ "'" .. item.serverCapability:gsub('%.', "', '") .. "'"
)
end
end
@@ -197,28 +235,116 @@ local function write_to_protocol(protocol, gen_methods, gen_capabilities)
vim.cmd.write()
end
----@class vim._gen_lsp.opt
----@field output_file string
----@field version string
----@field methods boolean
----@field capabilities boolean
+--- @param doc string
+local function process_documentation(doc)
+ doc = doc:gsub('\n', '\n---')
+ -- Remove <200b> (zero-width space) unicode characters: e.g., `**/<200b>*`
+ doc = doc:gsub('\226\128\139', '')
+ -- Escape annotations that are not recognized by lua-ls
+ doc = doc:gsub('%^---@sample', '---\\@sample')
+ return '---' .. doc
+end
----@param opt vim._gen_lsp.opt
-function M.gen(opt)
- --- @type vim._gen_lsp.Protocol
- local protocol = read_json(opt)
+local simple_types = {
+ string = true,
+ boolean = true,
+ integer = true,
+ uinteger = true,
+ decimal = true,
+}
+
+local anonymous_num = 0
+
+--- @type string[]
+local anonym_classes = {}
+
+--- @param type vim._gen_lsp.Type
+--- @param prefix? string Optional prefix associated with the this type, made of (nested) field name.
+--- Used to generate class name for structure literal types.
+--- @return string
+local function parse_type(type, prefix)
+ if type.kind == 'reference' or type.kind == 'base' then
+ if simple_types[type.name] then
+ return type.name
+ end
+ return 'lsp.' .. type.name
+ elseif type.kind == 'array' then
+ local parsed_items = parse_type(type.element, prefix)
+ if type.element.items and #type.element.items > 1 then
+ parsed_items = '(' .. parsed_items .. ')'
+ end
+ return parsed_items .. '[]'
+ elseif type.kind == 'or' then
+ local types = {} --- @type string[]
+ for _, item in ipairs(type.items) do
+ types[#types + 1] = parse_type(item, prefix)
+ end
+ return table.concat(types, '|')
+ elseif type.kind == 'stringLiteral' then
+ return '"' .. type.value .. '"'
+ elseif type.kind == 'map' then
+ local key = assert(type.key)
+ local value = type.value --[[ @as vim._gen_lsp.Type ]]
+ return ('table<%s, %s>'):format(parse_type(key, prefix), parse_type(value, prefix))
+ elseif type.kind == 'literal' then
+ -- can I use ---@param disabled? {reason: string}
+ -- use | to continue the inline class to be able to add docs
+ -- https://github.com/LuaLS/lua-language-server/issues/2128
+ anonymous_num = anonymous_num + 1
+ local anonymous_classname = 'lsp._anonym' .. anonymous_num
+ if prefix then
+ anonymous_classname = anonymous_classname .. '.' .. prefix
+ end
+
+ local anonym = { '---@class ' .. anonymous_classname }
+ if anonymous_num > 1 then
+ table.insert(anonym, 1, '')
+ end
- write_to_protocol(protocol, opt.methods, opt.capabilities)
+ ---@type vim._gen_lsp.StructureLiteral
+ local structural_literal = assert(type.value) --[[ @as vim._gen_lsp.StructureLiteral ]]
+ for _, field in ipairs(structural_literal.properties) do
+ anonym[#anonym + 1] = '---'
+ if field.documentation then
+ anonym[#anonym + 1] = process_documentation(field.documentation)
+ end
+ anonym[#anonym + 1] = ('---@field %s%s %s'):format(
+ field.name,
+ (field.optional and '?' or ''),
+ parse_type(field.type, prefix .. '.' .. field.name)
+ )
+ end
+ for _, line in ipairs(anonym) do
+ if line then
+ anonym_classes[#anonym_classes + 1] = line
+ end
+ end
+ return anonymous_classname
+ elseif type.kind == 'tuple' then
+ local types = {} --- @type string[]
+ for _, value in ipairs(type.items) do
+ types[#types + 1] = parse_type(value, prefix)
+ end
+ return '[' .. table.concat(types, ', ') .. ']'
+ end
+
+ vim.print('WARNING: Unknown type ', type)
+ return ''
+end
+--- @param protocol vim._gen_lsp.Protocol
+--- @param version string
+--- @param output_file string
+local function write_to_meta_protocol(protocol, version, output_file)
local output = {
'--' .. '[[',
- 'THIS FILE IS GENERATED by scr/gen/gen_lsp.lua',
+ 'THIS FILE IS GENERATED by src/gen/gen_lsp.lua',
'DO NOT EDIT MANUALLY',
'',
- 'Based on LSP protocol ' .. opt.version,
+ 'Based on LSP protocol ' .. version,
'',
'Regenerate:',
- ([=[nvim -l scr/gen/gen_lsp.lua gen --version %s]=]):format(DEFAULT_LSP_VERSION),
+ ([=[nvim -l src/gen/gen_lsp.lua --version %s]=]):format(version),
'--' .. ']]',
'',
'---@meta',
@@ -232,150 +358,9 @@ function M.gen(opt)
'',
}
- local anonymous_num = 0
-
- ---@type string[]
- local anonym_classes = {}
-
- local simple_types = {
- 'string',
- 'boolean',
- 'integer',
- 'uinteger',
- 'decimal',
- }
-
- ---@param documentation string
- local _process_documentation = function(documentation)
- documentation = documentation:gsub('\n', '\n---')
- -- Remove <200b> (zero-width space) unicode characters: e.g., `**/<200b>*`
- documentation = documentation:gsub('\226\128\139', '')
- -- Escape annotations that are not recognized by lua-ls
- documentation = documentation:gsub('%^---@sample', '---\\@sample')
- return '---' .. documentation
- end
-
- --- @class vim._gen_lsp.Type
- --- @field kind string a common field for all Types.
- --- @field name? string for ReferenceType, BaseType
- --- @field element? any for ArrayType
- --- @field items? vim._gen_lsp.Type[] for OrType, AndType
- --- @field key? vim._gen_lsp.Type for MapType
- --- @field value? string|vim._gen_lsp.Type for StringLiteralType, MapType, StructureLiteralType
-
- ---@param type vim._gen_lsp.Type
- ---@param prefix? string Optional prefix associated with the this type, made of (nested) field name.
- --- Used to generate class name for structure literal types.
- ---@return string
- local function parse_type(type, prefix)
- -- ReferenceType | BaseType
- if type.kind == 'reference' or type.kind == 'base' then
- if vim.tbl_contains(simple_types, type.name) then
- return type.name
- end
- return 'lsp.' .. type.name
-
- -- ArrayType
- elseif type.kind == 'array' then
- local parsed_items = parse_type(type.element, prefix)
- if type.element.items and #type.element.items > 1 then
- parsed_items = '(' .. parsed_items .. ')'
- end
- return parsed_items .. '[]'
-
- -- OrType
- elseif type.kind == 'or' then
- local val = ''
- for _, item in ipairs(type.items) do
- val = val .. parse_type(item, prefix) .. '|' --[[ @as string ]]
- end
- val = val:sub(0, -2)
- return val
-
- -- StringLiteralType
- elseif type.kind == 'stringLiteral' then
- return '"' .. type.value .. '"'
-
- -- MapType
- elseif type.kind == 'map' then
- local key = assert(type.key)
- local value = type.value --[[ @as vim._gen_lsp.Type ]]
- return 'table<' .. parse_type(key, prefix) .. ', ' .. parse_type(value, prefix) .. '>'
-
- -- StructureLiteralType
- elseif type.kind == 'literal' then
- -- can I use ---@param disabled? {reason: string}
- -- use | to continue the inline class to be able to add docs
- -- https://github.com/LuaLS/lua-language-server/issues/2128
- anonymous_num = anonymous_num + 1
- local anonymous_classname = 'lsp._anonym' .. anonymous_num
- if prefix then
- anonymous_classname = anonymous_classname .. '.' .. prefix
- end
- local anonym = vim
- .iter({
- (anonymous_num > 1 and { '' } or {}),
- { '---@class ' .. anonymous_classname },
- })
- :flatten()
- :totable()
-
- --- @class vim._gen_lsp.StructureLiteral translated to anonymous @class.
- --- @field deprecated? string
- --- @field description? string
- --- @field properties vim._gen_lsp.Property[]
- --- @field proposed? boolean
- --- @field since? string
-
- ---@type vim._gen_lsp.StructureLiteral
- local structural_literal = assert(type.value) --[[ @as vim._gen_lsp.StructureLiteral ]]
- for _, field in ipairs(structural_literal.properties) do
- anonym[#anonym + 1] = '---'
- if field.documentation then
- anonym[#anonym + 1] = _process_documentation(field.documentation)
- end
- anonym[#anonym + 1] = '---@field '
- .. field.name
- .. (field.optional and '?' or '')
- .. ' '
- .. parse_type(field.type, prefix .. '.' .. field.name)
- end
- -- anonym[#anonym + 1] = ''
- for _, line in ipairs(anonym) do
- if line then
- anonym_classes[#anonym_classes + 1] = line
- end
- end
- return anonymous_classname
-
- -- TupleType
- elseif type.kind == 'tuple' then
- local tuple = '['
- for _, value in ipairs(type.items) do
- tuple = tuple .. parse_type(value, prefix) .. ', '
- end
- -- remove , at the end
- tuple = tuple:sub(0, -3)
- return tuple .. ']'
- end
-
- vim.print('WARNING: Unknown type ', type)
- return ''
- end
-
- --- @class vim._gen_lsp.Structure translated to @class
- --- @field deprecated? string
- --- @field documentation? string
- --- @field extends? { kind: string, name: string }[]
- --- @field mixins? { kind: string, name: string }[]
- --- @field name string
- --- @field properties? vim._gen_lsp.Property[] members, translated to @field
- --- @field proposed? boolean
- --- @field since? string
for _, structure in ipairs(protocol.structures) do
- -- output[#output + 1] = ''
if structure.documentation then
- output[#output + 1] = _process_documentation(structure.documentation)
+ output[#output + 1] = process_documentation(structure.documentation)
end
local class_string = ('---@class lsp.%s'):format(structure.name)
if structure.extends or structure.mixins then
@@ -390,126 +375,97 @@ function M.gen(opt)
end
output[#output + 1] = class_string
- --- @class vim._gen_lsp.Property translated to @field
- --- @field deprecated? string
- --- @field documentation? string
- --- @field name string
- --- @field optional? boolean
- --- @field proposed? boolean
- --- @field since? string
- --- @field type { kind: string, name: string }
for _, field in ipairs(structure.properties or {}) do
output[#output + 1] = '---' -- Insert a single newline between @fields (and after @class)
if field.documentation then
- output[#output + 1] = _process_documentation(field.documentation)
+ output[#output + 1] = process_documentation(field.documentation)
end
- output[#output + 1] = '---@field '
- .. field.name
- .. (field.optional and '?' or '')
- .. ' '
- .. parse_type(field.type, field.name)
+ output[#output + 1] = ('---@field %s%s %s'):format(
+ field.name,
+ (field.optional and '?' or ''),
+ parse_type(field.type, field.name)
+ )
end
output[#output + 1] = ''
end
- --- @class vim._gen_lsp.Enumeration translated to @enum
- --- @field deprecated string?
- --- @field documentation string?
- --- @field name string?
- --- @field proposed boolean?
- --- @field since string?
- --- @field suportsCustomValues boolean?
- --- @field values { name: string, value: string, documentation?: string, since?: string }[]
for _, enum in ipairs(protocol.enumerations) do
if enum.documentation then
- output[#output + 1] = _process_documentation(enum.documentation)
+ output[#output + 1] = process_documentation(enum.documentation)
end
- local enum_type = '---@alias lsp.' .. enum.name
+ output[#output + 1] = '---@alias lsp.' .. enum.name
for _, value in ipairs(enum.values) do
- enum_type = enum_type
- .. '\n---| '
- .. (type(value.value) == 'string' and '"' .. value.value .. '"' or value.value)
- .. ' # '
- .. value.name
+ local value1 = (type(value.value) == 'string' and ('"%s"'):format(value.value) or value.value)
+ output[#output + 1] = ('---| %s # %s'):format(value1, value.name)
end
- output[#output + 1] = enum_type
output[#output + 1] = ''
end
- --- @class vim._gen_lsp.TypeAlias translated to @alias
- --- @field deprecated? string?
- --- @field documentation? string
- --- @field name string
- --- @field proposed? boolean
- --- @field since? string
- --- @field type vim._gen_lsp.Type
for _, alias in ipairs(protocol.typeAliases) do
if alias.documentation then
- output[#output + 1] = _process_documentation(alias.documentation)
+ output[#output + 1] = process_documentation(alias.documentation)
end
+
+ local alias_type --- @type string
+
if alias.type.kind == 'or' then
- local alias_type = '---@alias lsp.' .. alias.name .. ' '
+ local alias_types = {} --- @type string[]
for _, item in ipairs(alias.type.items) do
- alias_type = alias_type .. parse_type(item, alias.name) .. '|'
+ alias_types[#alias_types + 1] = parse_type(item, alias.name)
end
- alias_type = alias_type:sub(0, -2)
- output[#output + 1] = alias_type
+ alias_type = table.concat(alias_types, '|')
else
- output[#output + 1] = '---@alias lsp.'
- .. alias.name
- .. ' '
- .. parse_type(alias.type, alias.name)
+ alias_type = parse_type(alias.type, alias.name)
end
+ output[#output + 1] = ('---@alias lsp.%s %s'):format(alias.name, alias_type)
output[#output + 1] = ''
end
-- anonymous classes
- for _, line in ipairs(anonym_classes) do
- output[#output + 1] = line
- end
+ vim.list_extend(output, anonym_classes)
- tofile(opt.output_file, table.concat(output, '\n') .. '\n')
+ tofile(output_file, table.concat(output, '\n') .. '\n')
end
----@type vim._gen_lsp.opt
-local opt = {
- output_file = 'runtime/lua/vim/lsp/_meta/protocol.lua',
- version = DEFAULT_LSP_VERSION,
- methods = false,
- capabilities = false,
-}
+---@class vim._gen_lsp.opt
+---@field output_file string
+---@field version string
-local command = nil
-local i = 1
-while i <= #_G.arg do
- if _G.arg[i] == '--out' then
- opt.output_file = assert(_G.arg[i + 1], '--out <outfile> needed')
- i = i + 1
- elseif _G.arg[i] == '--version' then
- opt.version = assert(_G.arg[i + 1], '--version <version> needed')
- i = i + 1
- elseif _G.arg[i] == '--methods' then
- opt.methods = true
- elseif _G.arg[i] == '--capabilities' then
- opt.capabilities = true
- elseif vim.startswith(_G.arg[i], '-') then
- error('Unrecognized args: ' .. _G.arg[i])
- else
- if command then
- error('More than one command was given: ' .. _G.arg[i])
- else
- command = _G.arg[i]
+--- @return vim._gen_lsp.opt
+local function parse_args()
+ ---@type vim._gen_lsp.opt
+ local opt = {
+ output_file = 'runtime/lua/vim/lsp/_meta/protocol.lua',
+ version = '3.18',
+ }
+
+ local i = 1
+ while i <= #_G.arg do
+ local cur_arg = _G.arg[i]
+ if cur_arg == '--out' then
+ opt.output_file = assert(_G.arg[i + 1], '--out <outfile> needed')
+ i = i + 1
+ elseif cur_arg == '--version' then
+ opt.version = assert(_G.arg[i + 1], '--version <version> needed')
+ i = i + 1
+ elseif cur_arg == '--help' or cur_arg == '-h' then
+ print(USAGE)
+ os.exit(0)
+ elseif vim.startswith(cur_arg, '-') then
+ print('Unrecognized option:', cur_arg, '\n')
+ os.exit(1)
end
+ i = i + 1
end
- i = i + 1
+
+ return opt
end
-if not command then
- print(USAGE)
-elseif M[command] then
- M[command](opt) -- see M.gen()
-else
- error('Unknown command: ' .. command)
+local function main()
+ local opt = parse_args()
+ local protocol = read_json(opt)
+ write_to_vim_protocol(protocol)
+ write_to_meta_protocol(protocol, opt.version, opt.output_file)
end
-return M
+main()