diff --git a/lua/README.md b/lua/README.md index 79458aea..3f4003f4 100644 --- a/lua/README.md +++ b/lua/README.md @@ -1,29 +1,79 @@ # KCL Artifact Library for Lua -This repo is under development, PRs welcome! +> [!WARNING] +> This repo is under development, PRs welcome! -## Build from Source +A Lua library for interacting with KCL (Kusion Configuration Language) artifacts. This library +enables you to work with KCL modules, configurations, and artifacts directly from Lua scripts. -**Prerequisites** +## Installation -+ Lua -+ Cargo +### From Source -### Lua version +#### Prerequisites -You have to enable one of the features: lua54, lua53, lua52, lua51, luajit(52) or luau in `Cargo.toml`, according to the chosen Lua version. **Default Lua version is 5.2**. +- **Rust** (with Cargo) - For building the library. +- **Lua** - The target Lua version you want to use. +- **LuaRocks** - The Lua package manager to manage the build and installation. -If you build on macos, you can set the environment to prevent link errors. +#### Supported Lua Versions -```shell +This library supports multiple Lua versions. Enable the appropriate feature in `Cargo.toml`: + +- `lua54` - Lua 5.4 +- `lua53` - Lua 5.3 +- `lua52` - Lua 5.2 (default) +- `lua51` - Lua 5.1 +- `luajit` - LuaJIT + +#### Platform-Specific Setup + +**macOS:** Set the deployment target to avoid link errors: + +```bash # Set cargo build target on macos export MACOSX_DEPLOYMENT_TARGET='10.13' ``` -### Linux +#### Build Steps + +Use `luarocks` to build the library and install it for your current Lua version: + +```bash +luarocks --local make +``` + +## Usage + +### Basic Usage + +The KCL Lua library provides two main functions for working with KCL configurations: + +```lua +local kcl = require("kcl_lib") + +-- Execute a single KCL file +local result = assert(kcl.run("./config/schema.k")) +print("Configuration result:", result.yaml_result) + +-- Execute multiple KCL files +local result = assert(kcl.run({ + "./config/schema.k", + "./config/data.k" +})) +print("Combined configuration:", result.yaml_result) + +-- Format a KCL file +local formatted_files = assert(kcl.format("./config/unordered.k")) +print("Formatted files:", table.concat(formatted_files, ", ")) +``` + +## Development + +### Running Tests + +Tests are directly run in Lua using `busted`. You can run them using: -```shell -make -# copy to lua share library directory -cp ./target/release/libkcl_lib_lua.so /usr/lib/lua/5.2/kcl_lib.so +```bash +luarocks test ``` diff --git a/lua/spec/exec_spec.lua b/lua/spec/exec_spec.lua deleted file mode 100644 index 1ea73623..00000000 --- a/lua/spec/exec_spec.lua +++ /dev/null @@ -1,13 +0,0 @@ -local kcl_lib = require("kcl_lib") - -describe("kcl lua lib unit test", function() - describe("exec_program", function() - it("operator function in fs schema", function() - local result, err = kcl_lib.exec_program({ - k_filename_list = { "./spec/test_data/schema.k" }, - }) - assert.is_nil(err) - assert.are.equal(result.yaml_result, "app:\n replicas: 2") - end) - end) -end) diff --git a/lua/spec/kcl_lib_spec.lua b/lua/spec/kcl_lib_spec.lua new file mode 100644 index 00000000..59b11eb1 --- /dev/null +++ b/lua/spec/kcl_lib_spec.lua @@ -0,0 +1,85 @@ +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) + 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/spec/test_data/data.k b/lua/spec/test_data/data.k new file mode 100644 index 00000000..ec3ae7bf --- /dev/null +++ b/lua/spec/test_data/data.k @@ -0,0 +1,3 @@ +app2: AppConfig { + replicas: 4 +} diff --git a/lua/src/lib.rs b/lua/src/lib.rs index e1ad06c4..e18da8d5 100644 --- a/lua/src/lib.rs +++ b/lua/src/lib.rs @@ -2,17 +2,23 @@ extern crate kcl_api; use mlua::prelude::*; -/// Execute KCL file with arguments and return the JSON/YAML result. -fn exec_program<'a>(lua: &'a Lua, args: LuaTable<'a>) -> LuaResult> { +/// 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 work_dir: String = args.get("work_dir").unwrap_or_default(); - let k_filename_list: Vec = args.get("k_filename_list").unwrap_or_default(); - let k_code_list: Vec = args.get("k_code_list").unwrap_or_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", + )); + } + }?; let result = match api.exec_program(&kcl_api::ExecProgramArgs { - work_dir, k_filename_list, - k_code_list, ..Default::default() }) { Ok(r) => r, @@ -27,9 +33,29 @@ fn exec_program<'a>(lua: &'a Lua, args: LuaTable<'a>) -> LuaResult> 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) +} + #[mlua::lua_module] fn kcl_lib(lua: &Lua) -> LuaResult> { let module = lua.create_table()?; - module.set("exec_program", lua.create_function(exec_program)?)?; + module.set("run", lua.create_function(run)?)?; + module.set("format", lua.create_function(format)?)?; Ok(module) }