glue

everyday Lua functions

 
 
View on GitHub
Download as .tar.gz
Download as .zip

local glue = require'glue'

tables
glue.index(t) -> dt switch keys with values
glue.keys(t[, sorted | cmp]) -> dt make a list of all the keys
glue.update(dt,t1,...) -> dt merge tables - overwrites keys
glue.merge(dt,t1,...) -> dt merge tables - no overwriting
glue.sortedpairs(t[, cmp])-> iterator like pairs() but in key order
lists
glue.extend(dt,t1,...) -> dt extend a list
glue.append(dt,v1,...) -> dt append non-nil values to a list
glue.shift(t,i,n) -> t shift list elements
strings
glue.gsplit(s,sep[, plain]) -> iterator split a string by a pattern
glue.trim(s) -> s remove padding
glue.escape(s[,mode])-> s escape magic pattern characters
glue.tohex(s) -> s string to hex
glue.fromhex(s) -> s hex to string
iterators
glue.collect([i,]iterator)-> t collect iterated values into a list
closures
glue.pass(...) -> ... does nothing, returns back all arguments
metatables
glue.inherit(t,parent) -> t set or clear inheritance
glue.autotable([t]) -> t autotable pattern
i/o
glue.fileexists(file) -> true | false check if a file exists and it's readable
glue.readfile(file[,format]) -> s | nil, err read the contents of a file into a string
glue.writefile(file,s[,format]) write a string to a file
errors
glue.assert(v,[message[,args...]]) -> args assert with error message formatting
glue.unprotect(ok,result,...) -> result,... | nil,result,... unprotect a protected call
glue.pcall(f,...) -> true,... | false,traceback pcall with traceback (not for Lua 5.1)
glue.fpcall(f,...) -> result | nil,traceback coding with finally and except
glue.fcall(f,...) -> result
modules
glue.autoload(t, submodule) -> t autoload table keys from submodules
glue.autoload(t, key, module|loader) -> t autoload table keys from submodules
glue.bin get the script's directory
glue.luapath(path[, index[, ext]]) insert a path in package.path
glue.cpath(path[, index]) insert a path in package.cpath
malloc
glue.malloc([ctype, ]size) -> cdata allocate an array using system's malloc
glue.malloc(ctype) -> cdata allocate a C type using system's malloc
glue.free(cdata) free malloc'ed memory

glue.index(t) -> dt

Switch table keys with values.

Examples

Extract a rfc850 date from a string. Use lookup tables for weekdays and months.

local weekdays = glue.index{'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'}
local months = glue.index{'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'}

--weekday "," SP 2DIGIT "-" month "-" 2DIGIT SP 2DIGIT ":" 2DIGIT ":" 2DIGIT SP "GMT"
--eg. Sunday, 06-Nov-94 08:49:37 GMT
function rfc850date(s)
   local w,d,mo,y,h,m,s = s:match'([A-Za-z]+), (%d+)%-([A-Za-z]+)%-(%d+) (%d+):(%d+):(%d+) GMT'
   d,y,h,m,s = tonumber(d),tonumber(y),tonumber(h),tonumber(m),tonumber(s)
   w = assert(weekdays[w])
   mo = assert(months[mo])
   if y then y = y + (y > 50 and 1900 or 2000) end
   return {wday = w, day = d, year = y, month = mo, hour = h, min = m, sec = s}
end

for k,v in pairs(rfc850date'Sunday, 06-Nov-94 08:49:37 GMT') do
   print(k,v)
end

Output

day   6
sec   37
wday  1
min   49
year  1994
month 11
hour  8

Copy-paste a bunch of defines from a C header file and create an inverse lookup table to find the name of a value at runtime.

--from ibase.h
info_end_codes = {
   isc_info_end             = 1,  --normal ending
   isc_info_truncated       = 2,  --receiving buffer too small
   isc_info_error           = 3,  --error, check status vector
   isc_info_data_not_ready  = 4,  --data not available for some reason
   isc_info_svc_timeout     = 64, --timeout expired
}
info_end_code_names = glue.index(info_end_codes)
print(info_end_code_names[64])

Output

isc_info_svc_timeout

glue.keys(t[, sorted | cmp]) -> dt

Make a list of all the keys of t, optionally sorted.

Examples

An API expects a list of things but you have them as keys in a table because you are indexing something on them.

For instance, you have a table of the form {socket = thread} but socket.select wants a list of sockets.

See also: glue.sortedpairs.


glue.update(dt,t1,...) -> dt

Update a table with elements of other tables, overwriting any existing keys.

  • nil arguments are skipped.

Examples

Create an options table by merging the options received as an argument (if any) over the default options.

function f(opts)
   opts = glue.update({}, default_opts, opts)
end

Shallow table copy:

t = glue.update({}, t)

Static multiple inheritance:

C = glue.update({}, A, B) --#TODO: find real-world example of multiple inheritance

See also: glue.extend, glue.inherit.


glue.merge(dt,t1,...) -> dt

Update a table with elements of other tables skipping on any existing keys.

  • nil arguments are skipped.

Examples

Normalize a data object with default values:

glue.merge(t, defaults)

See also: glue.update.


glue.sortedpairs(t[,cmp]) -> iterator<k,v>

Like pairs() but in key order.

The implementation creates a temporary table to sort the keys in.

See also: glue.keys.


glue.extend(dt,t1,...) -> dt

Extend the list with the elements of other lists.

  • nil arguments are skipped.
  • list elements are the ones from 1 to #dt.

Uses

Accumulating values from multiple list sources.

See also: glue.append, glue.update.


glue.append(dt,v1,...) -> dt

Append non-nil arguments to a list.

Uses

Appending an object to a flattened list of lists (eg. appending a path element to a 2d path).

See also: glue.extend, glue.update.


glue.shift(t,i,n) -> t

Shift all the list elements starting at index i, n positions to the left or further to the right.

For a positive n, shift the elements further to the right, effectively creating room for n new elements at index i. When n is 1, the effect is the same as for table.insert(t, i, t[i]). The old values at index i to i+n-1 are preserved, so #t still works after the shifting.

For a negative n, shift the elements to the left, effectively removing the n elements at index i. When n is -1, the effect is the same as for table.remove(t, i).

Uses

Removing a portion of a list or making room for more elements inside the list.

See also: glue.extend.


glue.gsplit(s,sep[,plain]) -> iterator<e[,captures...]>

Split a string by a separator pattern (or plain string) and iterate over the elements.

  • if sep is "" return the entire string in one iteration
  • if s is "" return s in one iteration
  • empty strings between separators are always returned, eg. glue.gsplit(',', ',') produces 2 empty strings
  • captures are allowed in sep and they are returned after the element, except for the last element for which they don't match (by definition).

Examples

for s in glue.gsplit('Spam eggs spam spam and ham', '%s*spam%s*') do
   print('"'..s..'"')
end

> "Spam eggs"
> ""
> "and ham"

Design notes

  • name choice: associate with gmatch and gsub
  • allowing captures in sep doesn't have very readable semantics

glue.trim(s) -> s

Remove whitespace (defined as Lua pattern %s) from the beginning and end of a string.


glue.escape(s[,mode]) -> pat

Escape magic characters of the string s so that it can be used as a pattern to string matching functions.

  • the optional argument mode can have the value "*i" (for case insensitive), in which case each alphabetical character in s will also be escaped as [aA] so that it matches both its lowercase and uppercase variants.
  • escapes embedded zeroes as the %z pattern.

Uses

  • workaround for lack of pattern syntax for "this part of a match is an arbitrary string"
  • workaround for lack of a case-insensitive flag in pattern matching functions

Design notes

Test the performance of the case-insensitive hack to see if it's feasible.


glue.tohex(s[,upper]) -> s

glue.tohex(n[,upper]) -> s

Convert a binary string or a Lua number to its hex representation.

  • lowercase by default
  • uppercase if upper is anything non-false, like say, the string "upper"
  • numbers must be in the unsigned 32 bit integer range

See also: glue.fromhex.


glue.fromhex(s) -> s

Convert a hex string to its binary representation.

See also: glue.tohex.


glue.collect([i, ]iterator) -> t

Iterate an iterator and collect its i'th return value of every step into a list.

  • i defaults to 1

Examples

Implementation of keys() and values() in terms of collect()

keys = function(t) return glue.collect(pairs(t)) end
values = function(t) return glue.collect(2,pairs(t)) end

Collecting string matches:

s = 'a,b,c,'
t = glue.collect(s:gmatch'(.-),')
for i=1,#t do print(t[i]) end

> a
> b
> c

Design notes

Alt. name: ipack - like pack but for iterators; collect is better at suggesting a process done in steps.


glue.pass(...) -> ...

The identity function. Does nothing, returns back all arguments.

Uses

Default value for optional callback arguments:

function urlopen(url, callback, errback)
   callback = callback or glue.pass
   errback = errback or glue.pass
   ...
   callback()
end

glue.inherit(t, parent) -> t

glue.inherit(t, nil) -> t

Set a table to inherit attributes from a parent table, or clear inheritance.

If the table has no metatable (and inheritance has to be set, not cleared) make it one.

Examples

Logging mixin:

AbstractLogger = glue.inherit({}, function(t,k) error('abstract '..k) end)
NullLogger = glue.inherit({log = function() end}, AbstractLogger)
PrintLogger = glue.inherit({log = function(self,...) print(...) end}, AbstractLogger)

HttpRequest = glue.inherit({
   perform = function(self, url)
      self:log('Requesting', url, '...')
      ...
   end
}, NullLogger)

LoggedRequest = glue.inherit({log = PrintLogger.log}, HttpRequest)

LoggedRequest:perform'http://lua.org/'

> Requesting   http://lua.org/   ...

Defining a module in Lua 5.2

_ENV = glue.inherit({},_G)
...

Hints:

  • to get the effect of static (single or multiple) inheritance, use glue.update.
  • when setting inheritance, you can pass in a function.

Design notes

t = setmetatable({},{__index=parent}) is not much longer and it's idiomatic, but doesn't shout inheritance at you (you have to process the indirection, like with functional idioms) and you can't use it to change the parent (a minor quibble nevertheless).

Overriding of methods needs an easy way to access the "parent" or to invoke a method on the parent. A top-level class could provide this simply by defining function Object:parent() return getmetatable(self).__index end.


glue.autotable([t]) -> t

Set a table to create/return missing keys as autotables.

In the example below, t.a, t.a.b, t.a.b.c are created automatically as autotables.

local t = autotable()
t.a.b.c.d = 'hello'

glue.fileexists(file) -> true | false

Checks whether a file exists and it's available for reading.

See also: glue.readfile.


glue.readfile(file[,format]) -> s | nil, err

Read the contents of a file into a string.

  • format can be t in which case the file will be read in text mode (default is binary mode).

See also: glue.writefile, glue.fileexists.


glue.writefile(file,s[,format])

Write the contents of a string to a file.

  • format can be t in which case the file will be written in text mode (default is binary mode).

See also: glue.readfile.


glue.assert(v[, message[, format_args...]])

Like assert but supports formatting of the error message using string.format.

This is better than assert(string.format(message, format_args...)) because it avoids creating the message string when the assertion is true.

Example

glue.assert(depth <= maxdepth, 'maximum depth %d exceeded', maxdepth)

glue.unprotect(ok,result,...) -> result,... | nil,result,...

In Lua, API functions conventionally signal errors by returning nil and an error message instead of raising exceptions. In the implementation however, using assert() and error() is preferred to coding explicit conditional flows to cover exceptional cases. Use this function to convert error-raising functions to nice nil,error-returning functions:

function my_API_function()
  return glue.unprotect(pcall(function()
    ...
    assert(...)
    ...
    error(...)
    ...
    return result_value
  end))
end

glue.pcall(f,...) -> true,... | false,error..'\n'..traceback

With Lua's pcall() you lose the stack trace, and with usual uses of pcall() you don't want that. This variant appends the traceback to the error message.

NOTE: Lua 5.2 and LuaJIT only.


glue.fpcall(f,...) -> result | nil,error..'\n'..traceback

glue.fcall(f,...) -> result

These constructs bring the ubiquitous try/finally/except idiom to Lua. The first variant returns nil,error when errors occur while the second re-raises the error.

Example

local result = glue.fpcall(function(finally, except, ...)
  local temporary_resource = acquire_resource()
  finally(function() temporary_resource:free() end)
  ...
  local final_resource = acquire_resource()
  except(function() final_resource:free() end)
  ... code that might break ...
  return final_resource
end, ...)

glue.autoload(t, submodules) -> t

glue.autoload(t, key, module|loader) -> t

Assign a metatable to t such that when a missing key is accessed, the module said to contain that key is require'd automatically.

The submodules argument is a table of form {key = module_name | load_function} specifying the corresponding Lua module (or load function) that make each key available to t. The alternative syntax allows specifying the key - submodule associations one by one.

Motivation

Module autoloading allows you to split the implementation of a module in many submodules containing optional, self-contained functionality, without having to make this visible in the user API. This effectively separates how you split your APIs from how you split the implementation, allowing you to change the way the implementation is split at a later time while keeping the API intact.

Example

main module (foo.lua):

local function bar() --function implemented in the main module
  ...
end

--create and return the module table
return glue.autoload({
   ...
   bar = bar,
}, {
   baz = 'foo_extra', --autoloaded function, implemented in module foo_extra
})

submodule (foo_extra.lua):

local foo = require'foo'

function foo.baz(...)
  ...
end

in usage:

local foo = require'foo'

foo.baz(...) -- foo_extra was now loaded automatically

glue.bin

Get the script's directory, based on lua-find-bin by David Manura. This allows finding files in the script's directory regardless of the directory that Lua is started in.

Example

local foobar = glue.readfile(glue.bin .. '/' .. file_near_this_script)

Caveats

This only works if glue itself can already be found and required (chicken/egg catch22 and the rest). The path is relative to the current directory, it stops working if the current directory is changed. The assumption is that if you can do chdir() then you can also do getfullpath() or at least getcurrentdir(), and thus correct glue.bin in time with:

glue.bin = getfullpath(glue.bin)

or

glue.bin = getcurrentdir() .. '/' .. glue.bin

glue.luapath(path[, index[, ext]])

Insert a Lua search pattern in package.path such that require will be able to load Lua modules from that path. The optional index arg specifies the insert position (default is 1, that is, before all existing paths; can be negative, to start counting from the end; can be the string 'after', which is the same as 0). The optional ext arg specifies the file extension to use (default is "lua").

glue.cpath(path[, index])

Insert a Lua search pattern in package.cpath such that require will be able to load Lua/C modules from that path. The index arg has the same meaning as with glue.luapath.

Example

glue.luapath(glue.bin)
glue.cpath(glue.bin)

require'foo' --looking for `foo` in the same directory as the running script first

glue.malloc([ctype, ]size) -> cdata

Allocate a ctype[size] array with system's malloc. Useful for allocating larger chunks of memory without hitting the default allocator's 2 GB limit.

  • the returned cdata has the type ctype(&)[size] so ffi.sizeof(cdata) returns the correct size.
  • ctype defaults to char.
  • failure to allocate results in error.
  • the memory is freed when the cdata gets collected or with glue.free().

REMEMBER! Just like with ffi.new, casting the result cdata further will get you weak references to the allocated memory. To transfer ownership of the memory, use ffi.gc(original, nil); ffi.gc(pointer, glue.free).

NOTE: LuaJIT only.

CAVEAT: For primitive types, you must specify a size, or glue.free() will not work!

glue.malloc(ctype) -> cdata

Allocate a ctype with system's malloc. The result has the type ctype&.

glue.free(cdata)

Free malloc'ed memory.

Example

local data = glue.malloc(100)
assert(ffi.sizeof(data) == 100)
glue.free(data)

local data = glue.malloc('int', 100)
assert(ffi.sizeof(data) == 100 * ffi.sizeof'int')
glue.free(data)

local data = glue.malloc('struct S')
assert(ffi.typeof(data) ==
assert(ffi.sizeof(data) == ffi.sizeof'struct S')
glue.free(data)

Tips

String functions are also in the glue.string table. You can extend the Lua string namespace:

glue.update(string, glue.string)

so you can use them as string methods:

s = s:trim()

Keywords

for syntax highlighting

glue.index, glue.keys, glue.update, glue.merge, glue.extend, glue.append, glue.shift, glue.gsplit, glue.trim, glue.escape, glue.collect, glue.pass, glue.inherit, glue.fileexists, glue.readfile, glue.writefile, glue.assert, glue.unprotect, glue.pcall, glue.fpcall, glue.fcall, glue.autoload, glue.bin, glue.luapath, glue.cpath

Design

glue_design