• Stars
    star
    112
  • Rank 312,240 (Top 7 %)
  • Language MoonScript
  • Created almost 9 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Test the shape or structure of a Lua table, inspired by React.PropTypes & LPeg

tableshape

test

A Lua library for verifying the shape (schema, structure, etc.) of a table, and transforming it if necessary. The type checking syntax is inspired by the PropTypes module of React. Complex types & value transformations can be expressed using an operator overloading syntax similar to LPeg.

Install

$ luarocks install tableshape

Quick usage

local types = require("tableshape").types

-- define the shape of our player object
local player_shape = types.shape{
  class = types.one_of{"player", "enemy"},
  name = types.string,
  position = types.shape{
    x = types.number,
    y = types.number,
  },
  inventory = types.array_of(types.shape{
    name = types.string,
    id = types.integer
  }):is_optional()
}



-- create a valid object to test the shape with
local player = {
  class = "player",
  name = "Lee",
  position = {
    x = 2.8,
    y = 8.5
  },
}

-- verify that it matches the shape
assert(player_shape(player))

-- let's break the shape to see the error message:
player.position.x = "heck"
assert(player_shape(player))

-- error: field `position`: field `x`: got type `string`, expected `number`

Transforming

A malformed value can be repaired to the expected shape by using the transformation operator and method. The input value is cloned and modified before being returned.

local types = require("tableshape").types

-- a type checker that will coerce a value into a number from a string or return 0
local number = types.number + types.string / tonumber + types.any / 0

number:transform(5) --> 5
number:transform("500") --> 500
number:transform("hi") --> 0
number:transform({}) --> 0

Because type checkers are composable objects, we can build more complex types out of existing types we've written:

-- here we reference our transforming number type from above
local coordinate = types.shape {
  x = number,
  y = number
}

-- a compound type checker that can fill in missing values
local player_shape = types.shape({
  name = types.string + types.any / "unknown",
  position = coordinate
})

local bad_player = {
  position = {
    x = "234",
    y = false
  }
}

local fixed_player = player_shape:transform(bad_player)

-- fixed_player --> {
--   name = "unknown",
--   position = {
--     x = 234,
--     y = 0
--   }
-- }

Tutorial

To load the library require it. The most important part of the library is the types table, which will give you acess to all the type checkers

local types = require("tableshape").types

You can use the types table to check the types of simple values, not just tables. Calling the type checker like a function will test a value to see if it matches the shape or type. It returns true on a match, or nil and the error message if it fails. (This is done with the __call metamethod, you can also use the check_value method directly)

types.string("hello!") --> true
types.string(777)      --> nil, expected type "string", got "number"

You can see the full list of the available types below in the reference.

The real power of tableshape comes from the ability to describe complex types by nesting the type checkers.

Here we test for an array of numbers by using array_of:

local numbers_shape = types.array_of(types.number)

assert(numbers_shape({1,2,3}))

-- error: item 2 in array does not match: got type `string`, expected `number`
assert(numbers_shape({1,"oops",3}))

Note: The type checking is strict, a string that looks like a number, "123", is not a number and will trigger an error!

The structure of a generic table can be tested with types.shape. It takes a mapping table where the key is the field to check, and the value is the type checker:

local object_shape = types.shape{
  id = types.number,
  name = types.string:is_optional(),
}

-- success
assert(object_shape({
  id = 1234,
  name = "hello world"
}))

-- sucess, optional field is not there
assert(object_shape({
  id = 1235,
}))


-- error: field `id`: got type `nil`, expected `number`
assert(object_shape({
  name = 424,
}))

The is_optional method can be called on any type checker to return a new type checker that can also accept nil as a value. (It is equivalent to t + types['nil'])

If multiple fields fail the type check in a shape, the error message will contain all the failing fields

You can also use a literal value to match it directly: (This is equivalent to using types.literal(v))

local object_shape = types.shape{
  name = "Cowcat"
}

-- error: field `name` expected `Cowcat`
assert(object_shape({
  name = "Cowdog"
}))

The one_of type constructor lets you specify a list of types, and will succeed if one of them matches. (It works the same as the + operator)

local func_or_bool = types.one_of { types.func, types.boolean }

assert(func_or_bool(function() end))

-- error: expected type "function", or type "boolean"
assert(func_or_bool(2345))

It can also be used with literal values as well:

local limbs = types.one_of{"foot", "arm"}

assert(limbs("foot")) -- success
assert(limbs("arm")) -- success

-- error: expected "foot", or "arm"
assert(limbs("baseball"))

The pattern type can be used to test a string with a Lua pattern

local no_spaces = types.pattern "^[^%s]*$"

assert(no_spaces("hello!"))

-- error: doesn't match pattern `^[^%s]*$`
assert(no_spaces("oh no!"))

These examples only demonstrate some of the type checkers provided. You can see all the other type checkers in the reference below.

Type operators

Type checker objects have the operators *, +, and / overloaded to provide a quick way to make composite types.

  • * — The all of (and) operator, both operands must match.
  • + — The first of (or) operator, the operands are checked against the value from left to right
  • / — The transform operator, when using the transform method, the value will be converted by what's to the right of the operator
  • % — The transform with state operator, same as transform, but state is passed as second argument

The 'all of' operator

The all of operator checks if a value matches multiple types. Types are checked from left to right, and type checking will abort on the first failed check. It works the same as types.all_of.

local s = types.pattern("^hello") * types.pattern("world$")

s("hello 777 world")   --> true
s("good work")         --> nil, "doesn't match pattern `^hello`"
s("hello, umm worldz") --> nil, "doesn't match pattern `world$`"

The 'first of' operator

The first of operator checks if a value matches one of many types. Types are checked from left to right, and type checking will succeed on the first matched type. It works the same as types.one_of.

Once a type has been matched, no additional types are checked. If you use a greedy type first, like types.any, then it will not check any additional ones. This is important to realize if your subsequent types have any side effects like transformations or tags.

local s = types.number + types.string

s(44)            --> true
s("hello world") --> true
s(true)          --> nil, "no matching option (got type `boolean`, expected `number`; got type `boolean`, expected `string`)"

The 'transform' operator

In type matching mode, the transform operator has no effect. When using the transform method, however, the value will be modified by a callback or changed to a fixed value.

The following syntax is used: type / transform_callback --> transformable_type

local t = types.string + types.any / "unknown"

The proceeding type can be read as: "Match any string, or for any other type, transform it into the string 'unknown'".

t:transform("hello") --> "hello"
t:transform(5)       --> "unknown"

Because this type checker uses types.any, it will pass for whatever value is handed to it. A transforming type can fail also fail, here's an example:

local n = types.number + types.string / tonumber

n:transform("5") --> 5
n:transform({})  --> nil, "no matching option (got type `table`, expected `number`; got type `table`, expected `string`)"

The transform callback can either be a function, or a literal value. If a function is used, then the function is called with the current value being transformed, and the result of the transformation should be returned. If a literal value is used, then the transformation always turns the value into the specified value.

A transform function is not a predicate, and can't directly cause the type checking to fail. Returning nil is valid and will change the value to nil. If you wish to fail based on a function you can use the custom type or chain another type checker after the transformation:

-- this will fail unless `tonumber` returns a number
local t = (types.string / tonumber) * types.number
t:transform("nothing") --> nil, "got type `nil`, expected `number`"

A common pattern for repairing objects involves testing for the types you know how to fix followed by + types.any, followed by a type check of the final type you want:

Here we attempt to repair a value to the expected format for an x,y coordinate:

local types = require("tableshape").types

local str_to_coord = types.string / function(str)
  local x,y = str:match("(%d+)[^%d]+(%d+)")
  if not x then return end
  return {
    x = tonumber(x),
    y = tonumber(y)
  }
end

local array_to_coord = types.shape{types.number, types.number} / function(a)
  return {
    x = a[1],
    y = a[2]
  }
end

local cord = (str_to_coord + array_to_coord + types.any) * types.shape {
  x = types.number,
  y = types.number
}

cord:transform("100,200")        --> { x = 100, y = 200}
cord:transform({5, 23})          --> { x = 5, y = 23}
cord:transform({ x = 9, y = 10}) --> { x = 9, y = 10}

Tags

Tags can be used to extract values from a type as it's checked. A tag is only saved if the type it wraps matches. If a tag type wraps type checker that transforms a value, then the tag will store the result of the transformation

local t = types.shape {
  a = types.number:tag("x"),
  b = types.number:tag("y"),
} + types.shape {
  types.number:tag("x"),
  types.number:tag("y"),
}

t({1,2})          --> { x = 1, y = 2}
t({a = 3, b = 9}) --> { x = 3, y = 9}

The values captured by tags are stored in the state object, a table that is passed throughout the entire type check. When invoking a type check, on success the return value will be the state object if any state is used, via tags or any of the state APIs listed below. If no state is used, true is returned on a successful check.

If a tag name ends in "[]" (eg. "items[]"), then repeated use of the tag name will cause each value to accumulate into an array. Otherwise, re-use of a tag name will cause the value to be overwritten at that name.

Scopes

You can use scopes to nest state objects (which includes the result of tags). A scope can be created with types.scope. A scope works by pushing a new state on the state stack. After the scope is completed, it is assigned to the previous scope at the specified tag name.

local obj = types.shape {
  id = types.string:tag("name"),
  age = types.number
}

local many = types.array_of(types.scope(obj, { tag = "results[]"}))

many({
  { id = "leaf", age = 2000 },
  { id = "amos", age = 15 }
}) --> { results = {name = "leaf"}, {name = "amos"}}

Note: In this example, we use the special [] syntax in the tag name to accumulate all values that are tagged into an array. If the [] was left out, then each tagged value would overwrite the previous.

If the tag of the types.scope is left out, then an anonymous scope is created. An anonymous scope is thrown away after the scope is exited. This style is useful if you use state for a local transformation, and don't need those values to affect the enclosing state object.

Transforming

The transform method on a type object is a special way to invoke a type check that allows the value to be changed into something else during the type checking process. This can be usful for repairing or normalizing input into an expected shape.

The simplest way to tranform a value is using the transform operator, /:

For example, we can type checker for URLs that will either accept a valid url, or convert any other string into a valid URL:

local url_shape = types.pattern("^https?://") + types.string / function(val)
  return "http://" .. val
end
url_shape:transform("https://itch.io") --> https://itch.io
url_shape:transform("leafo.net")       --> http://leafo.net
url_shape:transform({})                --> nil, "no matching option (expected string for value; got type `table`)"

We can compose transformable type checkers. Now that we know how to fix a URL, we can fix an array of URLs:

local urls_array = types.array_of(url_shape + types.any / nil)

local fixed_urls = urls_array:transform({
  "https://itch.io",
  "leafo.net",
  {}
  "www.streak.club",
})

-- will return:
-- {
--   "https://itch.io",
--   "http://leafo.net",
--   "http://www.streak.club"
-- }

The transform method of the array_of type will transform each value of the array. A special property of the array_of transform is to exclude any values that get turned into nil in the final output. You can use this to filter out any bad data without having holes in your array. (You can override this with the keep_nils option.

Note how we add the types.any / nil alternative after the URL shape. This will ensure any unrecognized values are turned to nil so that they can be filtered out from the array_of shape. If this was not included, then the URL shape will fail on invalid values and the the entire transformation would be aborted.

Transformation and mutable objects

Special care must be made when writing a transformation function when working with mutable objects like tables. You should never modify the object, instead make a clone of it, make the changes, then return the new object.

Because types can be deeply nested, it's possible that transformation may be called on a value, but the type check later fails. If you mutated the input value then there's no way to undo that change, and you've created a side effect that may break your program.

Never do this:

local types = require("tableshape").types

-- WARNING: READ CAREFULLY
local add_id = types.table / function(t)
  -- NEVER DO THIS
  t.id = 100
  -- I repeat, don't do what's in the line above
  return t
end

-- This is why, imagine we create a new compund type:

local array_of_add_id = types.array_of(add_id)

-- And now we pass in the following malformed object:

local items = {
  { entry = 1},
  "entry2",
  { entry = 3},
}


-- And attempt to verify it by transforming it:

local result,err = array_of_add_id:transform(items)

-- This will fail because there's an value in items that will fail validation for
-- add_id. Since types are processed incrementally, the first entry would have
-- been permanently changed by the transformation. Even though the check failed,
-- the data is partially modified and may result in a hard-to-catch bug.

print items[1] --> = { id = 100, entry = 1}
print items[3] --> = { entry = 3}

Luckily, tableshape provides a helper type that is designed to clone objects, types.clone. Here's the correct way to write the transformation:

local types = require("tableshape").types

local add_id = types.table / function(t)
  local new_t = assert(types.clone:transform(t))
  new_t.id = 100
  return new_t
end

Advanced users only: Since types.clone is a type itself, you can chain it before any dirty functions you may have to ensure that mutations don't cause side effects to persist during type validation: types.table * types.clone / my_messy_function

The built in composite types that operate on objects will automatically clone an object if any of the nested types have transforms that return new values. This includes composite type constructors like types.shape, types.array_of, types.map_of, etc. You only need to be careful about mutations when using custom transformation functions.

Reference

local types = require("tableshape").types

Type constructors

Type constructors build a type checker configured by the parameters you pass. Here are all the available ones, full documentation is below.

  • types.shape - checks the shape of a table
  • types.partial - shorthand for an open types.shape
  • types.one_of - checks if value matches one of the types provided
  • types.pattern - checks if Lua pattern matches value
  • types.array_of - checks if value is array containing a type
  • types.array_contains - checks if value is an array that contains a type (short circuits by default)
  • types.map_of - checks if value is table that matches key and value types
  • types.literal - checks if value matches the provided value with ==
  • types.custom - lets you provide a function to check the type
  • types.equivalent - checks if values deeply compare to one another
  • types.range - checks if value is between two other values
  • types.proxy - dynamically load a type checker

types.shape(table_dec, options={})

Returns a type checker tests for a table where every key in table_dec has a type matching the associated value. The associated value can also be a literal value.

local t = types.shape{
  category = "object", -- matches the literal value `"object"`
  id = types.number,
  name = types.string
}

The following options are supported:

  • open — The shape will accept any additional fields without failing
  • extra_fields — A type checker for use with extra keys. For each extra field in the table, the value {key = value} is passed to the extra_fields type checker. During transformation, the table can be transformed to change either the key or value. Transformers that return nil will clear the field. See below for examples. The extra keys shape can also use tags.

Examples with extra_fields:

Basic type test for extra fields:

local t = types.shape({
  name = types.string
}, {
  extra_fields = types.map_of(types.string, types.number)
})

t({
  name = "lee",
  height = "10cm",
  friendly = false,
}) --> nil, "field `height` value in table does not match: got type `string`, expected `number`"

A transform can be used on extra_fields as well. In this example all extra fields are removed:

local t = types.shape({
  name = types.string
}, {
  extra_fields = types.any / nil
})

t:transform({
  name = "amos",
  color = "blue",
  1,2,3
}) --> { name = "amos"}

Modifying the extra keys using a transform:

local types = require("tableshape").types

local t = types.shape({
  name = types.string
}, {
  extra_fields = types.map_of(
    -- prefix all extra keys with _
    types.string / function(str) return "_" .. str end,

    -- leave values as is
    types.any
  )
})

t:transform({
  name = "amos",
  color = "blue"
}) --> { name = "amos", _color = "blue" }

types.partial(table_dec, options={})

The same as types.shape but sets open = true by default. This alias function was added because open shape objects are common when using tableshape.

local types = require("tableshape").types

local t = types.partial {
  name = types.string\tag "player_name"
}

t({
  t: "character"
  name: "Good Friend"
}) --> { player_name: "Good Friend" }

types.array_of(item_type, options={})

Returns a type checker that tests if the value is an array where each item matches the provided type.

local t = types.array_of(types.shape{
  id = types.number
})

The following options are supported:

  • keep_nils — By default, if a value is transformed into a nil then it won't be kept in the output array. If you need to keep these holes then set this option to true
  • length — Provide a type checker to be used on the length of the array. The length is calculated with the # operator. It's typical to use types.range to test for a range

types.array_contains(item_type, options={})

Returns a type checker that tests if item_type exists in the array. By default, short_circuit is enabled. It will search until it finds the first instance of item_type in the array then stop with a success. This impacts transforming types, as only the first match will be transformed by default. To process every entry in the array, set short_circuit = false in the options.

local t = types.array_contains(types.number)

t({"one", "two", 3, "four"}) --> true
t({"hello", true}) --> fails

The following options are supported:

  • short_circuit — (default true) Will stop scanning over the array if a single match is found
  • keep_nils — By default, if a value is transformed into a nil then it won't be kept in the output array. If you need to keep these holes then set this option to true

types.map_of(key_type, value_type)

Returns a type checker that tests for a table where every key and value matches the respective type checkers provided as arguments.

local t = types.map_of(types.string, types.any)

When transforming a map_of, you can remove fields from the table by transforming either the key or value to nil.

-- this will remove all fields with non-string keys
local t = types.map_of(types.string + types.any / nil, types.any)

t:transform({
  1,2,3,
  hello = "world"
}) --> { hello = "world" }

types.one_of({type1, type2, ...})

Returns a type checker that tests if the value matches one of the provided types. A literal value can also be passed as a type.

local t = types.one_of{"none", types.number}

types.pattern(lua_pattern)

Returns a type checker that tests if a string matches the provided Lua pattern

local t = types.pattern("^#[a-fA-F%d]+$")

types.literal(value)

Returns a type checker that checks if value is equal to the one provided. When using shape this is normally unnecessary since non-type checker values will be checked literally with ==. This lets you attach a repair function to a literal check.

local t = types.literal "hello world"
assert(t("hello world") == true)
assert(t("jello world") == false)

types.custom(fn)

Returns a type checker that calls the function provided to verify the value. The function will receive the value being tested as the first argument, and the type checker as the second.

The function should return true if the value passes, or nil and an error message if it fails.

local is_even = types.custom(function(val)
  if type(val) == "number" then
    if val % 2 == 0 then
      return true
    else
      return nil, "number is not even"
    end
  else
    return nil, "expected number"
  end
end)

types.equivalent(val)

Returns a type checker that will do a deep compare between val and the input.

local t = types.equivalent {
  color = {255,100,128},
  name = "leaf"
}

-- although we're testing a different instance of the table, the structure is
-- the same so it passes
t {
  name = "leaf"
  color = {255,100,128},
} --> true

types.range(left, right)

Creates a type checker that will check if a value is beween left and right inclusive. The type of the value is checked before doing the comparison: passing a string to a numeric type checker will fail up front.

local nums = types.range 1, 20
local letters = types.range "a", "f"

nums(4)    --> true
letters("c")  --> true
letters("n")  --> true

This checker works well with the length checks for strings and arrays.

types.proxy(fn)

The proxy type checker will execute the provided function, fn, when called and use the return value as the type checker. The fn function must return a valid tableshape type checker object.

This can be used to have types that circularly depend on one another, or handle recursive types. fn is called every time the proxy checks a value, if you want to optimize for performance then you are responsible for caching type checker that is returned.

An example recursive type checker:

local entity_type = types.shape {
  name = types.string,
  child = types['nil'] + types.proxy(function() return entity_type end)
}

A proxy is needed above because the value of entity_type is nil while the type checker is being constructed. By using the proxy, we can create a closure to the variable that will eventually hold the entity_type checker.

Built in types

Built in types can be used directly without being constructed.

  • types.string - checks for type(val) == "string"
  • types.number - checks for type(val) == "number"
  • types['function'] - checks for type(val) == "function"
  • types.func - alias for types['function']
  • types.boolean - checks for type(val) == "boolean"
  • types.userdata - checks for type(val) == "userdata"
  • types.table - checks for type(val) == "table"
  • types['nil'] - checks for type(val) == "nil"
  • types.null - alias for types['nil']
  • types.array - checks for table of numerically increasing indexes
  • types.integer - checks for a number with no decimal component
  • types.clone - creates a shallow copy of the input, fails if value is not cloneable (eg. userdata, function)

Additionally there's the special any type:

  • types.any - succeeds no matter value is passed, including nil

Type methods

Every type checker has the follow methods:

type(value) or type:check_value(value)

Calling check_value is equivalent to calling the type checker object like a function. The __call metamethod is provided on all type checker objects to allow you easily test a value by treating them like a function.

Tests value against the type checker. Returns true (or the current state object) if the value passes the check. Returns nil and an error message as a string if there is a mismatch. The error message will identify where the mismatch happened as best it can.

check_value will abort on the first error found, and only that error message is returned.

Note: Under the hood, checking a value will always execute the full transformation, but the resulting object is thrown away, and only the state is returned. Keep this in mind because there is no performance benefit to calling check_value over transform

type:transform(value, initial_state=nil)

Will apply transformation to the value with the provided type. If the type does not include any transformations then the same object will be returned assuming it matches the type check. If transformations take place then a new object will be returned with all other fields copied over.

You can use the transform operator (/) to specify how values are transformed.

A second argument can optionally be provided for the initial state. This should be a Lua table.

If no state is provided, an empty Lua table will automatically will automatically be created if any of the type transformations make changes to the state.

The state object is used to store the result of any tagged types. The state object can also be used to store data across the entire type checker for more advanced functionality when using the custom state operators and types.

local t = types.number + types.string / tonumber

t:transform(10) --> 10
t:transform("15") --> 15

On success, this method will return the resulting value and the resulting state. If no state is used, then no state will be returned. On failure, the method will return nil and a string error message.

type:repair(value)

This method is deprecated, use the type:transform instead

An alias for type:transform(value)

type:is_optional()

Returns a new type checker that matches the same type, or nil. This is effectively the same as using the expression:

local optional_my_type = types["nil"] + my_type

Internally, though, is_optional creates new OptionalType node in the type hierarchy to make printing summaries and error messages more clear.

type:describe(description)

Returns a wrapped type checker that will use description to describe the type when an error message is returned. description can either be a string literal, or a function. When using a function, it must return the description of the type as a string.

type:tag(name_or_fn)

Causes the type checker to save matched values into the state object. If name_or_fn is a string, then the tested value is stored into the state with key name_or_fn.

If name_or_fn is a function, then you provide a callback to control how the state is updated. The function takes as arguments the state object and the value that matched:

-- an example tag function that accumulates an array
types.number:tag(function(state, value)
  -- nested objects should be treated as read only, so modifications are done to a copy
  if state.numbers then
    state.numbers = { unpack state.numbers }
  else
    state.numbers = { }
  end

  table.insert(state.numbers, value)
end)

This is illustrative example. If you need to accumulate a list of values then use the [] syntax for tag names.

You can mutate the state argument with any changes. The return value of this function is ignored.

Note that state objects are generally immutable. Whenever a state modifying operation takes place, the modification is done to a copy of the state object. This is to prevent changes to the state object from being kept around when a failing type is tested.

A function tag gets a copy of the current state as its first argument ready for editing. The copy is a shallow copy. If you have any nested objects then it's necessary to clone them before making any modifications, as seen in the example above.

type:scope(name)

Pushes a new state object on top of the stack. After the scoped type matches, the state it created is assigned to the previous scope with the key name.

It is equivalent to using the types.scope constructor like so:

-- The following two lines are equivalent
type:scope(name)                  --> scoped type
types.scope(type, { tag = name }) --> scoped type

shape_type:is_open()

This method is deprecated, use the open = true constructor option on shapes instead

This method is only available on a type checker generated by types.shape.

Returns a new shape type checker that won't fail if there are extra fields not specified.

type:on_repair(func)

An alias for the transform pattern:

type + types.any / func * type

In English, this will let a value that matches type pass through, otherwise for anything else call func(value) and let the return value pass through if it matches type, otherwise fail.

Changelog

Jan 25 2021 - 2.2.0

  • Fixed bug where state could be overidden when tagging in array_contains
  • Expose (and add docs for) for types.proxy
  • Add experimental Annotated type
  • Update test suite to GitHub Actions

Oct 19 2019 - 2.1.0

  • Add types.partial alias for open shape
  • Add types.array_contains
  • Add not type, and unary minus operator
  • Add MoonScript module: class_type, instance_type, instance_type checkers

Aug 09 2018 - 2.0.0

  • Add overloaded operators to compose types
  • Add transformation interface
  • Add support for tagging
  • Add state parameter that's passed through type checks
  • Replace repair interface with simple transform
  • Error messages will never re-output the value
  • Type objects have a new interface to describe their shape

Feb 10 2016 - 1.2.1

  • Fix bug where literal fields with no dot operator could not be checked
  • Better failure message when field doesn't match literal value
  • Add types.nil

Feb 04 2016 - 1.2.0

  • Add the repair interface

Jan 25 2016 - 1.1.0

  • Add types.map_of
  • Add types.any

Jan 24 2016

  • Initial release

License (MIT)

Copyright (C) 2022 by Leaf Corcoran

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

More Repositories

1

moonscript

🌙 A language that compiles to Lua
Lua
3,026
star
2

lapis

A web framework for Lua and OpenResty written in MoonScript
MoonScript
2,923
star
3

sticky-kit

A jQuery plugin for creating smart sticky elements
CoffeeScript
2,908
star
4

lessphp

LESS compiler written in PHP
PHP
2,210
star
5

scssphp

SCSS compiler written in PHP
PHP
1,351
star
6

sightreading.training

🎹 Sight reading training tool
JavaScript
443
star
7

magick

Lua bindings to ImageMagick for LuaJIT using FFI
Lua
404
star
8

pgmoon

A pure Lua Postgres driver for use in OpenResty & more
MoonScript
393
star
9

gifine

Quickly record and edit gifs and videos of your desktop
Lua
285
star
10

etlua

Embedded Lua templates
Lua
219
star
11

aroma

a game engine: lua, opengl es 2.0, native client
C
197
star
12

streak.club

a website for running creative streaks
MoonScript
142
star
13

gifserver

A server for transcoding gif to video on the fly
Go
102
star
14

lovekit

Miscellaneous code for making games in LOVE with MoonScript
Lua
96
star
15

moonscript-vim

MoonScript support for vim
Vim Script
96
star
16

lua-enet

Bindings to ENet for Lua
C
85
star
17

gh-actions-lua

GitHub action for Lua/LuaJIT
JavaScript
83
star
18

heroku-openresty

Run OpenResty on Heroku with the Lua buildpack
Lua
78
star
19

sitegen

static site generator in MoonScript
Lua
76
star
20

lua-payments

Various payment provider APIs for Lua (and OpenResty): Stripe, PayPal
MoonScript
72
star
21

web_sanitize

Lua library for sanitizing, parsing, and editing untrusted HTML
MoonScript
70
star
22

moonlisp

a Lisp that compiles to Lua
C
67
star
23

scssphp-compass

Compass for scssphp
PHP
66
star
24

image-server-tutorial

An example of an image processing server in OpenResty and Lua
Lua
66
star
25

compohub

A website for listing game jams
CoffeeScript
62
star
26

lua-openai

OpenAI API bindings for Lua
MoonScript
52
star
27

lapis-community

Pluggable message board for Lapis powered websites
MoonScript
49
star
28

goattracker2

a fork of goattracker2
C
45
star
29

itchio-app-old

Desktop itch.io client
C++
45
star
30

gh-actions-luarocks

GitHub action for installing LuaRocks
JavaScript
43
star
31

loadkit

Loadkit allows you to load arbitrary files within the Lua package path
MoonScript
42
star
32

lapis-console

Interactive console for working with Lapis
Lua
42
star
33

cloud_storage

A Lua library for communicating with Google Cloud Storage
Lua
40
star
34

moonscript-javascript

MoonScript compiled to JavaScript with Emscripten
JavaScript
38
star
35

moonscript-tmbundle

textmate support for MoonScript
37
star
36

lapis-bayes

Naive Bayes classifier for use in Lua
MoonScript
30
star
37

moonrocks

command line tool for working with rocks.moonscript.org
Lua
29
star
38

lua-twitter

A Lua twitter library that works with OpenResty or LuaSocket
Lua
27
star
39

lua-mailgun

Lua bindings to Mailgun HTTP API
MoonScript
26
star
40

ludum-dare-browser

a website for browsing ludum dare games
HTML
25
star
41

lapis-redis

Redis integration for Lapis
Lua
23
star
42

lua-syntaxhighlight

A code syntax to HTML highlighter using lexers from Textadept
Lua
21
star
43

luajit-geoip

luajit bindings to maxmind geoip
MoonScript
21
star
44

lapis-systemd

systemd integration for lapis
Lua
18
star
45

lapis-exceptions

Exception tracking for Lapis
MoonScript
17
star
46

moonscript-javascript-compiler

compile moonscript to javascript
MoonScript
16
star
47

mursic

sight reading training tool
MoonScript
15
star
48

lapis-site

The homepage for Lapis
HTML
14
star
49

lua-date

LuaDate 2 modified to work with newer versions of lua/luajit
Lua
14
star
50

lua-base58

base58 decode and encode for strings in pure lua
MoonScript
13
star
51

moondoc

MoonScript library documentation generator
Lua
13
star
52

lapis-archlinux-docker

Dockerfile for running lapis in archlinux
Dockerfile
13
star
53

moonscript-love

LÖVE game engine with baked in moonscript support
C
13
star
54

lua-uinput

A Lua library for creating a virtual keyboard on Linux with uinput
MoonScript
13
star
55

ludum-dare-22

A game built in 48 hours for the Ludum Dare Competition
12
star
56

moonscript-site

CSS
11
star
57

heroku-lapis-example

An example of running Lapis on Heroku with heroku-openresty
Lua
11
star
58

imagesize

Detect size & format of image file
MoonScript
10
star
59

ludum-dare-27

theme: 10 seconds
MoonScript
9
star
60

ludum-dare-38

lets make a game "a small world"
MoonScript
9
star
61

ludum-dare-25

Make a game in 48 hours!
MoonScript
9
star
62

moonscript-textadept

Textadept support for MoonScript
MoonScript
9
star
63

saltw-bot

irc/twitch bot written in MoonScript
MoonScript
9
star
64

lapis-stats

Statsd and Influxdb support for Lua, OpenResty & Lapis
Lua
8
star
65

selfwatch

inspired by selfspy
Go
8
star
66

snes-renoise-instruments

Various snes samples ripped from spc -> xrni
8
star
67

lapis-spec-screenshot

A busted screenshot handler that takes images of your pages when testing
Lua
8
star
68

ludum-dare-24

Let's make a video game!
7
star
69

giflib

gif
MoonScript
7
star
70

garfield

garfield comic viewer 🐱
MoonScript
7
star
71

weeklyloops

loops for http://streak.club/s/134/weekly-loop
Go
7
star
72

lapis-annotate

Annotate lapis models with their schema from the database
Lua
7
star
73

moonscrape

web scraper
MoonScript
6
star
74

lessphp-site

the lessphp homepage
PHP
6
star
75

lapis-eswidget

A widget base class designed for generating ES modules for bundling JavaScript & more
MoonScript
5
star
76

wallrun-js

Trying to get love2d game working in browser using fengari
MoonScript
5
star
77

elng

an interpreted language running on erlang
Erlang
5
star
78

dullcache

A simple large file cache for sitting in front of storage provider to offload bandwidth
Go
5
star
79

ludum-dare-32

Something butt
MoonScript
4
star
80

uglyphp

a templating language for php with emphasis on macros
PHP
4
star
81

fireplace

GTK+ campfire client
Python
4
star
82

album-1

my first album, chiptune
4
star
83

net.leafo.MIDIThing

Control your MIDI modules from Renoise
TeX
4
star
84

gh-actions-openresty

Install OpenResty inside of your GitHub Actions runner
JavaScript
4
star
85

ludum-dare-23

make a game in 48 hours!
4
star
86

talkxtyper

Type with your voice
Go
4
star
87

workdad

a chorded keyboard layout and training program
MoonScript
3
star
88

lapis-http

HTTP library wrangler for OpenResty & Lua
MoonScript
3
star
89

somestory

lets make a game for real this time....
Lua
3
star
90

ludum-dare-30

Connected worlds
Lua
3
star
91

pixel-react

creating pixelated game interface with html in react
JavaScript
3
star
92

st

my terminal
Objective-C
3
star
93

awesome-config

My awesome configuration
Lua
2
star
94

godot-game-1

GDScript
2
star
95

ludum-dare-41

combine two genres
GDScript
2
star
96

noteshed

a personal wiki for notes
2
star
97

ludum-dare-31

Entire Game on One Screen
MoonScript
2
star
98

ludum-dare-40

Lets make another game
Lua
2
star
99

moonparse

MoonScript
2
star
100

gslog2pg

Copy Google Cloud storage logs into PostgreSQL
MoonScript
2
star