summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Miodrag Milanovic <mmicko@gmail.com>2016-02-14 10:58:18 +0100
committer Miodrag Milanovic <mmicko@gmail.com>2016-02-14 10:58:18 +0100
commitccae0382bb750c1deded19e05b34933a8303465e (patch)
tree1a2f0ae972163e6c1c76611b464d4257947c003e
parent2db49088141b6238e92aecc4c073076a02c73065 (diff)
Added plugins and boot.lua as startup script [Miodrag Milanovic]
-rw-r--r--.gitignore1
-rw-r--r--plugins/boot.lua26
-rw-r--r--plugins/coro-channel/LICENSE22
-rw-r--r--plugins/coro-channel/README.md2
-rw-r--r--plugins/coro-channel/init.lua128
-rw-r--r--plugins/coro-fs/LICENSE22
-rw-r--r--plugins/coro-fs/README.md2
-rw-r--r--plugins/coro-fs/init.lua222
-rw-r--r--plugins/coro-http/LICENSE22
-rw-r--r--plugins/coro-http/README.md2
-rw-r--r--plugins/coro-http/init.lua187
-rw-r--r--plugins/coro-net/LICENSE22
-rw-r--r--plugins/coro-net/README.md2
-rw-r--r--plugins/coro-net/init.lua113
-rw-r--r--plugins/coro-tls/LICENSE22
-rw-r--r--plugins/coro-tls/README.md2
-rw-r--r--plugins/coro-tls/init.lua122
-rw-r--r--plugins/coro-wrapper/LICENSE22
-rw-r--r--plugins/coro-wrapper/README.md2
-rw-r--r--plugins/coro-wrapper/init.lua41
-rw-r--r--plugins/http-codec/LICENSE202
-rw-r--r--plugins/http-codec/README.md2
-rw-r--r--plugins/http-codec/init.lua291
-rw-r--r--plugins/json/LICENSE22
-rw-r--r--plugins/json/README.md2
-rw-r--r--plugins/json/init.lua732
-rw-r--r--plugins/mime/LICENSE22
-rw-r--r--plugins/mime/README.md2
-rw-r--r--plugins/mime/init.lua194
-rw-r--r--plugins/path/LICENSE202
-rw-r--r--plugins/path/README.md2
-rw-r--r--plugins/path/init.lua139
-rw-r--r--plugins/pretty-print/LICENSE202
-rw-r--r--plugins/pretty-print/README.md2
-rw-r--r--plugins/pretty-print/init.lua362
-rw-r--r--plugins/querystring/LICENSE202
-rw-r--r--plugins/querystring/README.md2
-rw-r--r--plugins/querystring/init.lua105
-rw-r--r--plugins/weblit/LICENSE22
-rw-r--r--plugins/weblit/README.md239
-rw-r--r--plugins/weblit/app.lua261
-rw-r--r--plugins/weblit/auto-headers.lua92
-rw-r--r--plugins/weblit/etag-cache.lua39
-rw-r--r--plugins/weblit/init.lua8
-rw-r--r--plugins/weblit/logger.lua10
-rw-r--r--plugins/weblit/static.lua62
-rw-r--r--plugins/weblit/websocket.lua82
-rw-r--r--plugins/websocket-codec/LICENSE22
-rw-r--r--plugins/websocket-codec/README.md2
-rw-r--r--plugins/websocket-codec/init.lua261
-rw-r--r--src/emu/debug/debugcpu.cpp3
-rw-r--r--src/emu/emuopts.cpp1
-rw-r--r--src/emu/emuopts.h2
-rw-r--r--src/emu/machine.cpp4
-rw-r--r--src/emu/mame.cpp9
55 files changed, 4789 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index a3357981d32..148b004be92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
!/hlsl/
!/keymaps/
!/nl_examples/
+!/plugins/
!/regtests/
!/samples/
!/scripts/
diff --git a/plugins/boot.lua b/plugins/boot.lua
new file mode 100644
index 00000000000..e6cdde58004
--- /dev/null
+++ b/plugins/boot.lua
@@ -0,0 +1,26 @@
+local uv = require('luv')
+local cwd = uv.cwd()
+package.path = cwd .. "/plugins/?.lua;" .. cwd .. "/plugins/?/init.lua"
+
+require('weblit/app')
+
+ .bind({
+ host = "0.0.0.0",
+ port = 8080
+ })
+
+ .use(require('weblit/logger'))
+ .use(require('weblit/auto-headers'))
+ .use(require('weblit/etag-cache'))
+
+ .route({
+ method = "GET",
+ path = "/",
+ }, function (req, res, go)
+ res.code = 200
+ res.headers["Content-Type"] = "text/html"
+ res.body = "<h1>Hello!</h1>\n"
+ end)
+
+ .start()
+
diff --git a/plugins/coro-channel/LICENSE b/plugins/coro-channel/LICENSE
new file mode 100644
index 00000000000..5789b767285
--- /dev/null
+++ b/plugins/coro-channel/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Tim Caswell
+
+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.
+
diff --git a/plugins/coro-channel/README.md b/plugins/coro-channel/README.md
new file mode 100644
index 00000000000..3f5bb397c69
--- /dev/null
+++ b/plugins/coro-channel/README.md
@@ -0,0 +1,2 @@
+# luv-coro-channel
+A luv port of creationix/coro-channel from lit.luvit.io
diff --git a/plugins/coro-channel/init.lua b/plugins/coro-channel/init.lua
new file mode 100644
index 00000000000..0b715a0d470
--- /dev/null
+++ b/plugins/coro-channel/init.lua
@@ -0,0 +1,128 @@
+local exports = {}
+exports.name = "creationix/coro-channel"
+exports.version = "1.2.0"
+exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-channel.lua"
+exports.description = "An adapter for wrapping uv streams as coro-streams and chaining filters."
+exports.tags = {"coro", "adapter"}
+exports.license = "MIT"
+exports.author = { name = "Tim Caswell" }
+
+local function wrapRead(socket)
+ local paused = true
+ local queue = {}
+ local waiting
+ local onRead
+
+ function onRead(err, chunk)
+ local data = err and {nil, err} or {chunk}
+ if waiting then
+ local thread = waiting
+ waiting = nil
+ assert(coroutine.resume(thread, unpack(data)))
+ else
+ queue[#queue + 1] = data
+ if not paused then
+ paused = true
+ assert(socket:read_stop())
+ end
+ end
+ end
+
+ return function ()
+ if #queue > 0 then
+ return unpack(table.remove(queue, 1))
+ end
+ if paused then
+ paused = false
+ assert(socket:read_start(onRead))
+ end
+ waiting = coroutine.running()
+ return coroutine.yield()
+ end
+
+end
+
+local function wrapWrite(socket)
+
+ local function wait()
+ local thread = coroutine.running()
+ return function (err)
+ assert(coroutine.resume(thread, err))
+ end
+ end
+
+ local function shutdown()
+ socket:shutdown(wait())
+ coroutine.yield()
+ if not socket:is_closing() then
+ socket:close()
+ end
+ end
+
+ return function (chunk)
+ if chunk == nil then
+ return shutdown()
+ end
+ assert(socket:write(chunk, wait()))
+ local err = coroutine.yield()
+ return not err, err
+ end
+
+end
+
+exports.wrapRead = wrapRead
+exports.wrapWrite = wrapWrite
+
+-- Given a raw uv_stream_t userdata, return coro-friendly read/write functions.
+function exports.wrapStream(socket)
+ return wrapRead(socket), wrapWrite(socket)
+end
+
+
+function exports.chain(...)
+ local args = {...}
+ local nargs = select("#", ...)
+ return function (read, write)
+ local threads = {} -- coroutine thread for each item
+ local waiting = {} -- flag when waiting to pull from upstream
+ local boxes = {} -- storage when waiting to write to downstream
+ for i = 1, nargs do
+ threads[i] = coroutine.create(args[i])
+ waiting[i] = false
+ local r, w
+ if i == 1 then
+ r = read
+ else
+ function r()
+ local j = i - 1
+ if boxes[j] then
+ local data = boxes[j]
+ boxes[j] = nil
+ assert(coroutine.resume(threads[j]))
+ return unpack(data)
+ else
+ waiting[i] = true
+ return coroutine.yield()
+ end
+ end
+ end
+ if i == nargs then
+ w = write
+ else
+ function w(...)
+ local j = i + 1
+ if waiting[j] then
+ waiting[j] = false
+ assert(coroutine.resume(threads[j], ...))
+ else
+ boxes[i] = {...}
+ coroutine.yield()
+ end
+ end
+ end
+ assert(coroutine.resume(threads[i], r, w))
+ end
+ end
+end
+
+return exports
diff --git a/plugins/coro-fs/LICENSE b/plugins/coro-fs/LICENSE
new file mode 100644
index 00000000000..5789b767285
--- /dev/null
+++ b/plugins/coro-fs/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Tim Caswell
+
+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.
+
diff --git a/plugins/coro-fs/README.md b/plugins/coro-fs/README.md
new file mode 100644
index 00000000000..79ef57330a0
--- /dev/null
+++ b/plugins/coro-fs/README.md
@@ -0,0 +1,2 @@
+# luv-coro-fs
+A luv port of lit's coro-fs module
diff --git a/plugins/coro-fs/init.lua b/plugins/coro-fs/init.lua
new file mode 100644
index 00000000000..f4be5bff832
--- /dev/null
+++ b/plugins/coro-fs/init.lua
@@ -0,0 +1,222 @@
+local exports = {}
+exports.name = "creationix/coro-fs"
+exports.version = "1.3.0"
+exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-fs.lua"
+exports.description = "A coro style interface to the filesystem."
+exports.tags = {"coro", "fs"}
+exports.license = "MIT"
+exports.author = { name = "Tim Caswell" }
+
+local uv = require('luv')
+local fs = exports
+local pathJoin = require('path').join
+
+local function noop() end
+
+local function makeCallback()
+ local thread = coroutine.running()
+ return function (err, value, ...)
+ if err then
+ assert(coroutine.resume(thread, nil, err))
+ else
+ assert(coroutine.resume(thread, value == nil and true or value, ...))
+ end
+ end
+end
+
+function fs.mkdir(path, mode)
+ uv.fs_mkdir(path, mode or 511, makeCallback())
+ return coroutine.yield()
+end
+function fs.open(path, flags, mode)
+ uv.fs_open(path, flags or "r", mode or 438, makeCallback())
+ return coroutine.yield()
+end
+function fs.unlink(path)
+ uv.fs_unlink(path, makeCallback())
+ return coroutine.yield()
+end
+function fs.stat(path)
+ uv.fs_stat(path, makeCallback())
+ return coroutine.yield()
+end
+function fs.lstat(path)
+ uv.fs_lstat(path, makeCallback())
+ return coroutine.yield()
+end
+function fs.symlink(target, path)
+ uv.fs_symlink(target, path, makeCallback())
+ return coroutine.yield()
+end
+function fs.readlink(path)
+ uv.fs_readlink(path, makeCallback())
+ return coroutine.yield()
+end
+function fs.fstat(fd)
+ uv.fs_fstat(fd, makeCallback())
+ return coroutine.yield()
+end
+function fs.chmod(fd, path)
+ uv.fs_chmod(fd, path, makeCallback())
+ return coroutine.yield()
+end
+function fs.fchmod(fd, mode)
+ uv.fs_fchmod(fd, mode, makeCallback())
+ return coroutine.yield()
+end
+function fs.read(fd, length, offset)
+ uv.fs_read(fd, length or 1024*48, offset or -1, makeCallback())
+ return coroutine.yield()
+end
+function fs.write(fd, data, offset)
+ uv.fs_write(fd, data, offset or -1, makeCallback())
+ return coroutine.yield()
+end
+function fs.close(fd)
+ uv.fs_close(fd, makeCallback())
+ return coroutine.yield()
+end
+function fs.access(path, flags)
+ uv.fs_access(path, flags or "", makeCallback())
+ return coroutine.yield()
+end
+function fs.rename(path, newPath)
+ uv.fs_rename(path, newPath, makeCallback())
+ return coroutine.yield()
+end
+function fs.rmdir(path)
+ uv.fs_rmdir(path, makeCallback())
+ return coroutine.yield()
+end
+function fs.rmrf(path)
+ local success, err
+ success, err = fs.rmdir(path)
+ if success then return success end
+ if err:match("^ENOTDIR:") then return fs.unlink(path) end
+ if not err:match("^ENOTEMPTY:") then return success, err end
+ for entry in assert(fs.scandir(path)) do
+ local subPath = pathJoin(path, entry.name)
+ if entry.type == "directory" then
+ success, err = fs.rmrf(pathJoin(path, entry.name))
+ else
+ success, err = fs.unlink(subPath)
+ end
+ if not success then return success, err end
+ end
+ return fs.rmdir(path)
+end
+function fs.scandir(path)
+ uv.fs_scandir(path, makeCallback())
+ local req, err = coroutine.yield()
+ if not req then return nil, err end
+ return function ()
+ return uv.fs_scandir_next(req)
+ end
+end
+
+function fs.readFile(path)
+ local fd, stat, data, err
+ fd, err = fs.open(path)
+ if err then return nil, err end
+ stat, err = fs.fstat(fd)
+ if stat then
+ data, err = fs.read(fd, stat.size)
+ end
+ uv.fs_close(fd, noop)
+ return data, err
+end
+
+function fs.writeFile(path, data, mkdir)
+ local fd, success, err
+ fd, err = fs.open(path, "w")
+ if err then
+ if mkdir and string.match(err, "^ENOENT:") then
+ success, err = fs.mkdirp(pathJoin(path, ".."))
+ if success then return fs.writeFile(path, data) end
+ end
+ return nil, err
+ end
+ success, err = fs.write(fd, data)
+ uv.fs_close(fd, noop)
+ return success, err
+end
+
+function fs.mkdirp(path, mode)
+ local success, err = fs.mkdir(path, mode)
+ if success or string.match(err, "^EEXIST") then
+ return true
+ end
+ if string.match(err, "^ENOENT:") then
+ success, err = fs.mkdirp(pathJoin(path, ".."), mode)
+ if not success then return nil, err end
+ return fs.mkdir(path, mode)
+ end
+ return nil, err
+end
+
+function fs.chroot(base)
+ local chroot = {
+ base = base,
+ fstat = fs.fstat,
+ fchmod = fs.fchmod,
+ read = fs.read,
+ write = fs.write,
+ close = fs.close,
+ }
+ local function resolve(path)
+ assert(path, "path missing")
+ return pathJoin(base, pathJoin(path))
+ end
+ function chroot.mkdir(path, mode)
+ return fs.mkdir(resolve(path), mode)
+ end
+ function chroot.mkdirp(path, mode)
+ return fs.mkdirp(resolve(path), mode)
+ end
+ function chroot.open(path, flags, mode)
+ return fs.open(resolve(path), flags, mode)
+ end
+ function chroot.unlink(path)
+ return fs.unlink(resolve(path))
+ end
+ function chroot.stat(path)
+ return fs.stat(resolve(path))
+ end
+ function chroot.lstat(path)
+ return fs.lstat(resolve(path))
+ end
+ function chroot.symlink(target, path)
+ -- TODO: should we resolve absolute target paths or treat it as opaque data?
+ return fs.symlink(target, resolve(path))
+ end
+ function chroot.readlink(path)
+ return fs.readlink(resolve(path))
+ end
+ function chroot.chmod(path, mode)
+ return fs.chmod(resolve(path), mode)
+ end
+ function chroot.access(path, flags)
+ return fs.access(resolve(path), flags)
+ end
+ function chroot.rename(path, newPath)
+ return fs.rename(resolve(path), resolve(newPath))
+ end
+ function chroot.rmdir(path)
+ return fs.rmdir(resolve(path))
+ end
+ function chroot.rmrf(path)
+ return fs.rmrf(resolve(path))
+ end
+ function chroot.scandir(path, iter)
+ return fs.scandir(resolve(path), iter)
+ end
+ function chroot.readFile(path)
+ return fs.readFile(resolve(path))
+ end
+ function chroot.writeFile(path, data, mkdir)
+ return fs.writeFile(resolve(path), data, mkdir)
+ end
+ return chroot
+end
+
+return exports
diff --git a/plugins/coro-http/LICENSE b/plugins/coro-http/LICENSE
new file mode 100644
index 00000000000..5789b767285
--- /dev/null
+++ b/plugins/coro-http/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Tim Caswell
+
+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.
+
diff --git a/plugins/coro-http/README.md b/plugins/coro-http/README.md
new file mode 100644
index 00000000000..271b6698f5d
--- /dev/null
+++ b/plugins/coro-http/README.md
@@ -0,0 +1,2 @@
+# luv-coro-http
+A luv port of lit's coro-http library.
diff --git a/plugins/coro-http/init.lua b/plugins/coro-http/init.lua
new file mode 100644
index 00000000000..49613403eaf
--- /dev/null
+++ b/plugins/coro-http/init.lua
@@ -0,0 +1,187 @@
+local exports = {}
+exports.name = "creationix/coro-http"
+exports.version = "1.2.1-1"
+exports.dependencies = {
+ "creationix/coro-net@1.1.1",
+ "creationix/coro-tls@1.2.1",
+ "creationix/coro-wrapper@1.0.0",
+ "luvit/http-codec@1.0.0"
+}
+exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-http.lua"
+exports.description = "An coro style http(s) client and server helper."
+exports.tags = {"coro", "http"}
+exports.license = "MIT"
+exports.author = { name = "Tim Caswell" }
+
+local httpCodec = require('http-codec')
+local net = require('coro-net')
+local connect = net.connect
+local createServer = net.createServer
+--local tlsWrap = require('coro-tls').wrap
+local wrapper = require('coro-wrapper')
+
+function exports.createServer(options, onConnect)
+ createServer(options, function (rawRead, rawWrite, socket)
+ local read = wrapper.reader(rawRead, httpCodec.decoder())
+ local write = wrapper.writer(rawWrite, httpCodec.encoder())
+ for head in read do
+ local parts = {}
+ for part in read do
+ if #part > 0 then
+ parts[#parts + 1] = part
+ else
+ break
+ end
+ end
+ local body = table.concat(parts)
+ head, body = onConnect(head, body, socket)
+ write(head)
+ if body then write(body) end
+ write("")
+ if not head.keepAlive then break end
+ end
+ end)
+end
+
+local function parseUrl(url)
+ local protocol, host, hostname, port, path = url:match("^(https?:)//(([^/:]+):?([0-9]*))(/?.*)$")
+ if not protocol then error("Not a valid http url: " .. url) end
+ local tls = protocol == "https:"
+ port = port and tonumber(port) or (tls and 443 or 80)
+ if path == "" then path = "/" end
+ return {
+ tls = tls,
+ host = host,
+ hostname = hostname,
+ port = port,
+ path = path
+ }
+end
+exports.parseUrl = parseUrl
+
+local connections = {}
+
+local function getConnection(host, port, tls)
+ for i = #connections, 1, -1 do
+ local connection = connections[i]
+ if connection.host == host and connection.port == port and connection.tls == tls then
+ table.remove(connections, i)
+ -- Make sure the connection is still alive before reusing it.
+ if not connection.socket:is_closing() then
+ connection.reused = true
+ connection.socket:ref()
+ return connection
+ end
+ end
+ end
+ local read, write, socket = assert(connect({host=host,port=port}))
+ --if tls then
+ --read, write = tlsWrap(read, write)
+ --end
+ local httpRead, updateRead = wrapper.reader(read, httpCodec.decoder())
+ return {
+ socket = socket,
+ host = host,
+ port = port,
+ tls = tls,
+ read = httpRead,
+ write = wrapper.writer(write, httpCodec.encoder()),
+ reset = function ()
+ -- This is called after parsing the response head from a HEAD request.
+ -- If you forget, the codec might hang waiting for a body that doesn't exist.
+ updateRead(httpCodec.decoder())
+ end
+ }
+end
+exports.getConnection = getConnection
+
+local function saveConnection(connection)
+ if connection.socket:is_closing() then return end
+ connections[#connections + 1] = connection
+ connection.socket:unref()
+end
+exports.saveConnection = saveConnection
+
+function exports.request(method, url, headers, body)
+ local uri = parseUrl(url)
+ local connection = getConnection(uri.hostname, uri.port, uri.tls)
+ local read = connection.read
+ local write = connection.write
+
+ local req = {
+ method = method,
+ path = uri.path,
+ {"Host", uri.host}
+ }
+ local contentLength
+ local chunked
+ if headers then
+ for i = 1, #headers do
+ local key, value = unpack(headers[i])
+ key = key:lower()
+ if key == "content-length" then
+ contentLength = value
+ elseif key == "content-encoding" and value:lower() == "chunked" then
+ chunked = true
+ end
+ req[#req + 1] = headers[i]
+ end
+ end
+
+ if type(body) == "string" then
+ if not chunked and not contentLength then
+ req[#req + 1] = {"Content-Length", #body}
+ end
+ end
+
+ write(req)
+ if body then write(body) end
+ local res = read()
+ if not res then
+ write()
+ -- If we get an immediate close on a reused socket, try again with a new socket.
+ -- TODO: think about if this could resend requests with side effects and cause
+ -- them to double execute in the remote server.
+ if connection.reused then
+ return exports.request(method, url, headers, body)
+ end
+ error("Connection closed")
+ end
+
+ body = {}
+ if req.method == "HEAD" then
+ connection.reset()
+ else
+ while true do
+ local item = read()
+ if not item then
+ res.keepAlive = false
+ break
+ end
+ if #item == 0 then
+ break
+ end
+ body[#body + 1] = item
+ end
+ end
+
+ if res.keepAlive then
+ saveConnection(connection)
+ else
+ write()
+ end
+
+ -- Follow redirects
+ if method == "GET" and (res.code == 302 or res.code == 307) then
+ for i = 1, #res do
+ local key, location = unpack(res[i])
+ if key:lower() == "location" then
+ return exports.request(method, location, headers)
+ end
+ end
+ end
+
+ return res, table.concat(body)
+end
+
+return exports
diff --git a/plugins/coro-net/LICENSE b/plugins/coro-net/LICENSE
new file mode 100644
index 00000000000..5789b767285
--- /dev/null
+++ b/plugins/coro-net/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Tim Caswell
+
+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.
+
diff --git a/plugins/coro-net/README.md b/plugins/coro-net/README.md
new file mode 100644
index 00000000000..c8a94b9980a
--- /dev/null
+++ b/plugins/coro-net/README.md
@@ -0,0 +1,2 @@
+# luv-coro-net
+A luv port of creationix/coro-net from lit.luvit.io
diff --git a/plugins/coro-net/init.lua b/plugins/coro-net/init.lua
new file mode 100644
index 00000000000..3ae6c2ad824
--- /dev/null
+++ b/plugins/coro-net/init.lua
@@ -0,0 +1,113 @@
+local exports = {}
+exports.name = "creationix/coro-net"
+exports.version = "1.1.1-1"
+exports.dependencies = {
+ "creationix/coro-channel@1.2.0"
+}
+exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-net.lua"
+exports.description = "An coro style client and server helper for tcp and pipes."
+exports.tags = {"coro", "tcp", "pipe", "net"}
+exports.license = "MIT"
+exports.author = { name = "Tim Caswell" }
+
+local uv = require('luv')
+local wrapStream = require('coro-channel').wrapStream
+
+local function makeCallback(timeout)
+ local thread = coroutine.running()
+ local timer, done
+ if timeout then
+ timer = uv.new_timer()
+ timer:start(timeout, 0, function ()
+ if done then return end
+ done = true
+ timer:close()
+ return assert(coroutine.resume(thread, nil, "timeout"))
+ end)
+ end
+ return function (err, data)
+ if done then return end
+ done = true
+ if timer then timer:close() end
+ if err then
+ return assert(coroutine.resume(thread, nil, err))
+ end
+ return assert(coroutine.resume(thread, data or true))
+ end
+end
+exports.makeCallback = makeCallback
+
+local function normalize(options)
+ local t = type(options)
+ if t == "string" then
+ options = {path=options}
+ elseif t == "number" then
+ options = {port=options}
+ elseif t ~= "table" then
+ assert("Net options must be table, string, or number")
+ end
+ if options.port or options.host then
+ return true,
+ options.host or "127.0.0.1",
+ assert(options.port, "options.port is required for tcp connections")
+ elseif options.path then
+ return false, options.path
+ else
+ error("Must set either options.path or options.port")
+ end
+end
+
+function exports.connect(options)
+ local socket, success, err
+ local isTcp, host, port = normalize(options)
+ if isTcp then
+ assert(uv.getaddrinfo(host, port, {
+ socktype = options.socktype or "stream",
+ family = options.family or "inet",
+ }, makeCallback(options.timeout)))
+ local res
+ res, err = coroutine.yield()
+ if not res then return nil, err end
+ socket = uv.new_tcp()
+ socket:connect(res[1].addr, res[1].port, makeCallback(options.timeout))
+ else
+ socket = uv.new_pipe(false)
+ socket:connect(host, makeCallback(options.timeout))
+ end
+ success, err = coroutine.yield()
+ if not success then return nil, err end
+ local read, write = wrapStream(socket)
+ return read, write, socket
+end
+
+function exports.createServer(options, onConnect)
+ local server
+ local isTcp, host, port = normalize(options)
+ if isTcp then
+ server = uv.new_tcp()
+ assert(server:bind(host, port))
+ else
+ server = uv.new_pipe(false)
+ assert(server:bind(host))
+ end
+ assert(server:listen(256, function (err)
+ assert(not err, err)
+ local socket = isTcp and uv.new_tcp() or uv.new_pipe(false)
+ server:accept(socket)
+ coroutine.wrap(function ()
+ local success, failure = xpcall(function ()
+ local read, write = wrapStream(socket)
+ return onConnect(read, write, socket)
+ end, debug.traceback)
+ if not success then
+ print(failure)
+ end
+ if not socket:is_closing() then
+ socket:close()
+ end
+ end)()
+ end))
+ return server
+end
+
+return exports
diff --git a/plugins/coro-tls/LICENSE b/plugins/coro-tls/LICENSE
new file mode 100644
index 00000000000..5789b767285
--- /dev/null
+++ b/plugins/coro-tls/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Tim Caswell
+
+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.
+
diff --git a/plugins/coro-tls/README.md b/plugins/coro-tls/README.md
new file mode 100644
index 00000000000..50aae3f7657
--- /dev/null
+++ b/plugins/coro-tls/README.md
@@ -0,0 +1,2 @@
+# luv-coro-tls
+A luv port of lit's coro-tls module
diff --git a/plugins/coro-tls/init.lua b/plugins/coro-tls/init.lua
new file mode 100644
index 00000000000..03d6e1c3f76
--- /dev/null
+++ b/plugins/coro-tls/init.lua
@@ -0,0 +1,122 @@
+local exports = {}
+exports.name = "creationix/coro-tls"
+exports.version = "1.2.1"
+exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-tls.lua"
+exports.description = "A coro-stream wrapper implementing tls sessions."
+exports.tags = {"coro", "tls", "ssl"}
+exports.license = "MIT"
+exports.author = { name = "Tim Caswell" }
+
+local openssl = require('openssl')
+local bit = require('bit')
+
+local DEFAULT_CIPHERS = 'ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:' .. -- TLS 1.2
+ 'RC4:HIGH:!MD5:!aNULL:!EDH' -- TLS 1.0
+
+-- Given a read/write pair, return a new read/write pair for plaintext
+exports.wrap = function (read, write, options)
+ if not options then
+ options = {}
+ end
+
+ local ctx = openssl.ssl.ctx_new(options.protocol or 'TLSv1_2', options.ciphers or DEFAULT_CIPHERS)
+
+ local key, cert, ca
+ if options.key then
+ key = assert(openssl.pkey.read(options.key, true, 'pem'))
+ end
+ if options.cert then
+ cert = assert(openssl.x509.read(options.cert))
+ end
+ if options.ca then
+ if type(options.ca) == "string" then
+ ca = { assert(openssl.x509.read(options.ca)) }
+ elseif type(options.ca) == "table" then
+ ca = {}
+ for i = 1, #options.ca do
+ ca[i] = assert(openssl.x509.read(options.ca[i]))
+ end
+ else
+ error("options.ca must be string or table of strings")
+ end
+ end
+ if key and cert then
+ assert(ctx:use(key, cert))
+ end
+ if ca then
+ local store = openssl.x509.store:new()
+ for i = 1, #ca do
+ assert(store:add(ca[i]))
+ end
+ ctx:cert_store(store)
+ else
+ ctx:verify_mode({"none"})
+ end
+
+ ctx:options(bit.bor(
+ openssl.ssl.no_sslv2,
+ openssl.ssl.no_sslv3,
+ openssl.ssl.no_compression))
+ local bin, bout = openssl.bio.mem(8192), openssl.bio.mem(8192)
+ local ssl = ctx:ssl(bin, bout, options.server)
+
+ local function flush()
+ while bout:pending() > 0 do
+ write(bout:read())
+ end
+ end
+
+ -- Do handshake
+ while true do
+ if ssl:handshake() then break end
+ flush()
+ local chunk = read()
+ if chunk then
+ bin:write(chunk)
+ else
+ error("disconnect while handshaking")
+ end
+ end
+ flush()
+
+ local done = false
+ local function shutdown()
+ if done then return end
+ done = true
+ while true do
+ if ssl:shutdown() then break end
+ flush()
+ local chunk = read()
+ if chunk then
+ bin:write(chunk)
+ else
+ break
+ end
+ end
+ flush()
+ write()
+ end
+
+ local function plainRead()
+ while true do
+ local chunk = ssl:read()
+ if chunk then return chunk end
+ local cipher = read()
+ if not cipher then return end
+ bin:write(cipher)
+ end
+ end
+
+ local function plainWrite(plain)
+ if not plain then
+ return shutdown()
+ end
+ ssl:write(plain)
+ flush()
+ end
+
+ return plainRead, plainWrite
+
+end
+
+return exports
diff --git a/plugins/coro-wrapper/LICENSE b/plugins/coro-wrapper/LICENSE
new file mode 100644
index 00000000000..5789b767285
--- /dev/null
+++ b/plugins/coro-wrapper/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Tim Caswell
+
+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.
+
diff --git a/plugins/coro-wrapper/README.md b/plugins/coro-wrapper/README.md
new file mode 100644
index 00000000000..a9351a7f531
--- /dev/null
+++ b/plugins/coro-wrapper/README.md
@@ -0,0 +1,2 @@
+# luv-coro-wrapper
+A luv port of lit's coro-wrapper module
diff --git a/plugins/coro-wrapper/init.lua b/plugins/coro-wrapper/init.lua
new file mode 100644
index 00000000000..aab2683baf3
--- /dev/null
+++ b/plugins/coro-wrapper/init.lua
@@ -0,0 +1,41 @@
+local exports = {}
+exports.name = "creationix/coro-wrapper"
+exports.version = "1.0.0-1"
+exports.homepage = "https://github.com/luvit/lit/blob/master/deps/coro-wrapper.lua"
+exports.description = "An adapter for applying decoders to coro-streams."
+exports.tags = {"coro", "decoder", "adapter"}
+exports.license = "MIT"
+exports.author = { name = "Tim Caswell" }
+
+function exports.reader(read, decode)
+ local buffer = ""
+ return function ()
+ while true do
+ local item, extra = decode(buffer)
+ if item then
+ buffer = extra
+ return item
+ end
+ local chunk = read()
+ if not chunk then return end
+ buffer = buffer .. chunk
+ end
+ end,
+ function (newDecode)
+ decode = newDecode
+ end
+end
+
+function exports.writer(write, encode)
+ return function (item)
+ if not item then
+ return write()
+ end
+ return write(encode(item))
+ end,
+ function (newEncode)
+ encode = newEncode
+ end
+end
+
+return exports
diff --git a/plugins/http-codec/LICENSE b/plugins/http-codec/LICENSE
new file mode 100644
index 00000000000..8f71f43fee3
--- /dev/null
+++ b/plugins/http-codec/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/plugins/http-codec/README.md b/plugins/http-codec/README.md
new file mode 100644
index 00000000000..80d050416ae
--- /dev/null
+++ b/plugins/http-codec/README.md
@@ -0,0 +1,2 @@
+# luv-http-codec
+A luv port of luvit's http-codec
diff --git a/plugins/http-codec/init.lua b/plugins/http-codec/init.lua
new file mode 100644
index 00000000000..11a32945729
--- /dev/null
+++ b/plugins/http-codec/init.lua
@@ -0,0 +1,291 @@
+--[[
+
+Copyright 2014-2015 The Luvit Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS-IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+--]]
+
+local exports = {}
+
+exports.name = "luvit/http-codec"
+exports.version = "1.0.0-1"
+exports.homepage = "https://github.com/luvit/luvit/blob/master/deps/http-codec.lua"
+exports.description = "A simple pair of functions for converting between hex and raw strings."
+exports.tags = {"codec", "http"}
+exports.license = "Apache 2"
+exports.author = { name = "Tim Caswell" }
+
+local sub = string.sub
+local gsub = string.gsub
+local lower = string.lower
+local find = string.find
+local format = string.format
+local concat = table.concat
+local match = string.match
+
+local STATUS_CODES = {
+ [100] = 'Continue',
+ [101] = 'Switching Protocols',
+ [102] = 'Processing', -- RFC 2518, obsoleted by RFC 4918
+ [200] = 'OK',
+ [201] = 'Created',
+ [202] = 'Accepted',
+ [203] = 'Non-Authoritative Information',
+ [204] = 'No Content',
+ [205] = 'Reset Content',
+ [206] = 'Partial Content',
+ [207] = 'Multi-Status', -- RFC 4918
+ [300] = 'Multiple Choices',
+ [301] = 'Moved Permanently',
+ [302] = 'Moved Temporarily',
+ [303] = 'See Other',
+ [304] = 'Not Modified',
+ [305] = 'Use Proxy',
+ [307] = 'Temporary Redirect',
+ [400] = 'Bad Request',
+ [401] = 'Unauthorized',
+ [402] = 'Payment Required',
+ [403] = 'Forbidden',
+ [404] = 'Not Found',
+ [405] = 'Method Not Allowed',
+ [406] = 'Not Acceptable',
+ [407] = 'Proxy Authentication Required',
+ [408] = 'Request Time-out',
+ [409] = 'Conflict',
+ [410] = 'Gone',
+ [411] = 'Length Required',
+ [412] = 'Precondition Failed',
+ [413] = 'Request Entity Too Large',
+ [414] = 'Request-URI Too Large',
+ [415] = 'Unsupported Media Type',
+ [416] = 'Requested Range Not Satisfiable',
+ [417] = 'Expectation Failed',
+ [418] = "I'm a teapot", -- RFC 2324
+ [422] = 'Unprocessable Entity', -- RFC 4918
+ [423] = 'Locked', -- RFC 4918
+ [424] = 'Failed Dependency', -- RFC 4918
+ [425] = 'Unordered Collection', -- RFC 4918
+ [426] = 'Upgrade Required', -- RFC 2817
+ [500] = 'Internal Server Error',
+ [501] = 'Not Implemented',
+ [502] = 'Bad Gateway',
+ [503] = 'Service Unavailable',
+ [504] = 'Gateway Time-out',
+ [505] = 'HTTP Version not supported',
+ [506] = 'Variant Also Negotiates', -- RFC 2295
+ [507] = 'Insufficient Storage', -- RFC 4918
+ [509] = 'Bandwidth Limit Exceeded',
+ [510] = 'Not Extended' -- RFC 2774
+}
+
+exports.encoder = function ()
+
+ local mode
+ local encodeHead, encodeRaw, encodeChunked
+
+ function encodeHead(item)
+ if not item or item == "" then
+ return item
+ elseif not (type(item) == "table") then
+ error("expected a table but got a " .. type(item) .. " when encoding data")
+ end
+ local head, chunkedEncoding
+ local version = item.version or 1.1
+ if item.method then
+ local path = item.path
+ assert(path and #path > 0, "expected non-empty path")
+ head = { item.method .. ' ' .. item.path .. ' HTTP/' .. version .. '\r\n' }
+ else
+ local reason = item.reason or STATUS_CODES[item.code]
+ head = { 'HTTP/' .. version .. ' ' .. item.code .. ' ' .. reason .. '\r\n' }
+ end
+ for i = 1, #item do
+ local key, value = unpack(item[i])
+ local lowerKey = lower(key)
+ if lowerKey == "transfer-encoding" then
+ chunkedEncoding = lower(value) == "chunked"
+ end
+ value = gsub(tostring(value), "[\r\n]+", " ")
+ head[#head + 1] = key .. ': ' .. tostring(value) .. '\r\n'
+ end
+ head[#head + 1] = '\r\n'
+
+ mode = chunkedEncoding and encodeChunked or encodeRaw
+ return concat(head)
+ end
+
+ function encodeRaw(item)
+ if type(item) ~= "string" then
+ mode = encodeHead
+ return encodeHead(item)
+ end
+ return item
+ end
+
+ function encodeChunked(item)
+ if type(item) ~= "string" then
+ mode = encodeHead
+ local extra = encodeHead(item)
+ if extra then
+ return "0\r\n\r\n" .. extra
+ else
+ return "0\r\n\r\n"
+ end
+ end
+ if #item == 0 then
+ mode = encodeHead
+ end
+ return format("%x", #item) .. "\r\n" .. item .. "\r\n"
+ end
+
+ mode = encodeHead
+ return function (item)
+ return mode(item)
+ end
+end
+
+exports.decoder = function ()
+
+ -- This decoder is somewhat stateful with 5 different parsing states.
+ local decodeHead, decodeEmpty, decodeRaw, decodeChunked, decodeCounted
+ local mode -- state variable that points to various decoders
+ local bytesLeft -- For counted decoder
+
+ -- This state is for decoding the status line and headers.
+ function decodeHead(chunk)
+ if not chunk then return end
+
+ local _, length = find(chunk, "\r?\n\r?\n", 1)
+ -- First make sure we have all the head before continuing
+ if not length then
+ if #chunk < 8 * 1024 then return end
+ -- But protect against evil clients by refusing heads over 8K long.
+ error("entity too large")
+ end
+
+ -- Parse the status/request line
+ local head = {}
+ local _, offset
+ local version
+ _, offset, version, head.code, head.reason =
+ find(chunk, "^HTTP/(%d%.%d) (%d+) ([^\r\n]+)\r?\n")
+ if offset then
+ head.code = tonumber(head.code)
+ else
+ _, offset, head.method, head.path, version =
+ find(chunk, "^(%u+) ([^ ]+) HTTP/(%d%.%d)\r?\n")
+ if not offset then
+ error("expected HTTP data")
+ end
+ end
+ version = tonumber(version)
+ head.version = version
+ head.keepAlive = version > 1.0
+
+ -- We need to inspect some headers to know how to parse the body.
+ local contentLength
+ local chunkedEncoding
+
+ -- Parse the header lines
+ while true do
+ local key, value
+ _, offset, key, value = find(chunk, "^([^:\r\n]+): *([^\r\n]+)\r?\n", offset + 1)
+ if not offset then break end
+ local lowerKey = lower(key)
+
+ -- Inspect a few headers and remember the values
+ if lowerKey == "content-length" then
+ contentLength = tonumber(value)
+ elseif lowerKey == "transfer-encoding" then
+ chunkedEncoding = lower(value) == "chunked"
+ elseif lowerKey == "connection" then
+ head.keepAlive = lower(value) == "keep-alive"
+ end
+ head[#head + 1] = {key, value}
+ end
+
+ if head.keepAlive and (not (chunkedEncoding or (contentLength and contentLength > 0)))
+ or (head.method == "GET" or head.method == "HEAD") then
+ mode = decodeEmpty
+ elseif chunkedEncoding then
+ mode = decodeChunked
+ elseif contentLength then
+ bytesLeft = contentLength
+ mode = decodeCounted
+ elseif not head.keepAlive then
+ mode = decodeRaw
+ end
+
+ return head, sub(chunk, length + 1)
+
+ end
+
+ -- This is used for inserting a single empty string into the output string for known empty bodies
+ function decodeEmpty(chunk)
+ mode = decodeHead
+ return "", chunk or ""
+ end
+
+ function decodeRaw(chunk)
+ if not chunk then return "", "" end
+ if #chunk == 0 then return end
+ return chunk, ""
+ end
+
+ function decodeChunked(chunk)
+ local len, term
+ len, term = match(chunk, "^(%x+)(..)")
+ if not len then return end
+ assert(term == "\r\n")
+ local length = tonumber(len, 16)
+ if #chunk < length + 4 + #len then return end
+ if length == 0 then
+ mode = decodeHead
+ end
+ chunk = sub(chunk, #len + 3)
+ assert(sub(chunk, length + 1, length + 2) == "\r\n")
+ return sub(chunk, 1, length), sub(chunk, length + 3)
+ end
+
+ function decodeCounted(chunk)
+ if bytesLeft == 0 then
+ mode = decodeEmpty
+ return mode(chunk)
+ end
+ local length = #chunk
+ -- Make sure we have at least one byte to process
+ if length == 0 then return end
+
+ if length >= bytesLeft then
+ mode = decodeEmpty
+ end
+
+ -- If the entire chunk fits, pass it all through
+ if length <= bytesLeft then
+ bytesLeft = bytesLeft - length
+ return chunk, ""
+ end
+
+ return sub(chunk, 1, bytesLeft), sub(chunk, bytesLeft + 1)
+ end
+
+ -- Switch between states by changing which decoder mode points to
+ mode = decodeHead
+ return function (chunk)
+ return mode(chunk)
+ end
+
+end
+
+return exports
diff --git a/plugins/json/LICENSE b/plugins/json/LICENSE
new file mode 100644
index 00000000000..5789b767285
--- /dev/null
+++ b/plugins/json/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Tim Caswell
+
+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.
+
diff --git a/plugins/json/README.md b/plugins/json/README.md
new file mode 100644
index 00000000000..71965e3d0f7
--- /dev/null
+++ b/plugins/json/README.md
@@ -0,0 +1,2 @@
+# luv-json
+A luv port of luvit's json module
diff --git a/plugins/json/init.lua b/plugins/json/init.lua
new file mode 100644
index 00000000000..d49352cc6ff
--- /dev/null
+++ b/plugins/json/init.lua
@@ -0,0 +1,732 @@
+local exports = {}
+exports.name = "luvit/json"
+exports.version = "2.5.0"
+exports.homepage = "http://dkolf.de/src/dkjson-lua.fsl"
+exports.description = "David Kolf's JSON library repackaged for lit."
+exports.tags = {"json", "codec"}
+exports.license = "MIT"
+exports.author = {
+ name = "David Kolf",
+ homepage = "http://dkolf.de/",
+}
+exports.contributors = {
+ "Tim Caswell",
+}
+
+-- Module options:
+local always_try_using_lpeg = false
+local register_global_module_table = false
+local global_module_name = 'json'
+
+--[==[
+
+David Kolf's JSON module for Lua 5.1/5.2
+
+Version 2.5
+
+
+For the documentation see the corresponding readme.txt or visit
+<http://dkolf.de/src/dkjson-lua.fsl/>.
+
+You can contact the author by sending an e-mail to 'david' at the
+domain 'dkolf.de'.
+
+
+Copyright (C) 2010-2013 David Heiko Kolf
+
+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.
+
+--]==]
+
+-- global dependencies:
+local pairs, type, tostring, tonumber, getmetatable, setmetatable =
+ pairs, type, tostring, tonumber, getmetatable, setmetatable
+local error, require, pcall, select = error, require, pcall, select
+local floor, huge = math.floor, math.huge
+local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
+ string.rep, string.gsub, string.sub, string.byte, string.char,
+ string.find, string.len, string.format
+local strmatch = string.match
+local concat = table.concat
+
+local json = exports
+json.original_version = "dkjson 2.5"
+
+if register_global_module_table then
+ _G[global_module_name] = json
+end
+
+_ENV = nil -- blocking globals in Lua 5.2
+
+pcall (function()
+ -- Enable access to blocked metatables.
+ -- Don't worry, this module doesn't change anything in them.
+ local debmeta = require "debug".getmetatable
+ if debmeta then getmetatable = debmeta end
+end)
+
+json.null = setmetatable ({}, {
+ __tojson = function () return "null" end
+})
+
+local function isarray (tbl)
+ local max, n, arraylen = 0, 0, 0
+ for k,v in pairs (tbl) do
+ if k == 'n' and type(v) == 'number' then
+ arraylen = v
+ if v > max then
+ max = v
+ end
+ else
+ if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
+ return false
+ end
+ if k > max then
+ max = k
+ end
+ n = n + 1
+ end
+ end
+ if max > 10 and max > arraylen and max > n * 2 then
+ return false -- don't create an array with too many holes
+ end
+ return true, max
+end
+
+local escapecodes = {
+ ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
+ ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
+}
+
+local function escapeutf8 (uchar)
+ local value = escapecodes[uchar]
+ if value then
+ return value
+ end
+ local a, b, c, d = strbyte (uchar, 1, 4)
+ a, b, c, d = a or 0, b or 0, c or 0, d or 0
+ if a <= 0x7f then
+ value = a
+ elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
+ value = (a - 0xc0) * 0x40 + b - 0x80
+ elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
+ value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
+ elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
+ value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
+ else
+ return ""
+ end
+ if value <= 0xffff then
+ return strformat ("\\u%.4x", value)
+ elseif value <= 0x10ffff then
+ -- encode as UTF-16 surrogate pair
+ value = value - 0x10000
+ local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
+ return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
+ else
+ return ""
+ end
+end
+
+local function fsub (str, pattern, repl)
+ -- gsub always builds a new string in a buffer, even when no match
+ -- exists. First using find should be more efficient when most strings
+ -- don't contain the pattern.
+ if strfind (str, pattern) then
+ return gsub (str, pattern, repl)
+ else
+ return str
+ end
+end
+
+local function quotestring (value)
+ -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
+ value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
+ if strfind (value, "[\194\216\220\225\226\239]") then
+ value = fsub (value, "\194[\128-\159\173]", escapeutf8)
+ value = fsub (value, "\216[\128-\132]", escapeutf8)
+ value = fsub (value, "\220\143", escapeutf8)
+ value = fsub (value, "\225\158[\180\181]", escapeutf8)
+ value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
+ value = fsub (value, "\226\129[\160-\175]", escapeutf8)
+ value = fsub (value, "\239\187\191", escapeutf8)
+ value = fsub (value, "\239\191[\176-\191]", escapeutf8)
+ end
+ return "\"" .. value .. "\""
+end
+json.quotestring = quotestring
+
+local function replace(str, o, n)
+ local i, j = strfind (str, o, 1, true)
+ if i then
+ return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
+ else
+ return str
+ end
+end
+
+-- locale independent num2str and str2num functions
+local decpoint, numfilter
+
+local function updatedecpoint ()
+ decpoint = strmatch(tostring(0.5), "([^05+])")
+ -- build a filter that can be used to remove group separators
+ numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
+end
+
+updatedecpoint()
+
+local function num2str (num)
+ return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
+end
+
+local function str2num (str)
+ local num = tonumber(replace(str, ".", decpoint))
+ if not num then
+ updatedecpoint()
+ num = tonumber(replace(str, ".", decpoint))
+ end
+ return num
+end
+
+local function addnewline2 (level, buffer, buflen)
+ buffer[buflen+1] = "\n"
+ buffer[buflen+2] = strrep (" ", level)
+ buflen = buflen + 2
+ return buflen
+end
+
+function json.addnewline (state)
+ if state.indent then
+ state.bufferlen = addnewline2 (state.level or 0,
+ state.buffer, state.bufferlen or #(state.buffer))
+ end
+end
+
+local encode2 -- forward declaration
+
+local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
+ local kt = type (key)
+ if kt ~= 'string' and kt ~= 'number' then
+ return nil, "type '" .. kt .. "' is not supported as a key by JSON."
+ end
+ if prev then
+ buflen = buflen + 1
+ buffer[buflen] = ","
+ end
+ if indent then
+ buflen = addnewline2 (level, buffer, buflen)
+ end
+ buffer[buflen+1] = quotestring (key)
+ buffer[buflen+2] = ":"
+ return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
+end
+
+local function appendcustom(res, buffer, state)
+ local buflen = state.bufferlen
+ if type (res) == 'string' then
+ buflen = buflen + 1
+ buffer[buflen] = res
+ end
+ return buflen
+end
+
+local function exception(reason, value, state, buffer, buflen, defaultmessage)
+ defaultmessage = defaultmessage or reason
+ local handler = state.exception
+ if not handler then
+ return nil, defaultmessage
+ else
+ state.bufferlen = buflen
+ local ret, msg = handler (reason, value, state, defaultmessage)
+ if not ret then return nil, msg or defaultmessage end
+ return appendcustom(ret, buffer, state)
+ end
+end
+
+function json.encodeexception(reason, value, state, defaultmessage)
+ return quotestring("<" .. defaultmessage .. ">")
+end
+
+encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
+ local valtype = type (value)
+ local valmeta = getmetatable (value)
+ valmeta = type (valmeta) == 'table' and valmeta -- only tables
+ local valtojson = valmeta and valmeta.__tojson
+ if valtojson then
+ if tables[value] then
+ return exception('reference cycle', value, state, buffer, buflen)
+ end
+ tables[value] = true
+ state.bufferlen = buflen
+ local ret, msg = valtojson (value, state)
+ if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
+ tables[value] = nil
+ buflen = appendcustom(ret, buffer, state)
+ elseif value == nil then
+ buflen = buflen + 1
+ buffer[buflen] = "null"
+ elseif valtype == 'number' then
+ local s
+ if value ~= value or value >= huge or -value >= huge then
+ -- This is the behaviour of the original JSON implementation.
+ s = "null"
+ else
+ s = num2str (value)
+ end
+ buflen = buflen + 1
+ buffer[buflen] = s
+ elseif valtype == 'boolean' then
+ buflen = buflen + 1
+ buffer[buflen] = value and "true" or "false"
+ elseif valtype == 'string' then
+ buflen = buflen + 1
+ buffer[buflen] = quotestring (value)
+ elseif valtype == 'table' then
+ if tables[value] then
+ return exception('reference cycle', value, state, buffer, buflen)
+ end
+ tables[value] = true
+ level = level + 1
+ local isa, n = isarray (value)
+ if n == 0 and valmeta and valmeta.__jsontype == 'object' then
+ isa = false
+ end
+ local msg
+ if isa then -- JSON array
+ buflen = buflen + 1
+ buffer[buflen] = "["
+ for i = 1, n do
+ buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
+ if not buflen then return nil, msg end
+ if i < n then
+ buflen = buflen + 1
+ buffer[buflen] = ","
+ end
+ end
+ buflen = buflen + 1
+ buffer[buflen] = "]"
+ else -- JSON object
+ local prev = false
+ buflen = buflen + 1
+ buffer[buflen] = "{"
+ local order = valmeta and valmeta.__jsonorder or globalorder
+ if order then
+ local used = {}
+ n = #order
+ for i = 1, n do
+ local k = order[i]
+ local v = value[k]
+ local _
+ if v then
+ used[k] = true
+ buflen, _ = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
+ prev = true -- add a seperator before the next element
+ end
+ end
+ for k,v in pairs (value) do
+ if not used[k] then
+ buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
+ if not buflen then return nil, msg end
+ prev = true -- add a seperator before the next element
+ end
+ end
+ else -- unordered
+ for k,v in pairs (value) do
+ buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
+ if not buflen then return nil, msg end
+ prev = true -- add a seperator before the next element
+ end
+ end
+ if indent then
+ buflen = addnewline2 (level - 1, buffer, buflen)
+ end
+ buflen = buflen + 1
+ buffer[buflen] = "}"
+ end
+ tables[value] = nil
+ else
+ return exception ('unsupported type', value, state, buffer, buflen,
+ "type '" .. valtype .. "' is not supported by JSON.")
+ end
+ return buflen
+end
+
+function json.encode (value, state)
+ state = state or {}
+ local oldbuffer = state.buffer
+ local buffer = oldbuffer or {}
+ state.buffer = buffer
+ updatedecpoint()
+ local ret, msg = encode2 (value, state.indent, state.level or 0,
+ buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
+ if not ret then
+ error (msg, 2)
+ elseif oldbuffer == buffer then
+ state.bufferlen = ret
+ return true
+ else
+ state.bufferlen = nil
+ state.buffer = nil
+ return concat (buffer)
+ end
+end
+
+local function loc (str, where)
+ local line, pos, linepos = 1, 1, 0
+ while true do
+ pos = strfind (str, "\n", pos, true)
+ if pos and pos < where then
+ line = line + 1
+ linepos = pos
+ pos = pos + 1
+ else
+ break
+ end
+ end
+ return "line " .. line .. ", column " .. (where - linepos)
+end
+
+local function unterminated (str, what, where)
+ return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
+end
+
+local function scanwhite (str, pos)
+ while true do
+ pos = strfind (str, "%S", pos)
+ if not pos then return nil end
+ local sub2 = strsub (str, pos, pos + 1)
+ if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
+ -- UTF-8 Byte Order Mark
+ pos = pos + 3
+ elseif sub2 == "//" then
+ pos = strfind (str, "[\n\r]", pos + 2)
+ if not pos then return nil end
+ elseif sub2 == "/*" then
+ pos = strfind (str, "*/", pos + 2)
+ if not pos then return nil end
+ pos = pos + 2
+ else
+ return pos
+ end
+ end
+end
+
+local escapechars = {
+ ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
+ ["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
+}
+
+local function unichar (value)
+ if value < 0 then
+ return nil
+ elseif value <= 0x007f then
+ return strchar (value)
+ elseif value <= 0x07ff then
+ return strchar (0xc0 + floor(value/0x40),
+ 0x80 + (floor(value) % 0x40))
+ elseif value <= 0xffff then
+ return strchar (0xe0 + floor(value/0x1000),
+ 0x80 + (floor(value/0x40) % 0x40),
+ 0x80 + (floor(value) % 0x40))
+ elseif value <= 0x10ffff then
+ return strchar (0xf0 + floor(value/0x40000),
+ 0x80 + (floor(value/0x1000) % 0x40),
+ 0x80 + (floor(value/0x40) % 0x40),
+ 0x80 + (floor(value) % 0x40))
+ else
+ return nil
+ end
+end
+
+local function scanstring (str, pos)
+ local lastpos = pos + 1
+ local buffer, n = {}, 0
+ while true do
+ local nextpos = strfind (str, "[\"\\]", lastpos)
+ if not nextpos then
+ return unterminated (str, "string", pos)
+ end
+ if nextpos > lastpos then
+ n = n + 1
+ buffer[n] = strsub (str, lastpos, nextpos - 1)
+ end
+ if strsub (str, nextpos, nextpos) == "\"" then
+ lastpos = nextpos + 1
+ break
+ else
+ local escchar = strsub (str, nextpos + 1, nextpos + 1)
+ local value
+ if escchar == "u" then
+ value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
+ if value then
+ local value2
+ if 0xD800 <= value and value <= 0xDBff then
+ -- we have the high surrogate of UTF-16. Check if there is a
+ -- low surrogate escaped nearby to combine them.
+ if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
+ value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
+ if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
+ value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
+ else
+ value2 = nil -- in case it was out of range for a low surrogate
+ end
+ end
+ end
+ value = value and unichar (value)
+ if value then
+ if value2 then
+ lastpos = nextpos + 12
+ else
+ lastpos = nextpos + 6
+ end
+ end
+ end
+ end
+ if not value then
+ value = escapechars[escchar] or escchar
+ lastpos = nextpos + 2
+ end
+ n = n + 1
+ buffer[n] = value
+ end
+ end
+ if n == 1 then
+ return buffer[1], lastpos
+ elseif n > 1 then
+ return concat (buffer), lastpos
+ else
+ return "", lastpos
+ end
+end
+
+local scanvalue -- forward declaration
+
+local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
+ local tbl, n = {}, 0
+ local pos = startpos + 1
+ if what == 'object' then
+ setmetatable (tbl, objectmeta)
+ else
+ setmetatable (tbl, arraymeta)
+ end
+ while true do
+ pos = scanwhite (str, pos)
+ if not pos then return unterminated (str, what, startpos) end
+ local char = strsub (str, pos, pos)
+ if char == closechar then
+ return tbl, pos + 1
+ end
+ local val1, err
+ val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
+ if err then return nil, pos, err end
+ pos = scanwhite (str, pos)
+ if not pos then return unterminated (str, what, startpos) end
+ char = strsub (str, pos, pos)
+ if char == ":" then
+ if val1 == nil then
+ return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
+ end
+ pos = scanwhite (str, pos + 1)
+ if not pos then return unterminated (str, what, startpos) end
+ local val2
+ val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
+ if err then return nil, pos, err end
+ tbl[val1] = val2
+ pos = scanwhite (str, pos)
+ if not pos then return unterminated (str, what, startpos) end
+ char = strsub (str, pos, pos)
+ else
+ n = n + 1
+ tbl[n] = val1
+ end
+ if char == "," then
+ pos = pos + 1
+ end
+ end
+end
+
+scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
+ pos = pos or 1
+ pos = scanwhite (str, pos)
+ if not pos then
+ return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
+ end
+ local char = strsub (str, pos, pos)
+ if char == "{" then
+ return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
+ elseif char == "[" then
+ return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
+ elseif char == "\"" then
+ return scanstring (str, pos)
+ else
+ local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
+ if pstart then
+ local number = str2num (strsub (str, pstart, pend))
+ if number then
+ return number, pend + 1
+ end
+ end
+ pstart, pend = strfind (str, "^%a%w*", pos)
+ if pstart then
+ local name = strsub (str, pstart, pend)
+ if name == "true" then
+ return true, pend + 1
+ elseif name == "false" then
+ return false, pend + 1
+ elseif name == "null" then
+ return nullval, pend + 1
+ end
+ end
+ return nil, pos, "no valid JSON value at " .. loc (str, pos)
+ end
+end
+
+local function optionalmetatables(...)
+ if select("#", ...) > 0 then
+ return ...
+ else
+ return {__jsontype = 'object'}, {__jsontype = 'array'}
+ end
+end
+
+function json.decode (str, pos, nullval, ...)
+ local objectmeta, arraymeta = optionalmetatables(...)
+ return scanvalue (str, pos, nullval, objectmeta, arraymeta)
+end
+
+function json.use_lpeg ()
+ local g = require ("lpeg")
+
+ if g.version() == "0.11" then
+ error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
+ end
+
+ local pegmatch = g.match
+ local P, S, R = g.P, g.S, g.R
+
+ local function ErrorCall (str, pos, msg, state)
+ if not state.msg then
+ state.msg = msg .. " at " .. loc (str, pos)
+ state.pos = pos
+ end
+ return false
+ end
+
+ local function Err (msg)
+ return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
+ end
+
+ local SingleLineComment = P"//" * (1 - S"\n\r")^0
+ local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
+ local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
+
+ local PlainChar = 1 - S"\"\\\n\r"
+ local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
+ local HexDigit = R("09", "af", "AF")
+ local function UTF16Surrogate (match, pos, high, low)
+ high, low = tonumber (high, 16), tonumber (low, 16)
+ if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
+ return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
+ else
+ return false
+ end
+ end
+ local function UTF16BMP (hex)
+ return unichar (tonumber (hex, 16))
+ end
+ local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
+ local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
+ local Char = UnicodeEscape + EscapeSequence + PlainChar
+ local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string")
+ local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
+ local Fractal = P"." * R"09"^0
+ local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
+ local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
+ local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
+ local SimpleValue = Number + String + Constant
+ local ArrayContent, ObjectContent
+
+ -- The functions parsearray and parseobject parse only a single value/pair
+ -- at a time and store them directly to avoid hitting the LPeg limits.
+ local function parsearray (str, pos, nullval, state)
+ local obj, cont
+ local npos
+ local t, nt = {}, 0
+ repeat
+ obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
+ if not npos then break end
+ pos = npos
+ nt = nt + 1
+ t[nt] = obj
+ until cont == 'last'
+ return pos, setmetatable (t, state.arraymeta)
+ end
+
+ local function parseobject (str, pos, nullval, state)
+ local obj, key, cont
+ local npos
+ local t = {}
+ repeat
+ key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
+ if not npos then break end
+ pos = npos
+ t[key] = obj
+ until cont == 'last'
+ return pos, setmetatable (t, state.objectmeta)
+ end
+
+ local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
+ local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
+ local Value = Space * (Array + Object + SimpleValue)
+ local ExpectedValue = Value + Space * Err "value expected"
+ ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
+ local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
+ ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
+ local DecodeValue = ExpectedValue * g.Cp ()
+
+ function json.decode (str, pos, nullval, ...)
+ local state = {}
+ state.objectmeta, state.arraymeta = optionalmetatables(...)
+ local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
+ if state.msg then
+ return nil, state.pos, state.msg
+ else
+ return obj, retpos
+ end
+ end
+
+ -- use this function only once:
+ json.use_lpeg = function () return json end
+
+ json.using_lpeg = true
+
+ return json -- so you can get the module using json = require "dkjson".use_lpeg()
+end
+
+if always_try_using_lpeg then
+ pcall (json.use_lpeg)
+end
+
+json.parse = json.decode
+json.stringify = json.encode
+
+return exports
diff --git a/plugins/mime/LICENSE b/plugins/mime/LICENSE
new file mode 100644
index 00000000000..5789b767285
--- /dev/null
+++ b/plugins/mime/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Tim Caswell
+
+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.
+
diff --git a/plugins/mime/README.md b/plugins/mime/README.md
new file mode 100644
index 00000000000..550ba582b69
--- /dev/null
+++ b/plugins/mime/README.md
@@ -0,0 +1,2 @@
+# luv-mime
+A luv port of weblit's mime module
diff --git a/plugins/mime/init.lua b/plugins/mime/init.lua
new file mode 100644
index 00000000000..ac41dad1cfc
--- /dev/null
+++ b/plugins/mime/init.lua
@@ -0,0 +1,194 @@
+local exports = {}
+exports.name = "creationix/mime"
+exports.version = "0.1.2-1"
+exports.description = "A simple mime type database useful for serving static files over http."
+exports.tags = {"mime", "static"}
+exports.license = "MIT"
+exports.author = { name = "Tim Caswell" }
+exports.homepage = "https://github.com/creationix/weblit/blob/master/libs/mime.lua"
+
+local mime = exports
+local table = {
+ ["3gp"] = "video/3gpp",
+ a = "application/octet-stream",
+ ai = "application/postscript",
+ aif = "audio/x-aiff",
+ aiff = "audio/x-aiff",
+ asc = "application/pgp-signature",
+ asf = "video/x-ms-asf",
+ asm = "text/x-asm",
+ asx = "video/x-ms-asf",
+ atom = "application/atom+xml",
+ au = "audio/basic",
+ avi = "video/x-msvideo",
+ bat = "application/x-msdownload",
+ bin = "application/octet-stream",
+ bmp = "image/bmp",
+ bz2 = "application/x-bzip2",
+ c = "text/x-c",
+ cab = "application/vnd.ms-cab-compressed",
+ cc = "text/x-c",
+ chm = "application/vnd.ms-htmlhelp",
+ class = "application/octet-stream",
+ com = "application/x-msdownload",
+ conf = "text/plain",
+ cpp = "text/x-c",
+ crt = "application/x-x509-ca-cert",
+ css = "text/css",
+ csv = "text/csv",
+ cxx = "text/x-c",
+ deb = "application/x-debian-package",
+ der = "application/x-x509-ca-cert",
+ diff = "text/x-diff",
+ djv = "image/vnd.djvu",
+ djvu = "image/vnd.djvu",
+ dll = "application/x-msdownload",
+ dmg = "application/octet-stream",
+ doc = "application/msword",
+ dot = "application/msword",
+ dtd = "application/xml-dtd",
+ dvi = "application/x-dvi",
+ ear = "application/java-archive",
+ eml = "message/rfc822",
+ eps = "application/postscript",
+ exe = "application/x-msdownload",
+ f = "text/x-fortran",
+ f77 = "text/x-fortran",
+ f90 = "text/x-fortran",
+ flv = "video/x-flv",
+ ["for"] = "text/x-fortran",
+ gem = "application/octet-stream",
+ gemspec = "text/x-script.ruby",
+ gif = "image/gif",
+ gz = "application/x-gzip",
+ h = "text/x-c",
+ hh = "text/x-c",
+ htm = "text/html",
+ html = "text/html",
+ ico = "image/vnd.microsoft.icon",
+ ics = "text/calendar",
+ ifb = "text/calendar",
+ iso = "application/octet-stream",
+ jar = "application/java-archive",
+ java = "text/x-java-source",
+ jnlp = "application/x-java-jnlp-file",
+ jpeg = "image/jpeg",
+ jpg = "image/jpeg",
+ js = "application/javascript",
+ json = "application/json",
+ less = "text/css",
+ log = "text/plain",
+ lua = "text/x-lua",
+ luac = "application/x-lua-bytecode",
+ m3u = "audio/x-mpegurl",
+ m4v = "video/mp4",
+ man = "text/troff",
+ manifest = "text/cache-manifest",
+ markdown = "text/markdown",
+ mathml = "application/mathml+xml",
+ mbox = "application/mbox",
+ mdoc = "text/troff",
+ md = "text/markdown",
+ me = "text/troff",
+ mid = "audio/midi",
+ midi = "audio/midi",
+ mime = "message/rfc822",
+ mml = "application/mathml+xml",
+ mng = "video/x-mng",
+ mov = "video/quicktime",
+ mp3 = "audio/mpeg",
+ mp4 = "video/mp4",
+ mp4v = "video/mp4",
+ mpeg = "video/mpeg",
+ mpg = "video/mpeg",
+ ms = "text/troff",
+ msi = "application/x-msdownload",
+ odp = "application/vnd.oasis.opendocument.presentation",
+ ods = "application/vnd.oasis.opendocument.spreadsheet",
+ odt = "application/vnd.oasis.opendocument.text",
+ ogg = "application/ogg",
+ p = "text/x-pascal",
+ pas = "text/x-pascal",
+ pbm = "image/x-portable-bitmap",
+ pdf = "application/pdf",
+ pem = "application/x-x509-ca-cert",
+ pgm = "image/x-portable-graymap",
+ pgp = "application/pgp-encrypted",
+ pkg = "application/octet-stream",
+ pl = "text/x-script.perl",
+ pm = "text/x-script.perl-module",
+ png = "image/png",
+ pnm = "image/x-portable-anymap",
+ ppm = "image/x-portable-pixmap",
+ pps = "application/vnd.ms-powerpoint",
+ ppt = "application/vnd.ms-powerpoint",
+ ps = "application/postscript",
+ psd = "image/vnd.adobe.photoshop",
+ py = "text/x-script.python",
+ qt = "video/quicktime",
+ ra = "audio/x-pn-realaudio",
+ rake = "text/x-script.ruby",
+ ram = "audio/x-pn-realaudio",
+ rar = "application/x-rar-compressed",
+ rb = "text/x-script.ruby",
+ rdf = "application/rdf+xml",
+ roff = "text/troff",
+ rpm = "application/x-redhat-package-manager",
+ rss = "application/rss+xml",
+ rtf = "application/rtf",
+ ru = "text/x-script.ruby",
+ s = "text/x-asm",
+ sgm = "text/sgml",
+ sgml = "text/sgml",
+ sh = "application/x-sh",
+ sig = "application/pgp-signature",
+ snd = "audio/basic",
+ so = "application/octet-stream",
+ svg = "image/svg+xml",
+ svgz = "image/svg+xml",
+ swf = "application/x-shockwave-flash",
+ t = "text/troff",
+ tar = "application/x-tar",
+ tbz = "application/x-bzip-compressed-tar",
+ tci = "application/x-topcloud",
+ tcl = "application/x-tcl",
+ tex = "application/x-tex",
+ texi = "application/x-texinfo",
+ texinfo = "application/x-texinfo",
+ text = "text/plain",
+ tif = "image/tiff",
+ tiff = "image/tiff",
+ torrent = "application/x-bittorrent",
+ tr = "text/troff",
+ ttf = "application/x-font-ttf",
+ txt = "text/plain",
+ vcf = "text/x-vcard",
+ vcs = "text/x-vcalendar",
+ vrml = "model/vrml",
+ war = "application/java-archive",
+ wav = "audio/x-wav",
+ webm = "video/webm",
+ wma = "audio/x-ms-wma",
+ wmv = "video/x-ms-wmv",
+ wmx = "video/x-ms-wmx",
+ wrl = "model/vrml",
+ wsdl = "application/wsdl+xml",
+ xbm = "image/x-xbitmap",
+ xhtml = "application/xhtml+xml",
+ xls = "application/vnd.ms-excel",
+ xml = "application/xml",
+ xpm = "image/x-xpixmap",
+ xsl = "application/xml",
+ xslt = "application/xslt+xml",
+ yaml = "text/yaml",
+ yml = "text/yaml",
+ zip = "application/zip",
+}
+mime.table = table
+mime.default = "application/octet-stream"
+
+function mime.getType(path)
+ return mime.table[path:lower():match("[^.]*$")] or mime.default
+end
+
+return mime
diff --git a/plugins/path/LICENSE b/plugins/path/LICENSE
new file mode 100644
index 00000000000..8f71f43fee3
--- /dev/null
+++ b/plugins/path/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/plugins/path/README.md b/plugins/path/README.md
new file mode 100644
index 00000000000..75d881e35e3
--- /dev/null
+++ b/plugins/path/README.md
@@ -0,0 +1,2 @@
+# luv-path
+A luv port of luvi's path module
diff --git a/plugins/path/init.lua b/plugins/path/init.lua
new file mode 100644
index 00000000000..d0e427d02ee
--- /dev/null
+++ b/plugins/path/init.lua
@@ -0,0 +1,139 @@
+--[[
+
+Copyright 2014 The Luvit Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS-IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+--]]
+
+-- Extracted from https://github.com/luvit/luvi/blob/master/src/lua/init.lua
+
+local isWindows
+if jit and jit.os then
+ -- Luajit provides explicit platform detection
+ isWindows = jit.os == "Windows"
+else
+ -- Normal lua will only have \ for path separator on windows.
+ isWindows = package.config:find("\\") and true or false
+end
+
+local uv = require('luv')
+
+local getPrefix, splitPath, joinParts
+
+if isWindows then
+ -- Windows aware path utilities
+ function getPrefix(path)
+ return path:match("^%a:\\") or
+ path:match("^/") or
+ path:match("^\\+")
+ end
+ function splitPath(path)
+ local parts = {}
+ for part in string.gmatch(path, '([^/\\]+)') do
+ table.insert(parts, part)
+ end
+ return parts
+ end
+ function joinParts(prefix, parts, i, j)
+ if not prefix then
+ return table.concat(parts, '/', i, j)
+ elseif prefix ~= '/' then
+ return prefix .. table.concat(parts, '\\', i, j)
+ else
+ return prefix .. table.concat(parts, '/', i, j)
+ end
+ end
+else
+ -- Simple optimized versions for UNIX systems
+ function getPrefix(path)
+ return path:match("^/")
+ end
+ function splitPath(path)
+ local parts = {}
+ for part in string.gmatch(path, '([^/]+)') do
+ table.insert(parts, part)
+ end
+ return parts
+ end
+ function joinParts(prefix, parts, i, j)
+ if prefix then
+ return prefix .. table.concat(parts, '/', i, j)
+ end
+ return table.concat(parts, '/', i, j)
+ end
+end
+
+local function pathJoin(...)
+ local inputs = {...}
+ local l = #inputs
+
+ -- Find the last segment that is an absolute path
+ -- Or if all are relative, prefix will be nil
+ local i = l
+ local prefix
+ while true do
+ prefix = getPrefix(inputs[i])
+ if prefix or i <= 1 then break end
+ i = i - 1
+ end
+
+ -- If there was one, remove its prefix from its segment
+ if prefix then
+ inputs[i] = inputs[i]:sub(#prefix)
+ end
+
+ -- Split all the paths segments into one large list
+ local parts = {}
+ while i <= l do
+ local sub = splitPath(inputs[i])
+ for j = 1, #sub do
+ parts[#parts + 1] = sub[j]
+ end
+ i = i + 1
+ end
+
+ -- Evaluate special segments in reverse order.
+ local skip = 0
+ local reversed = {}
+ for idx = #parts, 1, -1 do
+ local part = parts[idx]
+ if part == '.' then
+ -- Ignore
+ elseif part == '..' then
+ skip = skip + 1
+ elseif skip > 0 then
+ skip = skip - 1
+ else
+ reversed[#reversed + 1] = part
+ end
+ end
+
+ -- Reverse the list again to get the correct order
+ parts = reversed
+ for idx = 1, #parts / 2 do
+ local j = #parts - idx + 1
+ parts[idx], parts[j] = parts[j], parts[idx]
+ end
+
+ local path = joinParts(prefix, parts)
+ return path
+end
+
+return {
+ isWindows = isWindows,
+ join = pathJoin,
+ getPrefix = getPrefix,
+ splitPath = splitPath,
+ joinparts = joinParts,
+}
diff --git a/plugins/pretty-print/LICENSE b/plugins/pretty-print/LICENSE
new file mode 100644
index 00000000000..8f71f43fee3
--- /dev/null
+++ b/plugins/pretty-print/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/plugins/pretty-print/README.md b/plugins/pretty-print/README.md
new file mode 100644
index 00000000000..82372160e09
--- /dev/null
+++ b/plugins/pretty-print/README.md
@@ -0,0 +1,2 @@
+# luv-pretty-print
+luvit's pretty-print module re-packaged as a luv submodule.
diff --git a/plugins/pretty-print/init.lua b/plugins/pretty-print/init.lua
new file mode 100644
index 00000000000..f1c112d6e4f
--- /dev/null
+++ b/plugins/pretty-print/init.lua
@@ -0,0 +1,362 @@
+--[[
+
+Copyright 2014-2015 The Luvit Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS-IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+--]]
+
+-- Luv port by Tim Caswell <tim@creationix.com>
+
+local exports = {}
+exports.name = "luvit/pretty-print"
+exports.version = "1.0.3"
+exports.homepage = "https://github.com/luvit/luvit/blob/master/deps/pretty-print.lua"
+exports.description = "A lua value pretty printer and colorizer for terminals."
+exports.tags = {"colors", "tty"}
+exports.license = "Apache 2"
+exports.author = { name = "Tim Caswell" }
+
+local uv = require('luv')
+
+local prettyPrint, dump, strip, color, colorize, loadColors
+local theme = {}
+local useColors = false
+local defaultTheme
+
+local stdout, stdin, stderr, width
+
+local quote, quote2, dquote, dquote2, obracket, cbracket, obrace, cbrace, comma, equals, controls
+
+local themes = {
+ -- nice color theme using 16 ansi colors
+ [16] = {
+ property = "0;37", -- white
+ sep = "1;30", -- bright-black
+ braces = "1;30", -- bright-black
+
+ ["nil"] = "1;30", -- bright-black
+ boolean = "0;33", -- yellow
+ number = "1;33", -- bright-yellow
+ string = "0;32", -- green
+ quotes = "1;32", -- bright-green
+ escape = "1;32", -- bright-green
+ ["function"] = "0;35", -- purple
+ thread = "1;35", -- bright-purple
+
+ table = "1;34", -- bright blue
+ userdata = "1;36", -- bright cyan
+ cdata = "0;36", -- cyan
+
+ err = "1;31", -- bright red
+ success = "1;33;42", -- bright-yellow on green
+ failure = "1;33;41", -- bright-yellow on red
+ highlight = "1;36;44", -- bright-cyan on blue
+ },
+ -- nice color theme using ansi 256-mode colors
+ [256] = {
+ property = "38;5;253",
+ braces = "38;5;247",
+ sep = "38;5;240",
+
+ ["nil"] = "38;5;244",
+ boolean = "38;5;220", -- yellow-orange
+ number = "38;5;202", -- orange
+ string = "38;5;34", -- darker green
+ quotes = "38;5;40", -- green
+ escape = "38;5;46", -- bright green
+ ["function"] = "38;5;129", -- purple
+ thread = "38;5;199", -- pink
+
+ table = "38;5;27", -- blue
+ userdata = "38;5;39", -- blue2
+ cdata = "38;5;69", -- teal
+
+ err = "38;5;196", -- bright red
+ success = "38;5;120;48;5;22", -- bright green on dark green
+ failure = "38;5;215;48;5;52", -- bright red on dark red
+ highlight = "38;5;45;48;5;236", -- bright teal on dark grey
+ },
+}
+
+local special = {
+ [7] = 'a',
+ [8] = 'b',
+ [9] = 't',
+ [10] = 'n',
+ [11] = 'v',
+ [12] = 'f',
+ [13] = 'r'
+}
+
+function strip(str)
+ return string.gsub(str, '\027%[[^m]*m', '')
+end
+
+
+function loadColors(index)
+ if index == nil then index = defaultTheme end
+
+ -- Remove the old theme
+ for key in pairs(theme) do
+ theme[key] = nil
+ end
+
+ if index then
+ local new = themes[index]
+ if not new then error("Invalid theme index: " .. tostring(index)) end
+ -- Add the new theme
+ for key in pairs(new) do
+ theme[key] = new[key]
+ end
+ useColors = true
+ else
+ useColors = false
+ end
+
+ quote = colorize('quotes', "'", 'string')
+ quote2 = colorize('quotes', "'")
+ dquote = colorize('quotes', '"', 'string')
+ dquote2 = colorize('quotes', '"')
+ obrace = colorize('braces', '{ ')
+ cbrace = colorize('braces', '}')
+ obracket = colorize('property', '[')
+ cbracket = colorize('property', ']')
+ comma = colorize('sep', ', ')
+ equals = colorize('sep', ' = ')
+
+ controls = {}
+ for i = 0, 31 do
+ local c = special[i]
+ if not c then
+ if i < 10 then
+ c = "00" .. tostring(i)
+ else
+ c = "0" .. tostring(i)
+ end
+ end
+ controls[i] = colorize('escape', '\\' .. c, 'string')
+ end
+ controls[92] = colorize('escape', '\\\\', 'string')
+ controls[34] = colorize('escape', '\\"', 'string')
+ controls[39] = colorize('escape', "\\'", 'string')
+ for i = 128, 255 do
+ local c
+ if i < 100 then
+ c = "0" .. tostring(i)
+ else
+ c = tostring(i)
+ end
+ controls[i] = colorize('escape', '\\' .. c, 'string')
+ end
+
+end
+
+function color(colorName)
+ return '\27[' .. (theme[colorName] or '0') .. 'm'
+end
+
+function colorize(colorName, string, resetName)
+ return useColors and
+ (color(colorName) .. tostring(string) .. color(resetName)) or
+ tostring(string)
+end
+
+local function stringEscape(c)
+ return controls[string.byte(c, 1)]
+end
+
+function dump(value, recurse, nocolor)
+ local seen = {}
+ local output = {}
+ local offset = 0
+ local stack = {}
+
+ local function recalcOffset(index)
+ for i = index + 1, #output do
+ local m = string.match(output[i], "\n([^\n]*)$")
+ if m then
+ offset = #(strip(m))
+ else
+ offset = offset + #(strip(output[i]))
+ end
+ end
+ end
+
+ local function write(text, length)
+ if not length then length = #(strip(text)) end
+ -- Create room for data by opening parent blocks
+ -- Start at the root and go down.
+ local i = 1
+ while offset + length > width and stack[i] do
+ local entry = stack[i]
+ if not entry.opened then
+ entry.opened = true
+ table.insert(output, entry.index + 1, "\n" .. string.rep(" ", i))
+ -- Recalculate the offset
+ recalcOffset(entry.index)
+ -- Bump the index of all deeper entries
+ for j = i + 1, #stack do
+ stack[j].index = stack[j].index + 1
+ end
+ end
+ i = i + 1
+ end
+ output[#output + 1] = text
+ offset = offset + length
+ if offset > width then
+ return dump(stack)
+ end
+ end
+
+ local function indent()
+ stack[#stack + 1] = {
+ index = #output,
+ opened = false,
+ }
+ end
+
+ local function unindent()
+ stack[#stack] = nil
+ end
+
+ local function process(value)
+ local typ = type(value)
+ if typ == 'string' then
+ if string.match(value, "'") and not string.match(value, '"') then
+ write(dquote .. string.gsub(value, '[%c\\\128-\255]', stringEscape) .. dquote2)
+ else
+ write(quote .. string.gsub(value, "[%c\\'\128-\255]", stringEscape) .. quote2)
+ end
+ elseif typ == 'table' and not seen[value] then
+ if not recurse then seen[value] = true end
+ write(obrace)
+ local i = 1
+ -- Count the number of keys so we know when to stop adding commas
+ local total = 0
+ for _ in pairs(value) do total = total + 1 end
+
+ local nextIndex = 1
+ for k, v in pairs(value) do
+ indent()
+ if k == nextIndex then
+ -- if the key matches the last numerical index + 1
+ -- This is how lists print without keys
+ nextIndex = k + 1
+ process(v)
+ else
+ if type(k) == "string" and string.find(k,"^[%a_][%a%d_]*$") then
+ write(colorize("property", k) .. equals)
+ else
+ write(obracket)
+ process(k)
+ write(cbracket .. equals)
+ end
+ if type(v) == "table" then
+ process(v)
+ else
+ indent()
+ process(v)
+ unindent()
+ end
+ end
+ if i < total then
+ write(comma)
+ else
+ write(" ")
+ end
+ i = i + 1
+ unindent()
+ end
+ write(cbrace)
+ else
+ write(colorize(typ, tostring(value)))
+ end
+ end
+
+ process(value)
+ local s = table.concat(output, "")
+ return nocolor and strip(s) or s
+end
+
+-- Print replacement that goes through libuv. This is useful on windows
+-- to use libuv's code to translate ansi escape codes to windows API calls.
+function _G.print(...)
+ local n = select('#', ...)
+ local arguments = {...}
+ for i = 1, n do
+ arguments[i] = tostring(arguments[i])
+ end
+ uv.write(stdout, table.concat(arguments, "\t") .. "\n")
+end
+
+function prettyPrint(...)
+ local n = select('#', ...)
+ local arguments = { ... }
+
+ for i = 1, n do
+ arguments[i] = dump(arguments[i])
+ end
+
+ print(table.concat(arguments, "\t"))
+end
+
+function strip(str)
+ return string.gsub(str, '\027%[[^m]*m', '')
+end
+
+if uv.guess_handle(0) == 'tty' then
+ stdin = assert(uv.new_tty(0, true))
+else
+ stdin = uv.new_pipe(false)
+ uv.pipe_open(stdin, 0)
+end
+
+if uv.guess_handle(1) == 'tty' then
+ stdout = assert(uv.new_tty(1, false))
+ width = uv.tty_get_winsize(stdout)
+ if width == 0 then width = 80 end
+ -- auto-detect when 16 color mode should be used
+ local term = os.getenv("TERM")
+ if term == 'xterm' or term == 'xterm-256color' then
+ defaultTheme = 256
+ else
+ defaultTheme = 16
+ end
+else
+ stdout = uv.new_pipe(false)
+ uv.pipe_open(stdout, 1)
+ width = 80
+end
+loadColors()
+
+if uv.guess_handle(2) == 'tty' then
+ stderr = assert(uv.new_tty(2, false))
+else
+ stderr = uv.new_pipe(false)
+ uv.pipe_open(stderr, 2)
+end
+
+exports.loadColors = loadColors
+exports.theme = theme
+exports.print = print
+exports.prettyPrint = prettyPrint
+exports.dump = dump
+exports.color = color
+exports.colorize = colorize
+exports.stdin = stdin
+exports.stdout = stdout
+exports.stderr = stderr
+exports.strip = strip
+
+return exports
diff --git a/plugins/querystring/LICENSE b/plugins/querystring/LICENSE
new file mode 100644
index 00000000000..8f71f43fee3
--- /dev/null
+++ b/plugins/querystring/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/plugins/querystring/README.md b/plugins/querystring/README.md
new file mode 100644
index 00000000000..7f3437312bb
--- /dev/null
+++ b/plugins/querystring/README.md
@@ -0,0 +1,2 @@
+# luv-querystring
+A luv port of luvit's querystring
diff --git a/plugins/querystring/init.lua b/plugins/querystring/init.lua
new file mode 100644
index 00000000000..0e2675f9252
--- /dev/null
+++ b/plugins/querystring/init.lua
@@ -0,0 +1,105 @@
+--[[
+
+Copyright 2015 The Luvit Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS-IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+--]]
+
+local exports = {}
+exports.name = "luvit/querystring"
+exports.version = "1.0.2"
+exports.license = "Apache 2"
+exports.homepage = "https://github.com/luvit/luvit/blob/master/deps/querystring.lua"
+exports.description = "Node-style query-string codec for luvit"
+exports.tags = {"luvit", "url", "codec"}
+
+local find = string.find
+local gsub = string.gsub
+local char = string.char
+local byte = string.byte
+local format = string.format
+local match = string.match
+local gmatch = string.gmatch
+
+function exports.urldecode(str)
+ str = gsub(str, '+', ' ')
+ str = gsub(str, '%%(%x%x)', function(h)
+ return char(tonumber(h, 16))
+ end)
+ str = gsub(str, '\r\n', '\n')
+ return str
+end
+
+function exports.urlencode(str)
+ if str then
+ str = gsub(str, '\n', '\r\n')
+ str = gsub(str, '([^%w])', function(c)
+ return format('%%%02X', byte(c))
+ end)
+ end
+ return str
+end
+
+local function stringifyPrimitive(v)
+ return tostring(v)
+end
+
+function exports.stringify(params, sep, eq)
+ if not sep then sep = '&' end
+ if not eq then eq = '=' end
+ if type(params) == "table" then
+ local fields = {}
+ for key,value in pairs(params) do
+ local keyString = exports.urlencode(stringifyPrimitive(key)) .. eq
+ if type(value) == "table" then
+ for _, v in ipairs(value) do
+ table.insert(fields, keyString .. exports.urlencode(stringifyPrimitive(v)))
+ end
+ else
+ table.insert(fields, keyString .. exports.urlencode(stringifyPrimitive(value)))
+ end
+ end
+ return table.concat(fields, sep)
+ end
+ return ''
+end
+
+-- parse querystring into table. urldecode tokens
+function exports.parse(str, sep, eq)
+ if not sep then sep = '&' end
+ if not eq then eq = '=' end
+ local vars = {}
+ for pair in gmatch(tostring(str), '[^' .. sep .. ']+') do
+ if not find(pair, eq) then
+ vars[exports.urldecode(pair)] = ''
+ else
+ local key, value = match(pair, '([^' .. eq .. ']*)' .. eq .. '(.*)')
+ if key then
+ key = exports.urldecode(key)
+ value = exports.urldecode(value)
+ local type = type(vars[key])
+ if type=='nil' then
+ vars[key] = value
+ elseif type=='table' then
+ table.insert(vars[key], value)
+ else
+ vars[key] = {vars[key],value}
+ end
+ end
+ end
+ end
+ return vars
+end
+
+return exports
diff --git a/plugins/weblit/LICENSE b/plugins/weblit/LICENSE
new file mode 100644
index 00000000000..5789b767285
--- /dev/null
+++ b/plugins/weblit/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Tim Caswell
+
+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.
+
diff --git a/plugins/weblit/README.md b/plugins/weblit/README.md
new file mode 100644
index 00000000000..55d6085d22b
--- /dev/null
+++ b/plugins/weblit/README.md
@@ -0,0 +1,239 @@
+# weblit
+
+A web framework for luv (ported from luvit/lit)
+
+Weblit is a collection of lit packages that together form a nice web framework.
+
+## weblit/app
+
+This is the core of the framework. It's export value is the app itself. The
+config functions can be chained off this config for super terse syntax.
+
+```lua
+require('weblit/app')
+
+ .bind({
+ host = "0.0.0.0",
+ port = 8080
+ })
+
+ .use(require('weblit/logger'))
+ .use(require('weblit/auto-headers'))
+ .use(require('weblit/etag-cache'))
+
+ .route({
+ method = "GET",
+ path = "/do/:user/:action",
+ domain = "*.myapp.io"
+ }, function (req, res, go)
+ -- Handle route
+ end)
+
+ .start()
+
+```
+
+### bind(options)
+
+Use this to configure your server. You can bind to multiple addresses and
+ports. For example, the same server can listen on port `8080` using normal HTTP
+while also listening on port `8443` using HTTPS.
+
+```lua
+-- Listen on port 8080 internally using plain HTTP.
+.bind({
+ host = "127.0.0.1",
+ port = 8080
+})
+
+-- Also listen on port 8443 externally using encrypted HTTPS.
+.bind({
+ host = "0.0.0.0",
+ port = 8443,
+ tls = {
+ cert = module:load("cert.pem"),
+ key = module:load("key.pem",
+ }
+})
+```
+
+The `host` option defaults to `"127.0.0.1"`. The default port depends on if
+you're running as root and if the connection is TLS encrypted.
+
+ | Root | User
+------|-----:|------:
+HTTP | 80 | 8080
+HTTPS | 442 | 8443
+
+
+### use(middleware)
+
+This adds a raw middleware to the chain. It's signature is:
+
+```lua
+.use(function (req, res, go)
+ -- Log the request table
+ p("request", req)
+ -- Hand off to the next layer.
+ return go()
+end)
+```
+
+The `req` table will contain information about the HTTP request. This includes
+several fields:
+
+ - `socket` - The raw libuv `uv_tty_t` socket.
+ - `method` - The HTTP request method verb like `GET` or `POST`.
+ - `path` - The raw HTTP request path (including query string).
+ - `headers` - A list of headers. Each header is a table with two entries for
+ key and value. For convenience, there are special `__index` and
+ `__newindex` metamethods that let you treat this like a case insensitive
+ key/value store.
+ - `version` - The HTTP version (Usually either `1.0` or `1.1`).
+ - `keepAlive` - A flag telling you if this should be a keepalive connection.
+ - `body` - The request body as a string. In the future, this may also be a stream.
+
+The `res` table also has some conventions used to form the response a piece at a
+time. Initially it contains:
+
+ - `code` - The response status code. Initially contains `404`.
+ - `headers` - Another special headers table like in `req`.
+ - `body` - The response body to send. Initially contains `"Not Found\n"`.
+
+The `go` function is to be called if you wish to not handle a request. This
+allows other middleware layers to get a chance to respond to the request. Use a
+tail call if there is nothing more to do.
+
+Otherwise do further processing after `go` returns. At this point, all inner
+layers have finished and a response is ready in `res`.
+
+### route(options, middleware)
+
+Route is like use, but allows you to pre-filter the requests before the middleware
+is called.
+
+```lua
+.route({
+ method = "PUT",
+ path = "/upload/:username"
+}, function (req, res, go)
+ local url = saveFile(req.params.username, req.body)
+ res.code = 201
+ res.headers.Location = url
+end)
+```
+
+The route options accept several parameters:
+
+ - `method` - This is a simple filter on a specific HTTP method verb.
+ - `path` - This is either an exact match or can contain patterns. Segments
+ looking like `:name` will match single path segments while `:name:` will
+ match multiple segments. The matches will go into `req.params`. Also any
+ query string will be stripped off, parsed out, and stored in `req.query`.
+ - `host` - Will filter against the `Host` header. This can be an exact match
+ or a glob match like `*.mydomain.org`.
+ - `filter` - Filter is a custom lua function that accepts `req` and returns
+ `true` or `false`.
+
+If the request matches all the requirements, then the middleware is called the
+same as with `use`.
+
+### start
+
+Bind to the port(s), listen on the socket(s) and start accepting connections.
+
+## weblit/logger
+
+This is a simple middleware that logs the request method, url and user agent.
+It also includes the response status code.
+
+Make sure to use it at the top of your middleware chain so that it's able to see
+the final response code sent to the client.
+
+```lua
+.use(require('weblit/logger'))
+```
+
+## weblit/auto-headers
+
+This implements lots of conventions and useful defaults that help your app
+implement a proper HTTP server.
+
+You should always use this near the top of the list. The only middleware that
+goes before this is the logger.
+
+
+```lua
+.use(require('weblit/auto-headers'))
+```
+
+## weblit/etag-cache
+
+This caches responses in memory keyed by etag. If there is no etag, but there
+is a response body, it will use the body to generate an etag.
+
+Put this in your list after auto-headers, but before custom server logic.
+
+```lua
+.use(require('weblit/etag-cache'))
+```
+
+## weblit/static
+
+This middleware serves static files to the user. Use this to serve your client-
+side web assets.
+
+Usage is pretty simplistic for now.
+
+```lua
+local static = require('weblit/static')
+app.use(static("path/to/static/assets"))
+```
+
+If you want to only match a sub-path, use the router.
+
+```lua
+app.route({
+ path = "/blog/:path:"
+}, static(pathJoin(module.dir, "articles")))
+```
+
+The `path` param will be used if it exists and the full path will be used
+otherwise.
+
+## weblit/websocket
+
+This implements a websocket upgrade handler. You can choose the subprotocol and
+other routing information.
+
+```lua
+app.websocket({
+ path = "/v2/socket", -- Prefix for matching
+ protocol = "virgo/2.0", -- Restrict to a websocket sub-protocol
+}, function (req, read, write)
+ -- Log the request headers
+ p(req)
+ -- Log and echo all messages
+ for message in read do
+ write(message)
+ end
+ -- End the stream
+ write()
+end)
+```
+
+
+## weblit
+
+This is the metapackage that simply includes the other modules.
+
+It exposes the other modules as a single exports table.
+
+```lua
+exports.app = require('weblit/app')
+exports.autoHeaders = require('weblit/auto-headers')
+exports.etagCache = require('weblit/etag-cache')
+exports.logger = require('weblit/logger')
+exports.static = require('weblit/static')
+exports.websocket = require('weblit/websocket')
+```
diff --git a/plugins/weblit/app.lua b/plugins/weblit/app.lua
new file mode 100644
index 00000000000..d3839c1df8e
--- /dev/null
+++ b/plugins/weblit/app.lua
@@ -0,0 +1,261 @@
+
+
+local createServer = require('coro-net').createServer
+local wrapper = require('coro-wrapper')
+local readWrap, writeWrap = wrapper.reader, wrapper.writer
+local httpCodec = require('http-codec')
+--local tlsWrap = require('coro-tls').wrap
+local parseQuery = require('querystring').parse
+
+-- Ignore SIGPIPE if it exists on platform
+local uv = require('luv')
+if uv.constants.SIGPIPE then
+ uv.new_signal():start("sigpipe")
+end
+
+local server = {}
+local handlers = {}
+local bindings = {}
+
+-- Provide a nice case insensitive interface to headers.
+local headerMeta = {
+ __index = function (list, name)
+ if type(name) ~= "string" then
+ return rawget(list, name)
+ end
+ name = name:lower()
+ for i = 1, #list do
+ local key, value = unpack(list[i])
+ if key:lower() == name then return value end
+ end
+ end,
+ __newindex = function (list, name, value)
+ -- non-string keys go through as-is.
+ if type(name) ~= "string" then
+ return rawset(list, name, value)
+ end
+ -- First remove any existing pairs with matching key
+ local lowerName = name:lower()
+ for i = #list, 1, -1 do
+ if list[i][1]:lower() == lowerName then
+ table.remove(list, i)
+ end
+ end
+ -- If value is nil, we're done
+ if value == nil then return end
+ -- Otherwise, set the key(s)
+ if (type(value) == "table") then
+ -- We accept a table of strings
+ for i = 1, #value do
+ rawset(list, #list + 1, {name, tostring(value[i])})
+ end
+ else
+ -- Or a single value interperted as string
+ rawset(list, #list + 1, {name, tostring(value)})
+ end
+ end,
+}
+
+local function handleRequest(head, input, socket)
+ local req = {
+ socket = socket,
+ method = head.method,
+ path = head.path,
+ headers = setmetatable({}, headerMeta),
+ version = head.version,
+ keepAlive = head.keepAlive,
+ body = input
+ }
+ for i = 1, #head do
+ req.headers[i] = head[i]
+ end
+
+ local res = {
+ code = 404,
+ headers = setmetatable({}, headerMeta),
+ body = "Not Found\n",
+ }
+
+ local function run(i)
+ local success, err = pcall(function ()
+ i = i or 1
+ local go = i < #handlers
+ and function ()
+ return run(i + 1)
+ end
+ or function () end
+ return handlers[i](req, res, go)
+ end)
+ if not success then
+ res.code = 500
+ res.headers = setmetatable({}, headerMeta)
+ res.body = err
+ print(err)
+ end
+ end
+ run(1)
+
+ local out = {
+ code = res.code,
+ keepAlive = res.keepAlive,
+ }
+ for i = 1, #res.headers do
+ out[i] = res.headers[i]
+ end
+ return out, res.body, res.upgrade
+end
+
+local function handleConnection(rawRead, rawWrite, socket)
+
+ -- Speak in HTTP events
+ local read, updateDecoder = readWrap(rawRead, httpCodec.decoder())
+ local write, updateEncoder = writeWrap(rawWrite, httpCodec.encoder())
+
+ for head in read do
+ local parts = {}
+ for chunk in read do
+ if #chunk > 0 then
+ parts[#parts + 1] = chunk
+ else
+ break
+ end
+ end
+ local res, body, upgrade = handleRequest(head, #parts > 0 and table.concat(parts) or nil, socket)
+ write(res)
+ if upgrade then
+ return upgrade(read, write, updateDecoder, updateEncoder, socket)
+ end
+ write(body)
+ if not (res.keepAlive and head.keepAlive) then
+ break
+ end
+ end
+ write()
+
+end
+
+function server.bind(options)
+ if not options.host then
+ options.host = "127.0.0.1"
+ end
+ if not options.port then
+ options.port = require('uv').getuid() == 0 and
+ (options.tls and 443 or 80) or
+ (options.tls and 8443 or 8080)
+ end
+ bindings[#bindings + 1] = options
+ return server
+end
+
+function server.use(handler)
+ handlers[#handlers + 1] = handler
+ return server
+end
+
+
+function server.start()
+ if #bindings == 0 then
+ server.bind({})
+ end
+ for i = 1, #bindings do
+ local options = bindings[i]
+ createServer(options, function (rawRead, rawWrite, socket)
+ --local tls = options.tls
+ --if tls then
+ --rawRead, rawWrite = tlsWrap(rawRead, rawWrite, {
+ -- server = true,
+ --key = assert(tls.key, "tls key required"),
+ --cert = assert(tls.cert, "tls cert required"),
+ --})
+ --end
+ return handleConnection(rawRead, rawWrite, socket)
+ end)
+ print("HTTP server listening at http" .. (options.tls and "s" or "") .. "://" .. options.host .. (options.port == (options.tls and 443 or 80) and "" or ":" .. options.port) .. "/")
+ end
+ return server
+end
+
+local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])'
+local function escape(str)
+ return str:gsub(quotepattern, "%%%1")
+end
+
+local function compileGlob(glob)
+ local parts = {"^"}
+ for a, b in glob:gmatch("([^*]*)(%**)") do
+ if #a > 0 then
+ parts[#parts + 1] = escape(a)
+ end
+ if #b > 0 then
+ parts[#parts + 1] = "(.*)"
+ end
+ end
+ parts[#parts + 1] = "$"
+ local pattern = table.concat(parts)
+ return function (string)
+ return string and string:match(pattern)
+ end
+end
+
+local function compileRoute(route)
+ local parts = {"^"}
+ local names = {}
+ for a, b, c, d in route:gmatch("([^:]*):([_%a][_%w]*)(:?)([^:]*)") do
+ if #a > 0 then
+ parts[#parts + 1] = escape(a)
+ end
+ if #c > 0 then
+ parts[#parts + 1] = "(.*)"
+ else
+ parts[#parts + 1] = "([^/]*)"
+ end
+ names[#names + 1] = b
+ if #d > 0 then
+ parts[#parts + 1] = escape(d)
+ end
+ end
+ if #parts == 1 then
+ return function (string)
+ if string == route then return {} end
+ end
+ end
+ parts[#parts + 1] = "$"
+ local pattern = table.concat(parts)
+ return function (string)
+ local matches = {string:match(pattern)}
+ if #matches > 0 then
+ local results = {}
+ for i = 1, #matches do
+ results[i] = matches[i]
+ results[names[i]] = matches[i]
+ end
+ return results
+ end
+ end
+end
+
+function server.route(options, handler)
+ local method = options.method
+ local path = options.path and compileRoute(options.path)
+ local host = options.host and compileGlob(options.host)
+ local filter = options.filter
+ server.use(function (req, res, go)
+ if method and req.method ~= method then return go() end
+ if host and not host(req.headers.host) then return go() end
+ if filter and not filter(req) then return go() end
+ local params
+ if path then
+ local pathname, query = req.path:match("^([^?]*)%??(.*)")
+ params = path(pathname)
+ if not params then return go() end
+ if #query > 0 then
+ req.query = parseQuery(query)
+ end
+ end
+ req.params = params or {}
+ return handler(req, res, go)
+ end)
+ return server
+end
+
+return server
diff --git a/plugins/weblit/auto-headers.lua b/plugins/weblit/auto-headers.lua
new file mode 100644
index 00000000000..44ad65779e9
--- /dev/null
+++ b/plugins/weblit/auto-headers.lua
@@ -0,0 +1,92 @@
+
+
+--[[
+
+Response automatic values:
+ - Auto Server header
+ - Auto Date Header
+ - code defaults to 404 with body "Not Found\n"
+ - if there is a string body add Content-Length and ETag if missing
+ - if string body and no Content-Type, use text/plain for valid utf-8, application/octet-stream otherwise
+ - Auto add "; charset=utf-8" to Content-Type when body is known to be valid utf-8
+ - Auto 304 responses for if-none-match requests
+ - Auto strip body with HEAD requests
+ - Auto chunked encoding if body with unknown length
+ - if Connection header set and not keep-alive, set res.keepAlive to false
+ - Add Connection Keep-Alive/Close if not found based on res.keepAlive
+
+--TODO: utf8 scanning
+
+]]
+
+--local digest = require('openssl').digest.digest
+local date = require('os').date
+
+return function (req, res, go)
+ local isHead = false
+ if req.method == "HEAD" then
+ req.method = "GET"
+ isHead = true
+ end
+
+ local requested = req.headers["if-none-match"]
+
+ go()
+
+ -- We could use the fancy metatable, but this is much faster
+ local lowerHeaders = {}
+ local headers = res.headers
+ for i = 1, #headers do
+ local key, value = unpack(headers[i])
+ lowerHeaders[key:lower()] = value
+ end
+
+
+ if not lowerHeaders.server then
+ headers[#headers + 1] = {"Server", serverName}
+ end
+ if not lowerHeaders.date then
+ headers[#headers + 1] = {"Date", date("!%a, %d %b %Y %H:%M:%S GMT")}
+ end
+
+ if not lowerHeaders.connection then
+ if req.keepAlive then
+ lowerHeaders.connection = "Keep-Alive"
+ headers[#headers + 1] = {"Connection", "Keep-Alive"}
+ else
+ headers[#headers + 1] = {"Connection", "Close"}
+ end
+ end
+ res.keepAlive = lowerHeaders.connection and lowerHeaders.connection:lower() == "keep-alive"
+
+ local body = res.body
+ if body then
+ local needLength = not lowerHeaders["content-length"] and not lowerHeaders["transfer-encoding"]
+ if type(body) == "string" then
+ if needLength then
+ headers[#headers + 1] = {"Content-Length", #body}
+ end
+ -- if not lowerHeaders.etag then
+ -- local etag = '"' .. digest("sha1", body) .. '"'
+ -- lowerHeaders.etag = etag
+ --headers[#headers + 1] = {"ETag", etag}
+ -- end
+ else
+ if needLength then
+ headers[#headers + 1] = {"Transfer-Encoding", "chunked"}
+ end
+ end
+ if not lowerHeaders["content-type"] then
+ headers[#headers + 1] = {"Content-Type", "text/plain"}
+ end
+ end
+
+ local etag = lowerHeaders.etag
+ if requested and res.code >= 200 and res.code < 300 and requested == etag then
+ res.code = 304
+ body = nil
+ end
+
+ if isHead then body = nil end
+ res.body = body
+end
diff --git a/plugins/weblit/etag-cache.lua b/plugins/weblit/etag-cache.lua
new file mode 100644
index 00000000000..e8c5d149b35
--- /dev/null
+++ b/plugins/weblit/etag-cache.lua
@@ -0,0 +1,39 @@
+
+local function clone(headers)
+ local copy = setmetatable({}, getmetatable(headers))
+ for i = 1, #headers do
+ copy[i] = headers[i]
+ end
+ return copy
+end
+
+local cache = {}
+return function (req, res, go)
+ local requested = req.headers["If-None-Match"]
+ local host = req.headers.Host
+ local key = host and host .. "|" .. req.path or req.path
+ local cached = cache[key]
+ if not requested and cached then
+ req.headers["If-None-Match"] = cached.etag
+ end
+ go()
+ local etag = res.headers.ETag
+ if not etag then return end
+ if res.code >= 200 and res.code < 300 then
+ local body = res.body
+ if not body or type(body) == "string" then
+ cache[key] = {
+ etag = etag,
+ code = res.code,
+ headers = clone(res.headers),
+ body = body
+ }
+ end
+ elseif res.code == 304 then
+ if not requested and cached and etag == cached.etag then
+ res.code = cached.code
+ res.headers = clone(cached.headers)
+ res.body = cached.body
+ end
+ end
+end
diff --git a/plugins/weblit/init.lua b/plugins/weblit/init.lua
new file mode 100644
index 00000000000..f9224b7880c
--- /dev/null
+++ b/plugins/weblit/init.lua
@@ -0,0 +1,8 @@
+local exports = {}
+exports.app = require('weblit/app')
+exports.autoHeaders = require('weblit/auto-headers')
+exports.etagCache = require('weblit/etag-cache')
+exports.logger = require('weblit/logger')
+exports.static = require('weblit/static')
+exports.websocket = require('weblit/websocket')
+return exports
diff --git a/plugins/weblit/logger.lua b/plugins/weblit/logger.lua
new file mode 100644
index 00000000000..912b4eed768
--- /dev/null
+++ b/plugins/weblit/logger.lua
@@ -0,0 +1,10 @@
+
+return function (req, res, go)
+ -- Skip this layer for clients who don't send User-Agent headers.
+ local userAgent = req.headers["user-agent"]
+ if not userAgent then return go() end
+ -- Run all inner layers first.
+ go()
+ -- And then log after everything is done
+ --print(string.format("%s %s %s %s", req.method, req.path, userAgent, res.code))
+end
diff --git a/plugins/weblit/static.lua b/plugins/weblit/static.lua
new file mode 100644
index 00000000000..b34ea638fa1
--- /dev/null
+++ b/plugins/weblit/static.lua
@@ -0,0 +1,62 @@
+
+local getType = require("mime").getType
+local jsonStringify = require('json').stringify
+
+local makeChroot = require('coro-fs').chroot
+
+return function (rootPath)
+
+ local fs = makeChroot(rootPath)
+
+ return function (req, res, go)
+ if req.method ~= "GET" then return go() end
+ local path = (req.params and req.params.path) or req.path
+ path = path:match("^[^?#]*")
+ if path:byte(1) == 47 then
+ path = path:sub(2)
+ end
+ local stat = fs.stat(path)
+ if not stat then return go() end
+
+ local function renderFile()
+ local body = assert(fs.readFile(path))
+ res.code = 200
+ res.headers["Content-Type"] = getType(path)
+ res.body = body
+ return
+ end
+
+ local function renderDirectory()
+ if req.path:byte(-1) ~= 47 then
+ res.code = 301
+ res.headers.Location = req.path .. '/'
+ return
+ end
+ local files = {}
+ for entry in fs.scandir(path) do
+ if entry.name == "index.html" and entry.type == "file" then
+ path = (#path > 0 and path .. "/" or "") .. "index.html"
+ return renderFile()
+ end
+ files[#files + 1] = entry
+ entry.url = "http://" .. req.headers.host .. req.path .. entry.name
+ end
+ local body = jsonStringify(files) .. "\n"
+ res.code = 200
+ res.headers["Content-Type"] = "application/json"
+ res.body = body
+ return
+ end
+
+ if stat.type == "directory" then
+ return renderDirectory()
+ elseif stat.type == "file" then
+ if req.path:byte(-1) == 47 then
+ res.code = 301
+ res.headers.Location = req.path:match("^(.*[^/])/+$")
+ return
+ end
+ return renderFile()
+ end
+ end
+end
diff --git a/plugins/weblit/websocket.lua b/plugins/weblit/websocket.lua
new file mode 100644
index 00000000000..d5dfe572ea6
--- /dev/null
+++ b/plugins/weblit/websocket.lua
@@ -0,0 +1,82 @@
+
+local websocketCodec = require('websocket-codec')
+
+local function websocketHandler(options, handler)
+ return function (req, res, go)
+ -- Websocket connections must be GET requests
+ -- with 'Upgrade: websocket'
+ -- and 'Connection: Upgrade' headers
+ local headers = req.headers
+ local connection = headers.connection
+ local upgrade = headers.upgrade
+ if not (
+ req.method == "GET" and
+ upgrade and upgrade:lower():find("websocket", 1, true) and
+ connection and connection:lower():find("upgrade", 1, true)
+ ) then
+ return go()
+ end
+
+ if options.filter and not options.filter(req) then
+ return go()
+ end
+
+ -- If there is a sub-protocol specified, filter on it.
+ local protocol = options.protocol
+ if protocol then
+ local list = headers["sec-websocket-protocol"]
+ local foundProtocol
+ if list then
+ for item in list:gmatch("[^, ]+") do
+ if item == protocol then
+ foundProtocol = true
+ break
+ end
+ end
+ end
+ if not foundProtocol then
+ return go()
+ end
+ end
+
+ -- Make sure it's a new client speaking v13 of the protocol
+ assert(tonumber(headers["sec-websocket-version"]) >= 13, "only websocket protocol v13 supported")
+
+ -- Get the security key
+ local key = assert(headers["sec-websocket-key"], "websocket security required")
+
+ res.code = 101
+ headers = res.headers
+ headers.Upgrade = "websocket"
+ headers.Connection = "Upgrade"
+ headers["Sec-WebSocket-Accept"] = websocketCodec.acceptKey(key)
+ if protocol then
+ headers["Sec-WebSocket-Protocol"] = protocol
+ end
+ function res.upgrade(read, write, updateDecoder, updateEncoder)
+ updateDecoder(websocketCodec.decode)
+ updateEncoder(websocketCodec.encode)
+ local success, err = pcall(handler, req, read, write)
+ if not success then
+ print(err)
+ write({
+ opcode = 1,
+ payload = err,
+ })
+ return write()
+ end
+ end
+ end
+end
+
+local server = require('weblit-app')
+function server.websocket(options, handler)
+ server.route({
+ method = "GET",
+ path = options.path,
+ host = options.host,
+ }, websocketHandler(options, handler))
+ return server
+end
+
+return websocketHandler
diff --git a/plugins/websocket-codec/LICENSE b/plugins/websocket-codec/LICENSE
new file mode 100644
index 00000000000..5789b767285
--- /dev/null
+++ b/plugins/websocket-codec/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Tim Caswell
+
+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.
+
diff --git a/plugins/websocket-codec/README.md b/plugins/websocket-codec/README.md
new file mode 100644
index 00000000000..50f41340dce
--- /dev/null
+++ b/plugins/websocket-codec/README.md
@@ -0,0 +1,2 @@
+# luv-websocket-codec
+A luv port of lit's websocket codec
diff --git a/plugins/websocket-codec/init.lua b/plugins/websocket-codec/init.lua
new file mode 100644
index 00000000000..8a7da7f9190
--- /dev/null
+++ b/plugins/websocket-codec/init.lua
@@ -0,0 +1,261 @@
+local exports = {}
+
+exports.name = "creationix/websocket-codec"
+exports.version = "1.0.7"
+exports.homepage = "https://github.com/luvit/lit/blob/master/deps/websocket-codec.lua"
+exports.description = "A codec implementing websocket framing and helpers for handshakeing"
+exports.tags = {"http", "websocket", "codec"}
+exports.license = "MIT"
+exports.author = { name = "Tim Caswell" }
+
+local digest = require('openssl').digest.digest
+local base64 = require('openssl').base64
+local random = require('openssl').random
+local bit = require('bit')
+
+local band = bit.band
+local bor = bit.bor
+local bxor = bit.bxor
+local rshift = bit.rshift
+local lshift = bit.lshift
+local char = string.char
+local byte = string.byte
+local sub = string.sub
+local gmatch = string.gmatch
+local lower = string.lower
+local gsub = string.gsub
+local concat = table.concat
+
+local function applyMask(data, mask)
+ local bytes = {
+ [0] = byte(mask, 1),
+ [1] = byte(mask, 2),
+ [2] = byte(mask, 3),
+ [3] = byte(mask, 4)
+ }
+ local out = {}
+ for i = 1, #data do
+ out[i] = char(
+ bxor(byte(data, i), bytes[(i - 1) % 4])
+ )
+ end
+ return concat(out)
+end
+
+function exports.decode(chunk)
+ if #chunk < 2 then return end
+ local second = byte(chunk, 2)
+ local len = band(second, 0x7f)
+ local offset
+ if len == 126 then
+ if #chunk < 4 then return end
+ len = bor(
+ lshift(byte(chunk, 3), 8),
+ byte(chunk, 4))
+ offset = 4
+ elseif len == 127 then
+ if #chunk < 10 then return end
+ len = bor(
+ lshift(byte(chunk, 3), 56),
+ lshift(byte(chunk, 4), 48),
+ lshift(byte(chunk, 5), 40),
+ lshift(byte(chunk, 6), 32),
+ lshift(byte(chunk, 7), 24),
+ lshift(byte(chunk, 8), 16),
+ lshift(byte(chunk, 9), 8),
+ byte(chunk, 10))
+ offset = 10
+ else
+ offset = 2
+ end
+ local mask = band(second, 0x80) > 0
+ if mask then
+ offset = offset + 4
+ end
+ if #chunk < offset + len then return end
+
+ local first = byte(chunk, 1)
+ local payload = sub(chunk, offset + 1, offset + len)
+ assert(#payload == len, "Length mismatch")
+ if mask then
+ payload = applyMask(payload, sub(chunk, offset - 3, offset))
+ end
+ local extra = sub(chunk, offset + len + 1)
+ return {
+ fin = band(first, 0x80) > 0,
+ rsv1 = band(first, 0x40) > 0,
+ rsv2 = band(first, 0x20) > 0,
+ rsv3 = band(first, 0x10) > 0,
+ opcode = band(first, 0xf),
+ mask = mask,
+ len = len,
+ payload = payload
+ }, extra
+end
+
+function exports.encode(item)
+ if type(item) == "string" then
+ item = {
+ opcode = 2,
+ payload = item
+ }
+ end
+ local payload = item.payload
+ assert(type(payload) == "string", "payload must be string")
+ local len = #payload
+ local fin = item.fin
+ if fin == nil then fin = true end
+ local rsv1 = item.rsv1
+ local rsv2 = item.rsv2
+ local rsv3 = item.rsv3
+ local opcode = item.opcode or 2
+ local mask = item.mask
+ local chars = {
+ char(bor(
+ fin and 0x80 or 0,
+ rsv1 and 0x40 or 0,
+ rsv2 and 0x20 or 0,
+ rsv3 and 0x10 or 0,
+ opcode
+ )),
+ char(bor(
+ mask and 0x80 or 0,
+ len < 126 and len or (len < 0x10000) and 126 or 127
+ ))
+ }
+ if len >= 0x10000 then
+ chars[3] = char(band(rshift(len, 56), 0xff))
+ chars[4] = char(band(rshift(len, 48), 0xff))
+ chars[5] = char(band(rshift(len, 40), 0xff))
+ chars[6] = char(band(rshift(len, 32), 0xff))
+ chars[7] = char(band(rshift(len, 24), 0xff))
+ chars[8] = char(band(rshift(len, 16), 0xff))
+ chars[9] = char(band(rshift(len, 8), 0xff))
+ chars[10] = char(band(len, 0xff))
+ elseif len >= 126 then
+ chars[3] = char(band(rshift(len, 8), 0xff))
+ chars[4] = char(band(len, 0xff))
+ end
+ if mask then
+ local key = random(4)
+ return concat(chars) .. key .. applyMask(payload, key)
+ end
+ return concat(chars) .. payload
+end
+
+local websocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+function exports.acceptKey(key)
+ return gsub(base64(digest("sha1", key .. websocketGuid, true)), "\n", "")
+end
+local acceptKey = exports.acceptKey
+
+-- Make a client handshake connection
+function exports.handshake(options, request)
+ local key = gsub(base64(random(20)), "\n", "")
+ local host = options.host
+ local path = options.path or "/"
+ local protocol = options.protocol
+ local req = {
+ method = "GET",
+ path = path,
+ {"Connection", "Upgrade"},
+ {"Upgrade", "websocket"},
+ {"Sec-WebSocket-Version", "13"},
+ {"Sec-WebSocket-Key", key},
+ }
+ for i = 1, #options do
+ req[#req + 1] = options[i]
+ end
+ if host then
+ req[#req + 1] = {"Host", host}
+ end
+ if protocol then
+ req[#req + 1] = {"Sec-WebSocket-Protocol", protocol}
+ end
+ local res = request(req)
+ if not res then
+ return nil, "Missing response from server"
+ end
+ -- Parse the headers for quick reading
+ if res.code ~= 101 then
+ return nil, "response must be code 101"
+ end
+
+ local headers = {}
+ for i = 1, #res do
+ local name, value = unpack(res[i])
+ headers[lower(name)] = value
+ end
+
+ if not headers.connection or lower(headers.connection) ~= "upgrade" then
+ return nil, "Invalid or missing connection upgrade header in response"
+ end
+ if headers["sec-websocket-accept"] ~= acceptKey(key) then
+ return nil, "challenge key missing or mismatched"
+ end
+ if protocol and headers["sec-websocket-protocol"] ~= protocol then
+ return nil, "protocol missing or mistmatched"
+ end
+ return true
+end
+
+function exports.handleHandshake(head, protocol)
+
+ -- WebSocket connections must be GET requests
+ if not head.method == "GET" then return end
+
+ -- Parse the headers for quick reading
+ local headers = {}
+ for i = 1, #head do
+ local name, value = unpack(head[i])
+ headers[lower(name)] = value
+ end
+
+ -- Must have 'Upgrade: websocket' and 'Connection: Upgrade' headers
+ if not (headers.connection and headers.upgrade and
+ headers.connection:lower():find("upgrade", 1, true) and
+ headers.upgrade:lower():find("websocket", 1, true)) then return end
+
+ -- Make sure it's a new client speaking v13 of the protocol
+ if tonumber(headers["sec-websocket-version"]) < 13 then
+ return nil, "only websocket protocol v13 supported"
+ end
+
+ local key = headers["sec-websocket-key"]
+ if not key then
+ return nil, "websocket security key missing"
+ end
+
+ -- If the server wants a specified protocol, check for it.
+ if protocol then
+ local foundProtocol = false
+ local list = headers["sec-websocket-protocol"]
+ if list then
+ for item in gmatch(list, "[^, ]+") do
+ if item == protocol then
+ foundProtocol = true
+ break
+ end
+ end
+ end
+ if not foundProtocol then
+ return nil, "specified protocol missing in request"
+ end
+ end
+
+ local accept = acceptKey(key)
+
+ local res = {
+ code = 101,
+ {"Upgrade", "websocket"},
+ {"Connection", "Upgrade"},
+ {"Sec-WebSocket-Accept", accept},
+ }
+ if protocol then
+ res[#res + 1] = {"Sec-WebSocket-Protocol", protocol}
+ end
+
+ return res
+end
+return exports
diff --git a/src/emu/debug/debugcpu.cpp b/src/emu/debug/debugcpu.cpp
index 293657b1f45..c4686c5566f 100644
--- a/src/emu/debug/debugcpu.cpp
+++ b/src/emu/debug/debugcpu.cpp
@@ -19,6 +19,7 @@
#include "uiinput.h"
#include "xmlfile.h"
#include "coreutil.h"
+#include "luaengine.h"
#include <ctype.h>
@@ -1928,6 +1929,8 @@ void device_debug::instruction_hook(offs_t curpc)
// flush any pending updates before waiting again
machine.debug_view().flush_osd_updates();
+ machine.manager().lua()->periodic_check();
+
// clear the memory modified flag and wait
global->memory_modified = false;
if (machine.debug_flags & DEBUG_FLAG_OSD_ENABLED)
diff --git a/src/emu/emuopts.cpp b/src/emu/emuopts.cpp
index f5d014750b2..97e107ebb49 100644
--- a/src/emu/emuopts.cpp
+++ b/src/emu/emuopts.cpp
@@ -43,6 +43,7 @@ const options_entry emu_options::s_option_entries[] =
{ OPTION_FONTPATH, ".", OPTION_STRING, "path to font files" },
{ OPTION_CHEATPATH, "cheat", OPTION_STRING, "path to cheat files" },
{ OPTION_CROSSHAIRPATH, "crosshair", OPTION_STRING, "path to crosshair files" },
+ { OPTION_PLUGINSPATH, "plugins", OPTION_STRING, "path to plugin files" },
// output directory options
{ nullptr, nullptr, OPTION_HEADER, "CORE OUTPUT DIRECTORY OPTIONS" },
diff --git a/src/emu/emuopts.h b/src/emu/emuopts.h
index 916a0f263f5..c8bbc58435a 100644
--- a/src/emu/emuopts.h
+++ b/src/emu/emuopts.h
@@ -56,6 +56,7 @@ enum
#define OPTION_FONTPATH "fontpath"
#define OPTION_CHEATPATH "cheatpath"
#define OPTION_CROSSHAIRPATH "crosshairpath"
+#define OPTION_PLUGINSPATH "pluginspath"
// core directory options
#define OPTION_CFG_DIRECTORY "cfg_directory"
@@ -231,6 +232,7 @@ public:
const char *font_path() const { return value(OPTION_FONTPATH); }
const char *cheat_path() const { return value(OPTION_CHEATPATH); }
const char *crosshair_path() const { return value(OPTION_CROSSHAIRPATH); }
+ const char *plugins_path() const { return value(OPTION_PLUGINSPATH); }
// core directory options
const char *cfg_directory() const { return value(OPTION_CFG_DIRECTORY); }
diff --git a/src/emu/machine.cpp b/src/emu/machine.cpp
index 7c59ef69a2d..5119dec397c 100644
--- a/src/emu/machine.cpp
+++ b/src/emu/machine.cpp
@@ -387,8 +387,10 @@ int running_machine::run(bool firstrun)
// execute CPUs if not paused
if (!m_paused)
+ {
m_scheduler.timeslice();
-
+ manager().lua()->periodic_check();
+ }
// otherwise, just pump video updates through
else
m_video->frame_update();
diff --git a/src/emu/mame.cpp b/src/emu/mame.cpp
index f17c04e3b68..55fc78928f8 100644
--- a/src/emu/mame.cpp
+++ b/src/emu/mame.cpp
@@ -167,6 +167,15 @@ int machine_manager::execute()
int error = MAMERR_NONE;
m_lua->initialize();
+ {
+ emu_file file(options().plugins_path(), OPEN_FLAG_READ);
+ file_error filerr = file.open("boot.lua");
+ if (filerr == FILERR_NONE)
+ {
+ m_lua->load_script(file.fullpath());
+ }
+ }
+
if (m_options.console()) {
m_lua->start_console();
}