1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-12-22 21:21:32 +03:00
vimr/bin/generate_api_methods.py

384 lines
9.8 KiB
Python
Raw Normal View History

2017-11-26 23:36:20 +03:00
#!/usr/bin/env python3
import subprocess
import msgpack
from string import Template
import re
import textwrap
import os
void_func_template = Template('''\
2017-12-01 15:46:34 +03:00
@discardableResult
2017-11-26 23:36:20 +03:00
public func ${func_name}(${args}
expectsReturnValue: Bool = true,
checkBlocked: Bool = true
2017-12-08 17:07:32 +03:00
) -> NvimApi.Response<Void> {
2017-12-08 17:07:32 +03:00
if expectsReturnValue && checkBlocked && self.getMode().value?["blocking"]?.boolValue == true {
return .failure(NvimApi.Error(type: .blocked, message: "Nvim is currently blocked"))
}
2017-11-26 23:36:20 +03:00
2017-12-08 17:07:32 +03:00
let params: [NvimApi.Value] = [
2017-11-26 23:36:20 +03:00
${params}
]
let response = self.rpc(method: "${nvim_func_name}", params: params, expectsReturnValue: expectsReturnValue)
if let error = response.error {
return .failure(error)
}
return .success(())
}
''')
get_mode_func_template = Template('''\
public func ${func_name}(${args}
2017-12-08 17:07:32 +03:00
) -> NvimApi.Response<${result_type}> {
2017-12-08 17:07:32 +03:00
let params: [NvimApi.Value] = [
${params}
]
let response = self.rpc(method: "${nvim_func_name}", params: params, expectsReturnValue: true)
guard let value = response.value else {
return .failure(response.error!)
}
guard let result = (${return_value}) else {
2017-12-08 17:07:32 +03:00
return .failure(NvimApi.Error("Error converting result to \\(${result_type}.self)"))
}
return .success(result)
}
''')
2017-11-26 23:36:20 +03:00
func_template = Template('''\
public func ${func_name}(${args}
checkBlocked: Bool = true
2017-12-08 17:07:32 +03:00
) -> NvimApi.Response<${result_type}> {
2017-12-08 17:07:32 +03:00
if checkBlocked && self.getMode().value?["blocking"]?.boolValue == true {
return .failure(NvimApi.Error(type: .blocked, message: "Nvim is currently blocked"))
}
2017-12-08 17:07:32 +03:00
let params: [NvimApi.Value] = [
2017-11-26 23:36:20 +03:00
${params}
]
let response = self.rpc(method: "${nvim_func_name}", params: params, expectsReturnValue: true)
2017-11-26 23:36:20 +03:00
guard let value = response.value else {
return .failure(response.error!)
}
guard let result = (${return_value}) else {
2017-12-08 17:07:32 +03:00
return .failure(NvimApi.Error("Error converting result to \\(${result_type}.self)"))
2017-11-26 23:36:20 +03:00
}
return .success(result)
}
''')
2017-11-28 00:00:42 +03:00
extension_template = Template('''\
// Auto generated for nvim version ${version}.
2017-11-26 23:36:20 +03:00
// See bin/generate_api_methods.py
2017-11-28 00:00:42 +03:00
import MsgPackRpc
2017-12-08 17:07:32 +03:00
public extension NvimApi.Error {
2017-11-28 00:00:42 +03:00
public enum ErrorType: Int {
${error_types}
2017-12-01 01:13:36 +03:00
case blocked
2017-11-28 00:00:42 +03:00
case unknown
}
}
2017-11-26 23:36:20 +03:00
2017-12-08 17:07:32 +03:00
public extension NvimApi {
2017-11-26 23:36:20 +03:00
$body
}
2017-12-08 17:07:32 +03:00
extension NvimApi.Buffer {
2017-11-26 23:36:20 +03:00
2017-12-08 17:07:32 +03:00
init?(_ value: NvimApi.Value) {
2017-11-26 23:36:20 +03:00
guard let (type, data) = value.extendedValue else {
return nil
}
guard type == ${buffer_type} else {
return nil
}
guard let handle = (try? unpack(data))?.value.integerValue else {
return nil
}
self.handle = Int(handle)
}
}
2017-12-08 17:07:32 +03:00
extension NvimApi.Window {
2017-11-26 23:36:20 +03:00
2017-12-08 17:07:32 +03:00
init?(_ value: NvimApi.Value) {
2017-11-26 23:36:20 +03:00
guard let (type, data) = value.extendedValue else {
return nil
}
guard type == ${window_type} else {
return nil
}
guard let handle = (try? unpack(data))?.value.integerValue else {
return nil
}
self.handle = Int(handle)
}
}
2017-12-08 17:07:32 +03:00
extension NvimApi.Tabpage {
2017-11-26 23:36:20 +03:00
2017-12-08 17:07:32 +03:00
init?(_ value: NvimApi.Value) {
2017-11-26 23:36:20 +03:00
guard let (type, data) = value.extendedValue else {
return nil
}
guard type == ${tabpage_type} else {
return nil
}
guard let handle = (try? unpack(data))?.value.integerValue else {
return nil
}
self.handle = Int(handle)
}
2017-12-08 17:07:32 +03:00
}
fileprivate func msgPackDictToSwift(_ dict: Dictionary<NvimApi.Value, NvimApi.Value>?) -> Dictionary<String, NvimApi.Value>? {
2017-12-08 17:07:32 +03:00
return dict?.flatMapToDict { k, v in
guard let strKey = k.stringValue else {
return nil
}
return (strKey, v)
}
}
fileprivate func msgPackArrayDictToSwift(_ array: [NvimApi.Value]?) -> [Dictionary<String, NvimApi.Value>]? {
2017-12-08 17:07:32 +03:00
return array?
.flatMap { v in v.dictionaryValue }
.flatMap { d in msgPackDictToSwift(d) }
}
2017-12-08 17:07:32 +03:00
extension Dictionary {
fileprivate func mapToDict<K, V>(_ transform: ((key: Key, value: Value)) throws -> (K, V)) rethrows -> Dictionary<K, V> {
2017-12-08 17:07:32 +03:00
let array = try self.map(transform)
return tuplesToDict(array)
}
fileprivate func flatMapToDict<K, V>(_ transform: ((key: Key, value: Value)) throws -> (K, V)?) rethrows -> Dictionary<K, V> {
2017-12-08 17:07:32 +03:00
let array = try self.flatMap(transform)
return tuplesToDict(array)
}
fileprivate func tuplesToDict<K:Hashable, V, S:Sequence>(_ sequence: S)
2017-12-08 17:07:32 +03:00
-> Dictionary<K, V> where S.Iterator.Element == (K, V) {
2017-12-08 17:07:32 +03:00
var result = Dictionary<K, V>(minimumCapacity: sequence.underestimatedCount)
2017-12-08 17:07:32 +03:00
for (key, value) in sequence {
result[key] = value
}
return result
}
}
''')
2017-11-26 23:36:20 +03:00
def snake_to_camel(snake_str):
components = snake_str.split('_')
return components[0] + "".join(x.title() for x in components[1:])
def nvim_type_to_swift(nvim_type):
if nvim_type == 'Boolean':
return 'Bool'
if nvim_type == 'Integer':
return 'Int'
if nvim_type == 'Float':
return nvim_type
if nvim_type == 'void':
return 'Void'
if nvim_type == 'String':
return 'String'
if nvim_type == 'Array':
2017-12-08 17:07:32 +03:00
return 'NvimApi.Value'
2017-11-26 23:36:20 +03:00
if nvim_type == 'Dictionary':
2017-12-08 17:07:32 +03:00
return 'Dictionary<String, NvimApi.Value>'
2017-11-26 23:36:20 +03:00
if nvim_type == 'Buffer':
2017-12-08 17:07:32 +03:00
return 'NvimApi.Buffer'
2017-11-26 23:36:20 +03:00
if nvim_type == 'Window':
2017-12-08 17:07:32 +03:00
return 'NvimApi.Window'
2017-11-26 23:36:20 +03:00
if nvim_type == 'Tabpage':
2017-12-08 17:07:32 +03:00
return 'NvimApi.Tabpage'
2017-11-26 23:36:20 +03:00
if nvim_type == 'Object':
2017-12-08 17:07:32 +03:00
return 'NvimApi.Value'
2017-11-26 23:36:20 +03:00
if nvim_type.startswith('ArrayOf('):
match = re.match(r'ArrayOf\((.*?)(?:, \d+)*\)', nvim_type)
return '[{}]'.format(nvim_type_to_swift(match.group(1)))
2017-12-08 17:07:32 +03:00
return 'NvimApi.Value'
2017-11-26 23:36:20 +03:00
def msgpack_to_swift(msgpack_value_name, type):
if type == 'Bool':
return f'{msgpack_value_name}.boolValue'
if type == 'Int':
return f'({msgpack_value_name}.integerValue == nil ? nil : Int({msgpack_value_name}.integerValue!))'
if type == 'Float':
return f'{msgpack_value_name}.floatValue'
if type == 'Void':
return f'()'
if type == 'String':
return f'{msgpack_value_name}.stringValue'
2017-12-08 17:07:32 +03:00
if type == 'NvimApi.Value':
2017-11-26 23:36:20 +03:00
return f'Optional({msgpack_value_name})'
2017-12-08 17:07:32 +03:00
if type in 'NvimApi.Buffer':
return f'NvimApi.Buffer({msgpack_value_name})'
2017-11-26 23:36:20 +03:00
2017-12-08 17:07:32 +03:00
if type in 'NvimApi.Window':
return f'NvimApi.Window({msgpack_value_name})'
2017-11-26 23:36:20 +03:00
2017-12-08 17:07:32 +03:00
if type in 'NvimApi.Tabpage':
return f'NvimApi.Tabpage({msgpack_value_name})'
if type.startswith('Dictionary<'):
return f'msgPackDictToSwift({msgpack_value_name}.dictionaryValue)'
if type.startswith('[Dictionary<'):
return f'msgPackArrayDictToSwift({msgpack_value_name}.arrayValue)'
2017-11-26 23:36:20 +03:00
if type.startswith('['):
element_type = re.match(r'\[(.*)\]', type).group(1)
return f'{msgpack_value_name}.arrayValue?.flatMap({{ v in {msgpack_to_swift("v", element_type)} }})'
2017-12-08 17:07:32 +03:00
return 'NvimApi.Value'
2017-11-26 23:36:20 +03:00
def swift_to_msgpack_value(name, type):
if type == 'Bool':
return f'.bool({name})'
if type == 'Int':
return f'.int(Int64({name}))'
if type == 'Float':
return f'.float({name})'
if type == 'Void':
return f'.nil()'
if type == 'String':
return f'.string({name})'
2017-12-08 17:07:32 +03:00
if type == 'Dictionary<String, NvimApi.Value>':
return f'.map({name}.mapToDict({{ (Value.string($0), $1) }}))'
if type == 'NvimApi.Value':
2017-11-26 23:36:20 +03:00
return name
2017-12-08 17:07:32 +03:00
if type in ['NvimApi.Buffer', 'NvimApi.Window', 'NvimApi.Tabpage']:
2017-11-26 23:36:20 +03:00
return f'.int(Int64({name}.handle))'
if type.startswith('['):
match = re.match(r'\[(.*)\]', type)
test = '$0'
return f'.array({name}.map {{ {swift_to_msgpack_value(test, match.group(1))} }})'
def parse_args(raw_params):
types = [nvim_type_to_swift(p[0]) for p in raw_params]
names = [p[1] for p in raw_params]
params = dict(zip(names, types))
result = '\n'.join([n + ': ' + t + ',' for n, t in params.items()])
if not result:
return ''
return '\n' + textwrap.indent(result, ' ')
def parse_params(raw_params):
types = [nvim_type_to_swift(p[0]) for p in raw_params]
names = [p[1] for p in raw_params]
params = dict(zip(names, types))
result = '\n'.join([swift_to_msgpack_value(n, t) + ',' for n, t in params.items()])
return textwrap.indent(result, ' ').strip()
def parse_function(f):
args = parse_args(f['parameters'])
template = void_func_template if f['return_type'] == 'void' else func_template
template = get_mode_func_template if f['name'] == 'nvim_get_mode' else template
2017-11-26 23:36:20 +03:00
result = template.substitute(
func_name=snake_to_camel(f['name'][5:]),
nvim_func_name=f['name'],
args=args,
params=parse_params(f['parameters']),
result_type=nvim_type_to_swift(f['return_type']),
return_value=msgpack_to_swift('value', nvim_type_to_swift(f['return_type']))
)
return result
def parse_version(version):
return '.'.join([str(v) for v in [version['major'], version['minor'], version['patch']]])
2017-11-28 00:00:42 +03:00
def parse_error_types(error_types):
return textwrap.indent('\n'.join([f'case {t.lower()} = {v["id"]}' for t, v in error_types.items()]), ' ').lstrip()
2017-11-26 23:36:20 +03:00
if __name__ == '__main__':
nvim_path = os.environ['NVIM_PATH'] if 'NVIM_PATH' in os.environ else 'nvim'
nvim_output = subprocess.run([nvim_path, '--api-info'], stdout=subprocess.PIPE)
api = msgpack.unpackb(nvim_output.stdout, encoding='utf-8')
version = parse_version(api['version'])
functions = [f for f in api['functions'] if 'deprecated_since' not in f]
body = '\n'.join([parse_function(f) for f in functions])
print(extension_template.substitute(
body=body,
version=version,
2017-11-28 00:00:42 +03:00
error_types=parse_error_types(api['error_types']),
2017-11-26 23:36:20 +03:00
buffer_type=api['types']['Buffer']['id'],
window_type=api['types']['Window']['id'],
tabpage_type=api['types']['Tabpage']['id']
))