From 95074991e7ae436bb6c7247ebcd589078e4efe20 Mon Sep 17 00:00:00 2001 From: Jakob Beckmann Date: Fri, 8 May 2026 13:50:19 +0200 Subject: [PATCH] feat(lua): initial implementation of a service based approach This exposes a low level raw API to the native service running over protobuf. Another highler level API is provided with more ergonomic functions based on the low level API for simpler use of the SDK. Signed-off-by: Jakob Beckmann --- lua/README.md | 29 ++- lua/hack/generate_pb.lua | 18 ++ lua/kcl_lib-0.12.3-1.rockspec | 8 + lua/kcl_lib/api.lua | 34 +++ lua/kcl_lib/raw_api.lua | 53 +++++ lua/kcl_lib/schema.lua | 382 ++++++++++++++++++++++++++++++ lua/kcl_lib/types.lua | 42 ++++ lua/spec/kcl_lib_api_spec.lua | 20 ++ lua/spec/kcl_lib_raw_api_spec.lua | 60 +++++ lua/spec/kcl_lib_spec.lua | 82 +------ lua/src/client.rs | 20 ++ lua/src/lib.rs | 54 +---- 12 files changed, 666 insertions(+), 136 deletions(-) create mode 100644 lua/hack/generate_pb.lua create mode 100644 lua/kcl_lib/api.lua create mode 100644 lua/kcl_lib/raw_api.lua create mode 100644 lua/kcl_lib/schema.lua create mode 100644 lua/kcl_lib/types.lua create mode 100644 lua/spec/kcl_lib_api_spec.lua create mode 100644 lua/spec/kcl_lib_raw_api_spec.lua create mode 100644 lua/src/client.rs diff --git a/lua/README.md b/lua/README.md index 3f4003f..7b70a07 100644 --- a/lua/README.md +++ b/lua/README.md @@ -43,6 +43,12 @@ Use `luarocks` to build the library and install it for your current Lua version: luarocks --local make ``` +If you need to re-generate the protobuf spec files, run: + +```bash +luajit hack/generate_pb.lua +``` + ## Usage ### Basic Usage @@ -50,22 +56,27 @@ luarocks --local make The KCL Lua library provides two main functions for working with KCL configurations: ```lua -local kcl = require("kcl_lib") +local api = require("kcl_lib.api") -- Execute a single KCL file -local result = assert(kcl.run("./config/schema.k")) -print("Configuration result:", result.yaml_result) +local result = api:run("./config/schema.k") +print("Configuration result:", result:yaml()) -- Execute multiple KCL files -local result = assert(kcl.run({ +local result = api:run({ "./config/schema.k", "./config/data.k" -})) -print("Combined configuration:", result.yaml_result) +}) +print("Combined configuration:", result:json()) + +-- Using the raw API to the native service +local raw_api = require("kcl_lib.raw_api") --- Format a KCL file -local formatted_files = assert(kcl.format("./config/unordered.k")) -print("Formatted files:", table.concat(formatted_files, ", ")) +-- Perform a call to a native service function +local result = raw_api:exec_program({ + k_filename_list = { "./config/schema.k" }, +}) +print("Configuration result", result.yaml_result) ``` ## Development diff --git a/lua/hack/generate_pb.lua b/lua/hack/generate_pb.lua new file mode 100644 index 0000000..f39b638 --- /dev/null +++ b/lua/hack/generate_pb.lua @@ -0,0 +1,18 @@ +local io = require("io") + +local protoc = require("protoc") + +local function main() + local p = protoc.new() + p.proto3_optional = true + local fh = assert(io.open("../spec/spec.proto", "r")) + local content = fh:read("*a") + fh:close() + local dump = p:compile(content, "spec.proto") + fh = assert(io.open("./kcl_lib/schema.lua", "w")) + assert(fh:write("return ")) + assert(fh:write(string.format("%q", dump))) + fh:close() +end + +main() diff --git a/lua/kcl_lib-0.12.3-1.rockspec b/lua/kcl_lib-0.12.3-1.rockspec index f9f00ab..1f08a06 100644 --- a/lua/kcl_lib-0.12.3-1.rockspec +++ b/lua/kcl_lib-0.12.3-1.rockspec @@ -19,6 +19,8 @@ description = { dependencies = { "lua >= 5.1", "luarocks-build-rust-mlua = 0.2.0", + "lua-protobuf >= 0.5", + "dkjson >= 2.9", } test_dependencies = { "busted >= 2.2", @@ -32,4 +34,10 @@ build = { ["kcl_lib"] = "kcl_lib_lua", }, target_path = "target", + include = { + "api.lua", + "raw_api.lua", + "types.lua", + "schema.lua", + }, } diff --git a/lua/kcl_lib/api.lua b/lua/kcl_lib/api.lua new file mode 100644 index 0000000..569bea8 --- /dev/null +++ b/lua/kcl_lib/api.lua @@ -0,0 +1,34 @@ +local api = require("kcl_lib.raw_api") +local types = require("kcl_lib.types") + +---@class kcl_lib.API +---@field private raw kcl_lib.RawAPI The object to access the raw API. +local API = {} + +---Create a new API object. +---@return kcl_lib.RawAPI +function API:new() + local o = { + raw = api, + } + setmetatable(o, self) + self.__index = self + o.overrides = {} + return o +end + +---Run a KCL program or set of programs and its output. +---@param file string|string[] The file or list of files to run. +---@return kcl_lib.types.RunResponse +function API:run(file) + if type(file) == "string" then + file = { file } + end + local args = { + k_filename_list = file, + } + local res = self.raw:exec_program(args) + return types.RunResponse:from_exec_program_result(res) +end + +return API:new() diff --git a/lua/kcl_lib/raw_api.lua b/lua/kcl_lib/raw_api.lua new file mode 100644 index 0000000..1703958 --- /dev/null +++ b/lua/kcl_lib/raw_api.lua @@ -0,0 +1,53 @@ +local kcl_lib = require("kcl_lib") +local schema = require("kcl_lib.schema") + +---@class kcl_lib.RawAPI +local RawAPI = {} + +---Create a new API object. +---@return kcl_lib.RawAPI +function RawAPI:new() + local pb = require("pb") + pb.clear() + assert(pb.load(schema)) + local o = { + pb = pb, + client = assert(kcl_lib.new_client(), "failed to create native KCL client"), + } + setmetatable(o, self) + self.__index = self + o.overrides = {} + return o +end + +---Add a method to the raw API. +---@param name string The KCL service function name to call. +---@param arg_name string The name of the argument type that the method accepts. +---@param return_name string The name of the return type that the method returns. +---@return function +local function add_method(name, arg_name, return_name) + return function(self, args) + local arg_type = ".com.kcl.api." .. arg_name + args = assert( + self.pb.encode(arg_type, args), + "failed to encode argument into " .. arg_type + ) + local res = assert( + self.client:call(name, args), + "failed to perform native call for method " .. name + ) + local return_type = ".com.kcl.api." .. return_name + return assert( + self.pb.decode(return_type, res), + "failed to decode buffer into " .. return_type + ) + end +end + +RawAPI.exec_program = + add_method("KclService.ExecProgram", "ExecProgramArgs", "ExecProgramResult") + +RawAPI.format_path = + add_method("KclService.FormatPath", "FormatPathArgs", "FormatPathResult") + +return RawAPI:new() diff --git a/lua/kcl_lib/schema.lua b/lua/kcl_lib/schema.lua new file mode 100644 index 0000000..7038f23 --- /dev/null +++ b/lua/kcl_lib/schema.lua @@ -0,0 +1,382 @@ +return "\ +N2\1\0186\26\23.com.kcl.api.PingResult\ +\4Ping\18\21.com.kcl.api.PingArgs\18H\26\29.com.kcl.api.ListMethodResult\ +\ +ListMethod\18\27.com.kcl.api.ListMethodArgs\ +\14BuiltinService2\13\0186\26\23.com.kcl.api.PingResult\ +\4Ping\18\21.com.kcl.api.PingArgs\18H\26\29.com.kcl.api.GetVersionResult\ +\ +GetVersion\18\27.com.kcl.api.GetVersionArgs\18N\26\31.com.kcl.api.ParseProgramResult\ +\12ParseProgram\18\29.com.kcl.api.ParseProgramArgs\18E\26\28.com.kcl.api.ParseFileResult\ +\9ParseFile\18\26.com.kcl.api.ParseFileArgs\18K\26\30.com.kcl.api.LoadPackageResult\ +\11LoadPackage\18\28.com.kcl.api.LoadPackageArgs\18L\26\30.com.kcl.api.ListOptionsResult\ +\11ListOptions\18\29.com.kcl.api.ParseProgramArgs\18Q\26 .com.kcl.api.ListVariablesResult\ +\13ListVariables\18\30.com.kcl.api.ListVariablesArgs\18K\26\30.com.kcl.api.ExecProgramResult\ +\11ExecProgram\18\28.com.kcl.api.ExecProgramArgs\18N\26\31.com.kcl.api.BuildProgramResult\ +\12BuildProgram\18\29.com.kcl.api.BuildProgramArgs\18M\26\30.com.kcl.api.ExecProgramResult\ +\12ExecArtifact\18\29.com.kcl.api.ExecArtifactArgs\18N\26\31.com.kcl.api.OverrideFileResult\ +\12OverrideFile\18\29.com.kcl.api.OverrideFileArgs\18f\26'.com.kcl.api.GetSchemaTypeMappingResult\ +\20GetSchemaTypeMapping\18%.com.kcl.api.GetSchemaTypeMappingArgs\18H\26\29.com.kcl.api.FormatCodeResult\ +\ +FormatCode\18\27.com.kcl.api.FormatCodeArgs\18H\26\29.com.kcl.api.FormatPathResult\ +\ +FormatPath\18\27.com.kcl.api.FormatPathArgs\18B\26\27.com.kcl.api.LintPathResult\ +\8LintPath\18\25.com.kcl.api.LintPathArgs\18N\26\31.com.kcl.api.ValidateCodeResult\ +\12ValidateCode\18\29.com.kcl.api.ValidateCodeArgs\18N\26\31.com.kcl.api.ListDepFilesResult\ +\12ListDepFiles\18\29.com.kcl.api.ListDepFilesArgs\18]\26$.com.kcl.api.LoadSettingsFilesResult\ +\17LoadSettingsFiles\18\".com.kcl.api.LoadSettingsFilesArgs\18<\26\25.com.kcl.api.RenameResult\ +\6Rename\18\23.com.kcl.api.RenameArgs\18H\26\29.com.kcl.api.RenameCodeResult\ +\ +RenameCode\18\27.com.kcl.api.RenameCodeArgs\0186\26\23.com.kcl.api.TestResult\ +\4Test\18\21.com.kcl.api.TestArgs\18`\26%.com.kcl.api.UpdateDependenciesResult\ +\18UpdateDependencies\18#.com.kcl.api.UpdateDependenciesArgs\ +\ +KclServiceb\6proto3\ +\ +spec.proto\18\11com.kcl.apiB\20Z\5.;api\2\ +KclLib.API\"1\18\16\24\1(\9 \1\ +\8pkg_name\18\16\24\2(\9 \1\ +\8pkg_path\ +\11ExternalPkg\"'\18\12\24\1(\9 \1\ +\4name\18\13\24\2(\9 \1\ +\5value\ +\8Argument\"L\18\13\24\1(\9 \1\ +\5level\18\12\24\2(\9 \1\ +\4code\18&\24\3(\11 \3\ +\8messages2\20.com.kcl.api.Message\ +\5Error\":\18\11\24\1(\9 \1\ +\3msg\18\"\24\2(\11 \1\ +\3pos2\21.com.kcl.api.Position\ +\7Message\"\25\18\13\24\1(\9 \1\ +\5value\ +\8PingArgs\"\27\18\13\24\1(\9 \1\ +\5value\ +\ +PingResult\"\16\ +\14GetVersionArgs\"\\\18\15\24\1(\9 \1\ +\7version\18\16\24\2(\9 \1\ +\8checksum\18\15\24\3(\9 \1\ +\7git_sha\18\20\24\4(\9 \1\ +\12version_info\ +\16GetVersionResult\"\16\ +\14ListMethodArgs\",\18\24\24\1(\9 \3\ +\16method_name_list\ +\16ListMethodResult\"^\18\12\24\1(\9 \1\ +\4path\18\14\24\2(\9 \1\ +\6source\18/\24\3(\11 \3\ +\13external_pkgs2\24.com.kcl.api.ExternalPkg\ +\13ParseFileArgs\"U\18\16\24\1(\9 \1\ +\8ast_json\18\12\24\2(\9 \3\ +\4deps\18\"\24\3(\11 \3\ +\6errors2\18.com.kcl.api.Error\ +\15ParseFileResult\"c\18\13\24\1(\9 \3\ +\5paths\18\15\24\2(\9 \3\ +\7sources\18/\24\3(\11 \3\ +\13external_pkgs2\24.com.kcl.api.ExternalPkg\ +\16ParseProgramArgs\"Y\18\16\24\1(\9 \1\ +\8ast_json\18\13\24\2(\9 \3\ +\5paths\18\"\24\3(\11 \3\ +\6errors2\18.com.kcl.api.Error\ +\18ParseProgramResult\"\1\0181\24\1(\11 \1\ +\ +parse_args2\29.com.kcl.api.ParseProgramArgs\18\19\24\2(\8 \1\ +\11resolve_ast\18\20\24\3(\8 \1\ +\12load_builtin\18\22\24\4(\8 \1\ +\14with_ast_index\ +\15LoadPackageArgs\"\7\26A\ +\11ScopesEntry\18\11\24\1(\9 \1\ +\3key\18!\24\2(\11 \1\ +\5value2\18.com.kcl.api.Scope:\0028\1\26C\ +\12SymbolsEntry\18\11\24\1(\9 \1\ +\3key\18\"\24\2(\11 \1\ +\5value2\19.com.kcl.api.Symbol:\0028\1\26N\ +\18NodeSymbolMapEntry\18\11\24\1(\9 \1\ +\3key\18'\24\2(\11 \1\ +\5value2\24.com.kcl.api.SymbolIndex:\0028\1\0264\ +\18SymbolNodeMapEntry\18\11\24\1(\9 \1\ +\3key\18\13\24\2(\9 \1\ +\5value:\0028\1\26V\ +\26FullyQualifiedNameMapEntry\18\11\24\1(\9 \1\ +\3key\18'\24\2(\11 \1\ +\5value2\24.com.kcl.api.SymbolIndex:\0028\1\26K\ +\16PkgScopeMapEntry\18\11\24\1(\9 \1\ +\3key\18&\24\2(\11 \1\ +\5value2\23.com.kcl.api.ScopeIndex:\0028\1\ +\17LoadPackageResult\18\15\24\1(\9 \1\ +\7program\18\13\24\2(\9 \3\ +\5paths\18(\24\3(\11 \3\ +\12parse_errors2\18.com.kcl.api.Error\18'\24\4(\11 \3\ +\11type_errors2\18.com.kcl.api.Error\18:\24\5(\11 \3\ +\6scopes2*.com.kcl.api.LoadPackageResult.ScopesEntry\18<\24\6(\11 \3\ +\7symbols2+.com.kcl.api.LoadPackageResult.SymbolsEntry\18J\24\7(\11 \3\ +\15node_symbol_map21.com.kcl.api.LoadPackageResult.NodeSymbolMapEntry\18J\24\8(\11 \3\ +\15symbol_node_map21.com.kcl.api.LoadPackageResult.SymbolNodeMapEntry\18[\24\9(\11 \3\ +\24fully_qualified_name_map29.com.kcl.api.LoadPackageResult.FullyQualifiedNameMapEntry\18F\24\ +(\11 \3\ +\13pkg_scope_map2/.com.kcl.api.LoadPackageResult.PkgScopeMapEntry\"=\18(\24\2(\11 \3\ +\7options2\23.com.kcl.api.OptionHelp\ +\17ListOptionsResult\"_\18\12\24\1(\9 \1\ +\4name\18\12\24\2(\9 \1\ +\4type\18\16\24\3(\8 \1\ +\8required\18\21\24\4(\9 \1\ +\13default_value\18\12\24\5(\9 \1\ +\4help\ +\ +OptionHelp\"\1\18 \24\1(\11 \1\ +\2ty2\20.com.kcl.api.KclType\18\12\24\2(\9 \1\ +\4name\18'\24\3(\11 \1\ +\5owner2\24.com.kcl.api.SymbolIndex\18%\24\4(\11 \1\ +\3def2\24.com.kcl.api.SymbolIndex\18'\24\5(\11 \3\ +\5attrs2\24.com.kcl.api.SymbolIndex\18\17\24\6(\8 \1\ +\9is_global\ +\6Symbol\"\1\18\12\24\1(\9 \1\ +\4kind\18'\24\2(\11 \1\ +\6parent2\23.com.kcl.api.ScopeIndex\18'\24\3(\11 \1\ +\5owner2\24.com.kcl.api.SymbolIndex\18)\24\4(\11 \3\ +\8children2\23.com.kcl.api.ScopeIndex\18&\24\5(\11 \3\ +\4defs2\24.com.kcl.api.SymbolIndex\ +\5Scope\"1\18\9\24\1(\4 \1\ +\1i\18\9\24\2(\4 \1\ +\1g\18\12\24\3(\9 \1\ +\4kind\ +\11SymbolIndex\"0\18\9\24\1(\4 \1\ +\1i\18\9\24\2(\4 \1\ +\1g\18\12\24\3(\9 \1\ +\4kind\ +\ +ScopeIndex\"\3\18\16\24\1(\9 \1\ +\8work_dir\18\23\24\2(\9 \3\ +\15k_filename_list\18\19\24\3(\9 \3\ +\11k_code_list\18#\24\4(\11 \3\ +\4args2\21.com.kcl.api.Argument\18\17\24\5(\9 \3\ +\9overrides\18\27\24\6(\8 \1\ +\19disable_yaml_result\18\26\24\7(\8 \1\ +\18print_override_ast\18\26\24\8(\8 \1\ +\18strict_range_check\18\20\24\9(\8 \1\ +\12disable_none\18\15\24\ +(\5 \1\ +\7verbose\18\13\24\11(\5 \1\ +\5debug\18\17\24\12(\8 \1\ +\9sort_keys\18/\24\13(\11 \3\ +\13external_pkgs2\24.com.kcl.api.ExternalPkg\18 \24\14(\8 \1\ +\24include_schema_type_path\18\20\24\15(\8 \1\ +\12compile_only\18\19\24\16(\8 \1\ +\11show_hidden\18\21\24\17(\9 \3\ +\13path_selector\18\17\24\18(\8 \1\ +\9fast_eval\ +\15ExecProgramArgs\"g\18\19\24\1(\9 \1\ +\11json_result\18\19\24\2(\9 \1\ +\11yaml_result\18\19\24\3(\9 \1\ +\11log_message\18\19\24\4(\9 \1\ +\11err_message\ +\17ExecProgramResult\"S\18/\24\1(\11 \1\ +\9exec_args2\28.com.kcl.api.ExecProgramArgs\18\14\24\2(\9 \1\ +\6output\ +\16BuildProgramArgs\"\"\18\12\24\1(\9 \1\ +\4path\ +\18BuildProgramResult\"Q\18\12\24\1(\9 \1\ +\4path\18/\24\2(\11 \1\ +\9exec_args2\28.com.kcl.api.ExecProgramArgs\ +\16ExecArtifactArgs\" \18\14\24\1(\9 \1\ +\6source\ +\14FormatCodeArgs\"%\18\17\24\1(\12 \1\ +\9formatted\ +\16FormatCodeResult\"\30\18\12\24\1(\9 \1\ +\4path\ +\14FormatPathArgs\")\18\21\24\1(\9 \3\ +\13changed_paths\ +\16FormatPathResult\"\29\18\13\24\1(\9 \3\ +\5paths\ +\12LintPathArgs\"!\18\15\24\1(\9 \3\ +\7results\ +\14LintPathResult\"E\18\12\24\1(\9 \1\ +\4file\18\13\24\2(\9 \3\ +\5specs\18\20\24\3(\9 \3\ +\12import_paths\ +\16OverrideFileArgs\"N\18\14\24\1(\8 \1\ +\6result\18(\24\2(\11 \3\ +\12parse_errors2\18.com.kcl.api.Error\ +\18OverrideFileResult\"-\18\21\24\1(\8 \1\ +\13merge_program\ +\20ListVariablesOptions\"8\18(\24\1(\11 \3\ +\9variables2\21.com.kcl.api.Variable\ +\12VariableList\"e\18\13\24\1(\9 \3\ +\5files\18\13\24\2(\9 \3\ +\5specs\0182\24\3(\11 \1\ +\7options2!.com.kcl.api.ListVariablesOptions\ +\17ListVariablesArgs\"\1\26K\ +\14VariablesEntry\18\11\24\1(\9 \1\ +\3key\18(\24\2(\11 \1\ +\5value2\25.com.kcl.api.VariableList:\0028\1\ +\19ListVariablesResult\18B\24\1(\11 \3\ +\9variables2/.com.kcl.api.ListVariablesResult.VariablesEntry\18\25\24\2(\9 \3\ +\17unsupported_codes\18(\24\3(\11 \3\ +\12parse_errors2\18.com.kcl.api.Error\"\1\18\13\24\1(\9 \1\ +\5value\18\17\24\2(\9 \1\ +\9type_name\18\14\24\3(\9 \1\ +\6op_sym\18)\24\4(\11 \3\ +\ +list_items2\21.com.kcl.api.Variable\18+\24\5(\11 \3\ +\12dict_entries2\21.com.kcl.api.MapEntry\ +\8Variable\"=\18\11\24\1(\9 \1\ +\3key\18$\24\2(\11 \1\ +\5value2\21.com.kcl.api.Variable\ +\8MapEntry\"`\18/\24\1(\11 \1\ +\9exec_args2\28.com.kcl.api.ExecProgramArgs\18\19\24\2(\9 \1\ +\11schema_name\ +\24GetSchemaTypeMappingArgs\"\1\26N\ +\22SchemaTypeMappingEntry\18\11\24\1(\9 \1\ +\3key\18#\24\2(\11 \1\ +\5value2\20.com.kcl.api.KclType:\0028\1\ +\26GetSchemaTypeMappingResult\18[\24\1(\11 \3\ +\19schema_type_mapping2>.com.kcl.api.GetSchemaTypeMappingResult.SchemaTypeMappingEntry\"\1\26R\ +\22SchemaTypeMappingEntry\18\11\24\1(\9 \1\ +\3key\18'\24\2(\11 \1\ +\5value2\24.com.kcl.api.SchemaTypes:\0028\1\ +#GetSchemaTypeMappingUnderPathResult\18d\24\1(\11 \3\ +\19schema_type_mapping2G.com.kcl.api.GetSchemaTypeMappingUnderPathResult.SchemaTypeMappingEntry\"8\18)\24\1(\11 \3\ +\11schema_type2\20.com.kcl.api.KclType\ +\11SchemaTypes\"\1\18\16\24\1(\9 \1\ +\8datafile\18\12\24\2(\9 \1\ +\4data\18\12\24\3(\9 \1\ +\4file\18\12\24\4(\9 \1\ +\4code\18\14\24\5(\9 \1\ +\6schema\18\22\24\6(\9 \1\ +\14attribute_name\18\14\24\7(\9 \1\ +\6format\18/\24\8(\11 \3\ +\13external_pkgs2\24.com.kcl.api.ExternalPkg\ +\16ValidateCodeArgs\":\18\15\24\1(\8 \1\ +\7success\18\19\24\2(\9 \1\ +\11err_message\ +\18ValidateCodeResult\":\18\12\24\1(\3 \1\ +\4line\18\14\24\2(\3 \1\ +\6column\18\16\24\3(\9 \1\ +\8filename\ +\8Position\"h\18\16\24\1(\9 \1\ +\8work_dir\18\20\24\2(\8 \1\ +\12use_abs_path\18\19\24\3(\8 \1\ +\11include_all\18\23\24\4(\8 \1\ +\15use_fast_parser\ +\16ListDepFilesArgs\"E\18\15\24\1(\9 \1\ +\7pkgroot\18\15\24\2(\9 \1\ +\7pkgpath\18\13\24\3(\9 \3\ +\5files\ +\18ListDepFilesResult\"8\18\16\24\1(\9 \1\ +\8work_dir\18\13\24\2(\9 \3\ +\5files\ +\21LoadSettingsFilesArgs\"z\18/\24\1(\11 \1\ +\15kcl_cli_configs2\22.com.kcl.api.CliConfig\18.\24\2(\11 \3\ +\11kcl_options2\25.com.kcl.api.KeyValuePair\ +\23LoadSettingsFilesResult\"\2\18\13\24\1(\9 \3\ +\5files\18\14\24\2(\9 \1\ +\6output\18\17\24\3(\9 \3\ +\9overrides\18\21\24\4(\9 \3\ +\13path_selector\18\26\24\5(\8 \1\ +\18strict_range_check\18\20\24\6(\8 \1\ +\12disable_none\18\15\24\7(\3 \1\ +\7verbose\18\13\24\8(\8 \1\ +\5debug\18\17\24\9(\8 \1\ +\9sort_keys\18\19\24\ +(\8 \1\ +\11show_hidden\18 \24\11(\8 \1\ +\24include_schema_type_path\18\17\24\12(\8 \1\ +\9fast_eval\ +\9CliConfig\"*\18\11\24\1(\9 \1\ +\3key\18\13\24\2(\9 \1\ +\5value\ +\12KeyValuePair\"]\18\20\24\1(\9 \1\ +\12package_root\18\19\24\2(\9 \1\ +\11symbol_path\18\18\24\3(\9 \3\ +\ +file_paths\18\16\24\4(\9 \1\ +\8new_name\ +\ +RenameArgs\"%\18\21\24\1(\9 \3\ +\13changed_files\ +\12RenameResult\"\1\0262\ +\16SourceCodesEntry\18\11\24\1(\9 \1\ +\3key\18\13\24\2(\9 \1\ +\5value:\0028\1\ +\14RenameCodeArgs\18\20\24\1(\9 \1\ +\12package_root\18\19\24\2(\9 \1\ +\11symbol_path\18B\24\3(\11 \3\ +\12source_codes2,.com.kcl.api.RenameCodeArgs.SourceCodesEntry\18\16\24\4(\9 \1\ +\8new_name\"\1\0263\ +\17ChangedCodesEntry\18\11\24\1(\9 \1\ +\3key\18\13\24\2(\9 \1\ +\5value:\0028\1\ +\16RenameCodeResult\18F\24\1(\11 \3\ +\13changed_codes2/.com.kcl.api.RenameCodeResult.ChangedCodesEntry\"t\18/\24\1(\11 \1\ +\9exec_args2\28.com.kcl.api.ExecProgramArgs\18\16\24\2(\9 \3\ +\8pkg_list\18\18\24\3(\9 \1\ +\ +run_regexp\18\17\24\4(\8 \1\ +\9fail_fast\ +\8TestArgs\"5\18'\24\2(\11 \3\ +\4info2\25.com.kcl.api.TestCaseInfo\ +\ +TestResult\"R\18\12\24\1(\9 \1\ +\4name\18\13\24\2(\9 \1\ +\5error\18\16\24\3(\4 \1\ +\8duration\18\19\24\4(\9 \1\ +\11log_message\ +\12TestCaseInfo\"?\18\21\24\1(\9 \1\ +\13manifest_path\18\14\24\2(\8 \1\ +\6vendor\ +\22UpdateDependenciesArgs\"K\18/\24\3(\11 \3\ +\13external_pkgs2\24.com.kcl.api.ExternalPkg\ +\24UpdateDependenciesResult\"\5\26G\ +\15PropertiesEntry\18\11\24\1(\9 \1\ +\3key\18#\24\2(\11 \1\ +\5value2\20.com.kcl.api.KclType:\0028\1\26E\ +\13ExamplesEntry\18\11\24\1(\9 \1\ +\3key\18#\24\2(\11 \1\ +\5value2\20.com.kcl.api.Example:\0028\1\ +\7KclType\18\12\24\1(\9 \1\ +\4type\18)\24\2(\11 \3\ +\11union_types2\20.com.kcl.api.KclType\18\15\24\3(\9 \1\ +\7default\18\19\24\4(\9 \1\ +\11schema_name\18\18\24\5(\9 \1\ +\ +schema_doc\0188\24\6(\11 \3\ +\ +properties2$.com.kcl.api.KclType.PropertiesEntry\18\16\24\7(\9 \3\ +\8required\18!\24\8(\11 \1\ +\3key2\20.com.kcl.api.KclType\18\"\24\9(\11 \1\ +\4item2\20.com.kcl.api.KclType\18\12\24\ +(\5 \1\ +\4line\18*\24\11(\11 \3\ +\ +decorators2\22.com.kcl.api.Decorator\18\16\24\12(\9 \1\ +\8filename\18\16\24\13(\9 \1\ +\8pkg_path\18\19\24\14(\9 \1\ +\11description\0184\24\15(\11 \3\ +\8examples2\".com.kcl.api.KclType.ExamplesEntry\18)\24\16(\11 \1\ +\11base_schema2\20.com.kcl.api.KclType\18-\24\17H\0 \1\ +\8function(\0112\25.com.kcl.api.FunctionType\0186\24\18H\1 \1\ +\15index_signature(\0112\27.com.kcl.api.IndexSignatureB\11\ +\9_functionB\18\ +\16_index_signature\"_\18&\24\1(\11 \3\ +\6params2\22.com.kcl.api.Parameter\18'\24\2(\11 \1\ +\9return_ty2\20.com.kcl.api.KclType\ +\12FunctionType\";\18\12\24\1(\9 \1\ +\4name\18 \24\2(\11 \1\ +\2ty2\20.com.kcl.api.KclType\ +\9Parameter\"\1\ +\14IndexSignature\18\18\24\1(\9 \1\ +\8key_nameH\0\18!\24\2(\11 \1\ +\3key2\20.com.kcl.api.KclType\18!\24\3(\11 \1\ +\3val2\20.com.kcl.api.KclType\18\17\24\4(\8 \1\ +\9any_otherB\11\ +\9_key_name\"\1\26/\ +\13KeywordsEntry\18\11\24\1(\9 \1\ +\3key\18\13\24\2(\9 \1\ +\5value:\0028\1\ +\9Decorator\18\12\24\1(\9 \1\ +\4name\18\17\24\2(\9 \3\ +\9arguments\0186\24\3(\11 \3\ +\8keywords2$.com.kcl.api.Decorator.KeywordsEntry\">\18\15\24\1(\9 \1\ +\7summary\18\19\24\2(\9 \1\ +\11description\18\13\24\3(\9 \1\ +\5value\ +\7Example" \ No newline at end of file diff --git a/lua/kcl_lib/types.lua b/lua/kcl_lib/types.lua new file mode 100644 index 0000000..dae30f1 --- /dev/null +++ b/lua/kcl_lib/types.lua @@ -0,0 +1,42 @@ +local json = require("dkjson") + +local M = {} + +---@class kcl_lib.types.RunResponse +---@field private inner table The inner ExecProgramResult object this is wrapping. +local RunResponse = {} + +---Create a RunResponse from an ExecProgramResult. +---@param result table The ExecProgramResult to use as the source. +---@return kcl_lib.types.RunResponse +function RunResponse:from_exec_program_result(result) + local o = { + inner = result, + } + setmetatable(o, self) + self.__index = self + o.overrides = {} + return o +end + +---Return the YAML output from the program execution as a string. +---@return string +function RunResponse:yaml() + return self.inner.yaml_result +end + +---Return the JSON output from the program execution as a string. +---@return string +function RunResponse:json() + return self.inner.json_result +end + +---Return the program execution output as a Lua object. +---@return table +function RunResponse:object() + return json.decode(self.inner.json_result) +end + +M.RunResponse = RunResponse + +return M diff --git a/lua/spec/kcl_lib_api_spec.lua b/lua/spec/kcl_lib_api_spec.lua new file mode 100644 index 0000000..13d9be4 --- /dev/null +++ b/lua/spec/kcl_lib_api_spec.lua @@ -0,0 +1,20 @@ +local api = require("kcl_lib.api") + +describe("kcl_lib.api", function() + describe("run", function() + it("takes a file to run and returns its output", function() + local expected = [[app: + replicas: 2]] + local result = api:run("./spec/test_data/schema.k") + assert.are.equal(expected, result:yaml()) + end) + + it("can take a list of files to run", function() + local result = + api:run({ "./spec/test_data/schema.k", "./spec/test_data/data.k" }) + local tbl = result:object() + assert.are.equal(2, tbl.app.replicas) + assert.are.equal(4, tbl.app2.replicas) + end) + end) +end) diff --git a/lua/spec/kcl_lib_raw_api_spec.lua b/lua/spec/kcl_lib_raw_api_spec.lua new file mode 100644 index 0000000..31f7331 --- /dev/null +++ b/lua/spec/kcl_lib_raw_api_spec.lua @@ -0,0 +1,60 @@ +local io = require("io") +local os = require("os") + +local api = require("kcl_lib.raw_api") + +describe("kcl_lib.raw_api", function() + describe("exec_program", function() + it("can call the native function", function() + local expected = [[app: + replicas: 2 +app2: + replicas: 4]] + local args = { + k_filename_list = { + "./spec/test_data/schema.k", + "./spec/test_data/data.k", + }, + } + local result = assert(api:exec_program(args)) + assert.are.equal(expected, result.yaml_result) + end) + end) + + describe("format_path", function() + it("can call the native function", function() + local unformated_file = "/tmp/unformated.k" + local unformated_content = [[ +schema AppConfig: + replicas: int + +app: AppConfig { + replicas: 2 +}]] + local expected_content = [[schema AppConfig: + replicas: int + +app: AppConfig { + replicas: 2 +} +]] + local file = assert( + io.open(unformated_file, "w"), + "failed to open test file for formatting" + ) + file:write(unformated_content) + file:close() + local args = { path = unformated_file } + local result = assert(api:format_path(args)) + assert.are.same({ unformated_file }, result.changed_paths) + file = assert( + io.open(unformated_file, "r"), + "failed to open formatted file for reading" + ) + local data = file:read("*a") + file:close() + assert.are.equal(expected_content, data) + assert(os.remove(unformated_file)) + end) + end) +end) diff --git a/lua/spec/kcl_lib_spec.lua b/lua/spec/kcl_lib_spec.lua index 59b11eb..de40f39 100644 --- a/lua/spec/kcl_lib_spec.lua +++ b/lua/spec/kcl_lib_spec.lua @@ -1,85 +1,9 @@ -local io = require("io") -local os = require("os") - local kcl_lib = require("kcl_lib") describe("kcl_lib", function() - describe("run", function() - it("should take a single path, run it, and return the result", function() - local expected = [[app: - replicas: 2]] - local result = assert(kcl_lib.run("./spec/test_data/schema.k")) - assert.are.equal(expected, result.yaml_result) + describe("new_client", function() + it("creates a new client", function() + assert(kcl_lib.new_client()) end) - - it( - "should take an array of paths, run them, and return the result", - function() - local expected = [[app: - replicas: 2 -app2: - replicas: 4]] - local result = assert(kcl_lib.run({ - "./spec/test_data/schema.k", - "./spec/test_data/data.k", - })) - assert.are.equal(expected, result.yaml_result) - end - ) - end) - - describe("format", function() - local unformated_file = "/tmp/unformated.k" - local unformated_content = [[ -schema AppConfig: - replicas: int - -app: AppConfig { - replicas: 2 -}]] - local expected_content = [[schema AppConfig: - replicas: int - -app: AppConfig { - replicas: 2 -} -]] - - it( - "should take a single path to unformated code, properly format it, and the path", - function() - local file = assert( - io.open(unformated_file, "w"), - "failed to open test file for formatting" - ) - file:write(unformated_content) - file:close() - local result = assert(kcl_lib.format(unformated_file)) - assert.are.same({ unformated_file }, result) - file = assert( - io.open(unformated_file, "r"), - "failed to open formatted file for reading" - ) - local data = file:read("*a") - file:close() - assert.are.equal(expected_content, data) - os.execute("rm " .. unformated_file) - end - ) - - it( - "should take a single path to formated code, do nothing, and return an empty table", - function() - local file = assert( - io.open(unformated_file, "w"), - "failed to open test file for formatting" - ) - file:write(expected_content) - file:close() - local result = assert(kcl_lib.format(unformated_file)) - assert.are.same({}, result) - os.execute("rm " .. unformated_file) - end - ) end) end) diff --git a/lua/src/client.rs b/lua/src/client.rs new file mode 100644 index 0000000..df08510 --- /dev/null +++ b/lua/src/client.rs @@ -0,0 +1,20 @@ +extern crate kcl_api; + +use mlua::{Error, MetaMethod, String as LuaString, UserData, UserDataMethods}; + +#[derive(Default)] +pub struct NativeServiceClient; + +impl UserData for NativeServiceClient { + fn add_methods<'a, M: UserDataMethods<'a, Self>>(methods: &mut M) { + methods.add_method( + "call", + |lua, _this, (name, args): (LuaString, LuaString)| { + kcl_api::call(name.as_bytes(), args.as_bytes()) + .map(|v| lua.create_string(v)) + .map_err(|e| Error::runtime(e.to_string())) + }, + ); + methods.add_meta_function(MetaMethod::Call, |_, ()| Ok(NativeServiceClient::default())); + } +} diff --git a/lua/src/lib.rs b/lua/src/lib.rs index e18da8d..4b558de 100644 --- a/lua/src/lib.rs +++ b/lua/src/lib.rs @@ -2,60 +2,18 @@ extern crate kcl_api; use mlua::prelude::*; -/// Execute KCL code and return the JSON/YAML result. -fn run<'a>(lua: &'a Lua, path: LuaValue) -> LuaResult> { - let api = kcl_api::API::default(); - let k_filename_list = match path { - LuaValue::String(s) => Ok(vec![s.to_str()?.to_owned()]), - LuaValue::Table(t) => t - .sequence_values::() - .collect::, LuaError>>(), - _ => { - return Err(LuaError::runtime( - "invalid argument type for function `run`, expecting string or table", - )); - } - }?; +mod client; - let result = match api.exec_program(&kcl_api::ExecProgramArgs { - k_filename_list, - ..Default::default() - }) { - Ok(r) => r, - Err(e) => return Err(LuaError::external(e)), - }; +pub use client::NativeServiceClient; - let t = lua.create_table()?; - t.set("json_result", lua.create_string(result.json_result)?)?; - t.set("yaml_result", lua.create_string(result.yaml_result)?)?; - t.set("log_message", lua.create_string(result.log_message)?)?; - t.set("err_message", lua.create_string(result.err_message)?)?; - Ok(t) -} - -/// Format KCL code from a file. -fn format<'a>(lua: &'a Lua, path: String) -> LuaResult> { - let api = kcl_api::API::default(); - - let result = match api.format_path(&kcl_api::FormatPathArgs { - path, - ..Default::default() - }) { - Ok(r) => r, - Err(e) => return Err(LuaError::external(e)), - }; - - let t = lua.create_table()?; - for changed_path in result.changed_paths.iter() { - t.push(lua.create_string(changed_path)?)?; - } - Ok(t) +/// Create a new NativeServiceClient instance +fn new_client<'a>(_lua: &'a Lua, _args: ()) -> LuaResult { + Ok(NativeServiceClient::default()) } #[mlua::lua_module] fn kcl_lib(lua: &Lua) -> LuaResult> { let module = lua.create_table()?; - module.set("run", lua.create_function(run)?)?; - module.set("format", lua.create_function(format)?)?; + module.set("new_client", lua.create_function(new_client)?)?; Ok(module) }