Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 64 additions & 14 deletions lua/README.md
Original file line number Diff line number Diff line change
@@ -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
```
13 changes: 0 additions & 13 deletions lua/spec/exec_spec.lua

This file was deleted.

85 changes: 85 additions & 0 deletions lua/spec/kcl_lib_spec.lua
Original file line number Diff line number Diff line change
@@ -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)
3 changes: 3 additions & 0 deletions lua/spec/test_data/data.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
app2: AppConfig {
replicas: 4
}
42 changes: 34 additions & 8 deletions lua/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<LuaTable<'a>> {
/// Execute KCL code and return the JSON/YAML result.
fn run<'a>(lua: &'a Lua, path: LuaValue) -> LuaResult<LuaTable<'a>> {
let api = kcl_api::API::default();
let work_dir: String = args.get("work_dir").unwrap_or_default();
let k_filename_list: Vec<String> = args.get("k_filename_list").unwrap_or_default();
let k_code_list: Vec<String> = 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::<String>()
.collect::<Result<Vec<String>, 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,
Expand All @@ -27,9 +33,29 @@ fn exec_program<'a>(lua: &'a Lua, args: LuaTable<'a>) -> LuaResult<LuaTable<'a>>
Ok(t)
}

/// Format KCL code from a file.
fn format<'a>(lua: &'a Lua, path: String) -> LuaResult<LuaTable<'a>> {
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<LuaTable<'_>> {
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)
}
Loading