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
99 changes: 99 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,102 @@ jobs:
LD_LIBRARY_PATH="$PWD/target/release" \
busted --lua=$(which luajit) tests/lua \
--lpath='./lua/?.lua'

- name: Validate LuaRocks package
run: |
rm -rf /tmp/lua-qjson-rock
ROCKSPEC="$(python3 - <<'PY'
import re
from pathlib import Path

pattern = re.compile(r"^lua-qjson-(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?-(\d+)\.rockspec$")
def prerelease_key(value):
if value is None:
return ()
key = []
for part in value.split("."):
if part.isdigit():
key.append((0, int(part)))
else:
key.append((1, part))
return tuple(key)

matches = []
for path in Path("rockspec").glob("lua-qjson-*.rockspec"):
match = pattern.match(path.name)
if match:
major, minor, patch, prerelease, revision = match.groups()
matches.append((int(major), int(minor), int(patch), prerelease is None, prerelease_key(prerelease), int(revision), str(path)))
if not matches:
raise SystemExit("no lua-qjson rockspec found")
print(max(matches)[-1])
PY
)"
luarocks make "$ROCKSPEC" --tree /tmp/lua-qjson-rock
Comment thread
jarvis9443 marked this conversation as resolved.
eval "$(luarocks path --tree /tmp/lua-qjson-rock)"
unset LD_LIBRARY_PATH
luajit -e 'local qjson = require("qjson"); local doc = qjson.parse("{\"a\":42}"); assert(doc:get_i64("a") == 42)'
busted --lua=$(which luajit) tests/lua

package:
name: LuaRocks package (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: rust
strategy:
matrix:
os: [ubuntu-latest, macos-14]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Rust (stable)
run: |
rustup toolchain install stable --profile minimal --no-self-update
rustup default stable

- name: Install LuaJIT
uses: leafo/gh-actions-lua@v13
with:
luaVersion: "luajit-2.1.0-beta3"

- name: Install LuaRocks
uses: leafo/gh-actions-luarocks@v4
with:
luarocksVersion: "3.11.1"

- name: Validate LuaRocks package
run: |
rm -rf /tmp/lua-qjson-rock
ROCKSPEC="$(python3 - <<'PY'
import re
from pathlib import Path

pattern = re.compile(r"^lua-qjson-(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?-(\d+)\.rockspec$")
def prerelease_key(value):
if value is None:
return ()
key = []
for part in value.split("."):
if part.isdigit():
key.append((0, int(part)))
else:
key.append((1, part))
return tuple(key)

matches = []
for path in Path("rockspec").glob("lua-qjson-*.rockspec"):
match = pattern.match(path.name)
if match:
major, minor, patch, prerelease, revision = match.groups()
matches.append((int(major), int(minor), int(patch), prerelease is None, prerelease_key(prerelease), int(revision), str(path)))
if not matches:
raise SystemExit("no lua-qjson rockspec found")
print(max(matches)[-1])
PY
)"
luarocks make "$ROCKSPEC" --tree /tmp/lua-qjson-rock
eval "$(luarocks path --tree /tmp/lua-qjson-rock)"
unset LD_LIBRARY_PATH
unset DYLD_LIBRARY_PATH
lua -e 'assert(jit, "LuaJIT required"); local qjson = require("qjson"); local doc = qjson.parse("{\"a\":42}"); assert(doc:get_i64("a") == 42)'
111 changes: 111 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Release

on:
push:
branches:
- "main"
paths:
- "rockspec/**"

permissions:
contents: write

jobs:
release:
name: Release
Comment thread
jarvis9443 marked this conversation as resolved.
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
Comment thread
jarvis9443 marked this conversation as resolved.
with:
persist-credentials: false

- name: Install Lua
uses: leafo/gh-actions-lua@v10
with:
luaVersion: "5.1.5"

- name: Install LuaRocks
uses: leafo/gh-actions-luarocks@v4
with:
luarocksVersion: "3.11.1"

- name: Install upload dependency
run: luarocks install dkjson

- name: Extract release name
id: release_env
shell: bash
env:
HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
title="${HEAD_COMMIT_MESSAGE%%$'\n'*}"
re="^feat: release v?([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?)$"
if [[ $title =~ $re ]]; then
Comment thread
jarvis9443 marked this conversation as resolved.
v=v${BASH_REMATCH[1]}
echo "version=${v}" >> $GITHUB_OUTPUT
echo "version_without_v=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
Comment thread
jarvis9443 marked this conversation as resolved.
if [[ ${BASH_REMATCH[1]} == *-* ]]; then
echo "is_prerelease=true" >> $GITHUB_OUTPUT
else
echo "is_prerelease=false" >> $GITHUB_OUTPUT
fi
else
echo "commit format is not correct (expected: feat: release vX.Y.Z[-prerelease])"
exit 1
fi

- name: Verify rockspec exists
id: verify_rockspec
Comment thread
jarvis9443 marked this conversation as resolved.
shell: bash
env:
VERSION_WITHOUT_V: ${{ steps.release_env.outputs.version_without_v }}
run: |
rockspec_file="$(python3 - <<'PY'
import os
import re
from pathlib import Path

version = os.environ["VERSION_WITHOUT_V"]
pattern = re.compile(r"^lua-qjson-" + re.escape(version) + r"-(\d+)\.rockspec$")
matches = []
for path in Path("rockspec").glob("lua-qjson-" + version + "-*.rockspec"):
match = pattern.match(path.name)
if match:
matches.append((int(match.group(1)), str(path)))
if not matches:
raise SystemExit("rockspec file not found for version " + version)
print(max(matches)[1])
PY
)"
echo "rockspec_file=${rockspec_file}" >> $GITHUB_OUTPUT

- name: Validate LuaRocks token
shell: bash
env:
LUAROCKS_TOKEN: ${{ secrets.LUAROCKS_TOKEN }}
run: |
if [ -z "$LUAROCKS_TOKEN" ]; then
echo "LUAROCKS_TOKEN secret is not configured"
exit 1
fi

- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.release_env.outputs.version }}
name: ${{ steps.release_env.outputs.version }}
draft: false
prerelease: ${{ steps.release_env.outputs.is_prerelease }}

- name: Upload to LuaRocks
env:
LUAROCKS_TOKEN: ${{ secrets.LUAROCKS_TOKEN }}
ROCKSPEC_FILE: ${{ steps.verify_rockspec.outputs.rockspec_file }}
shell: bash
run: |
mkdir -p "$HOME/.luarocks"
chmod 700 "$HOME/.luarocks"
lua -e 'local token = os.getenv("LUAROCKS_TOKEN"); assert(token and token ~= "", "missing LUAROCKS_TOKEN"); local f = assert(io.open(os.getenv("HOME") .. "/.luarocks/upload_config.lua", "w")); f:write(("key = %q\n"):format(token)); f:write("server = \"https://luarocks.org\"\nversion = \"1.0\"\n"); f:close()'
chmod 600 "$HOME/.luarocks/upload_config.lua"
luarocks upload "$ROCKSPEC_FILE"
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ cargo build --release

A `Makefile` wraps the common workflows; run `make help` to see `build`, `test`, `lint`, `bench`, and `clean` targets. Override `LUAJIT` / `LUA_CPATH` per invocation if your environment differs from the defaults.

## Installing

```sh
luarocks install lua-qjson
```

The rock builds the Rust native library during installation, so Rust/Cargo
and LuaJIT must be available on the target system. The Lua module name remains
`qjson`:

Comment thread
jarvis9443 marked this conversation as resolved.
```lua
local qjson = require("qjson")
```

## Testing

```sh
Expand Down
45 changes: 1 addition & 44 deletions lua/qjson.lua
Original file line number Diff line number Diff line change
@@ -1,49 +1,6 @@
local ffi = require("ffi")

ffi.cdef[[
typedef struct qjson_doc qjson_doc;
typedef struct {
const qjson_doc* doc;
uint32_t idx_start, idx_end, _reserved0, _reserved1;
} qjson_cursor;

typedef struct {
uint32_t mode;
uint32_t max_depth;
} qjson_options;

const char* qjson_strerror(int code);
qjson_doc* qjson_parse (const uint8_t* buf, size_t len, int* err_out);
qjson_doc* qjson_parse_ex(const uint8_t* buf, size_t len,
const qjson_options* opts, int* err_out);
void qjson_free (qjson_doc* doc);

int qjson_get_str (qjson_doc*, const char* path, size_t path_len, const uint8_t** p, size_t* n);
int qjson_get_i64 (qjson_doc*, const char* path, size_t path_len, int64_t* out);
int qjson_get_f64 (qjson_doc*, const char* path, size_t path_len, double* out);
int qjson_get_bool(qjson_doc*, const char* path, size_t path_len, int* out);
int qjson_is_null (qjson_doc*, const char* path, size_t path_len, int* out);
int qjson_typeof (qjson_doc*, const char* path, size_t path_len, int* out);
int qjson_len (qjson_doc*, const char* path, size_t path_len, size_t* out);

int qjson_open (qjson_doc*, const char* path, size_t path_len, qjson_cursor* out);
int qjson_cursor_open (const qjson_cursor*, const char* path, size_t path_len, qjson_cursor* out);
int qjson_cursor_field(const qjson_cursor*, const char* key, size_t key_len, qjson_cursor* out);
int qjson_cursor_index(const qjson_cursor*, size_t i, qjson_cursor* out);

int qjson_cursor_get_str (const qjson_cursor*, const char*, size_t, const uint8_t**, size_t*);
int qjson_cursor_get_i64 (const qjson_cursor*, const char*, size_t, int64_t*);
int qjson_cursor_get_f64 (const qjson_cursor*, const char*, size_t, double*);
int qjson_cursor_get_bool(const qjson_cursor*, const char*, size_t, int*);
int qjson_cursor_typeof (const qjson_cursor*, const char*, size_t, int*);
int qjson_cursor_len (const qjson_cursor*, const char*, size_t, size_t*);
int qjson_cursor_bytes(const qjson_cursor*, size_t* byte_start, size_t* byte_end);
int qjson_cursor_object_entry_at(const qjson_cursor*, size_t i,
const uint8_t** key_ptr, size_t* key_len,
qjson_cursor* value_out);
]]

local C = ffi.load("qjson")
local C = require("qjson.lib")

local err_box = ffi.new("int[1]")
local i64_box = ffi.new("int64_t[1]")
Expand Down
Loading
Loading