pax_global_header00006660000000000000000000000064125324715220014515gustar00rootroot0000000000000052 comment=31cb1367084086579079e384ccfa6a40bf63cea7 mediator_lua-1.1.2-0/000077500000000000000000000000001253247152200143205ustar00rootroot00000000000000mediator_lua-1.1.2-0/.travis.yml000066400000000000000000000015611253247152200164340ustar00rootroot00000000000000language: erlang env: - LUA="Lua 5.1" - LUA="Lua 5.2" - LUA="Lua 5.3" - LUA="LuaJIT 2.0" branches: only: - master before_install: - bash .travis_setup.sh install: - sudo luarocks install luafilesystem - git clone git://github.com/Olivine-Labs/say.git - cd say - sudo luarocks make - cd ../ - git clone git://github.com/Olivine-Labs/luassert.git - cd luassert - sudo luarocks make - cd ../ - git clone git://github.com/Olivine-Labs/busted.git - cd busted - sudo luarocks make busted-scm-0.rockspec - cd ../ - git clone git://github.com/Olivine-Labs/mediator_lua.git - cd mediator_lua - sudo luarocks make - cd ../ script: "busted spec" notifications: webhooks: - http://hollow-mountain-1250.herokuapp.com/hubot/travis recipients: - engineers@olivinelabs.com email: on_success: always on_failure: always mediator_lua-1.1.2-0/.travis_setup.sh000066400000000000000000000020561253247152200174650ustar00rootroot00000000000000# A script for setting up environment for travis-ci testing. # Sets up Lua and Luarocks. # LUA must be "Lua 5.1", "Lua 5.2", "Lua 5.3" or "LuaJIT 2.0". set -e if [ "$LUA" == "LuaJIT 2.0" ]; then wget -O - http://luajit.org/download/LuaJIT-2.0.3.tar.gz | tar xz cd LuaJIT-2.0.3 make && sudo make install INSTALL_TSYMNAME=lua; else if [ "$LUA" == "Lua 5.1" ]; then wget -O - http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz cd lua-5.1.5; elif [ "$LUA" == "Lua 5.2" ]; then wget -O - http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz cd lua-5.2.4; elif [ "$LUA" == "Lua 5.3" ]; then wget -O - http://www.lua.org/ftp/lua-5.3.0.tar.gz | tar xz cd lua-5.3.0; fi sudo make linux install; fi cd .. wget -O - http://luarocks.org/releases/luarocks-2.2.2.tar.gz | tar xz || wget -O - http://keplerproject.github.io/luarocks/releases/luarocks-2.2.2.tar.gz | tar xz cd luarocks-2.2.2 if [ "$LUA" == "LuaJIT 2.0" ]; then ./configure --with-lua-include=/usr/local/include/luajit-2.0; else ./configure; fi make && sudo make install cd .. mediator_lua-1.1.2-0/README.md000066400000000000000000000131021253247152200155740ustar00rootroot00000000000000mediator\_lua =========== Version 1.0 For more information, please see [View the project on Github](https://github.com/OlivineLabs/mediator_lua) [View the documentation](http://olivinelabs.com/mediator_lua) If you have [luarocks](http://luarocks.org), install it with `luarocks install mediator_lua`. If you don't, get it. If you really don't want to, just copy mediator.lua from the [Git repository](https://github.com/OlivineLabs/mediator_lua). A utility class to help you manage events. ------------------------------------------ mediator\_lua is a simple class that allows you to listen to events by subscribing to and sending data to channels. Its purpose is to help you decouple code where you might otherwise have functions calling functions calling functions, and instead simply call `mediator.publish("chat", { message = "hi" })` Why? ---- My specific use case: manage HTTP routes called in OpenResty. There's an excellent article that talks about the Mediator pattern (in Javascript) in more in detail by [Addy Osmani](http://addyosmani.com/largescalejavascript/#mediatorpattern) (that made me go back and refactor this code a bit.) Usage ----- You can register events with the mediator two ways: using channels, or with a *predicate* to perform more complex matching (a predicate is a function that returns a true/false value that determines if mediator should run the callback.) Instantiate a new mediator, and then you can being subscribing, removing, and publishing. Example: ```lua Mediator = require "mediator_lua" mediator = Mediator() -- instantiate a new mediator mediator:publish(channel, ) mediator:remove() ``` Subscription signature: ```lua (channel, callback, , ); ``` Callback signature: ```lua function(, channel); ``` Mediator:subscribe `options` (all are optional; default is empty): ```lua { predicate = function(arg1, arg2) return arg1 == arg2 end priority = 0|1|... (array index; max of callback array length, min of 0) } ``` When you call `subscribe`, you get a `subscriber` object back that you can use to update and change options. It looks like: ```lua { id, -- unique identifier fn, -- function you passed in options, -- options context, -- context for fn to be called within channel, -- provides a pointer back to its channel update(options) -- function that accepts { fn, options, context } } ``` Examples: ```lua Mediator = require("mediator_lua") local mediator = Mediator() -- Print data when the "message" channel is published to -- Subscribe returns a "Subscriber" object mediator:subscribe({ "message" }, function(data) print(data) end); mediator:publish({ "message" }, "Hello, world"); >> Hello, world -- Print the message when the predicate function returns true local predicate = function(data) return data.From == "Jack" end mediator.Subscribe({ "channel" }, function(data) print(data.Message) end, { predicate = predicate }); mediator.Publish({ "channel" }, { Message = "Hey!", From = "Jack" }) mediator.Publish({ "channel" }, { Message = "Hey!", From = "Drew" }) >> Hey! ``` You can remove events by passing in a type or predicate, and optionally the function to remove. ```lua -- removes all methods bound to a channel mediator:remove({ "channel" }) -- unregisters MethodFN, a named function we defined elsewhere, from "channel" mediator:remove({ "channel" }, MethodFN) ``` You can call the registered functions with the `publish` method, which accepts an args array: ```lua mediator:publish({ "channel" }, "argument", "another one", { etc: true }); # args go on forever ``` You can namespace your subscribing / removing / publishing. This will recurisevely call children, and also subscribers to direct parents. ```lua mediator:subscribe({ "application:chat:receiveMessage" }, function(data){ ... }) -- will recursively call anything in the appllication:chat:receiveMessage namespace -- will also call thins directly subscribed to application and application:chat, -- but not their children mediator:publish({ "application", "chat", "receiveMessage" }, "Jack Lawson", "Hey") -- will recursively remove everything under application:chat mediator:remove({ "application", "chat" }) ``` You can update Subscriber priority: ```lua local sub = mediator:subscribe({ "application", "chat" }, function(data){ ... }) local sub2 = mediator:subscribe({ "application", "chat" }, function(data){ ... }) -- have sub2 executed first mediator.GetChannel({ "application", "chat" }).SetPriority(sub2.id, 0); ``` You can update Subscriber callback, context, and/or options: ```lua sub:update({ fn: ..., context = { }, options = { ... }) ``` You can stop the chain of execution by calling channel:stopPropagation() ```lua -- for example, let's not post the message if the `from` and `to` are the same mediator.Subscribe({ "application", "chat" }, function(data, channel) -- throw an error message or something channel:stopPropagation() end, options = { predicate = function(data) return data.From == data.To end, priority = 0 }) ``` Testing ------- Uses [lunit](http://www.nessie.de/mroth/lunit/) for testing; you can install it through [luarocks](http://luarocks.org). Contributing ------------ Build stuff, run the tests, then submit a pull request with comments and a description of what you've done, and why. License ------- This code and its accompanying README and are [MIT licensed](http://www.opensource.org/licenses/mit-license.php). In Closing ---------- Have fun, and please submit suggestions and improvements! You can leave any issues here, or contact me on Twitter (@ajacksified). mediator_lua-1.1.2-0/mediator_lua-1.1.2-0.rockspec000066400000000000000000000013271253247152200213150ustar00rootroot00000000000000package = "mediator_lua" version = "1.1.2-0" source = { url = "https://github.com/Olivine-Labs/mediator_lua/archive/v1.1.2-0.tar.gz", dir = "mediator_lua-1.1.2-0" } description = { summary = "Event handling through channels", detailed = [[ mediator_lua allows you to subscribe and publish to a central object so you can decouple function calls in your application. It's as simple as mediator:subscribe("channel", function). Supports namespacing, predicates, and more. ]], homepage = "http://olivinelabs.com/mediator_lua/", license = "MIT " } dependencies = { "lua >= 5.1" } build = { type = "builtin", modules = { mediator = "src/mediator.lua" } } mediator_lua-1.1.2-0/spec/000077500000000000000000000000001253247152200152525ustar00rootroot00000000000000mediator_lua-1.1.2-0/spec/mediator_spec.lua000066400000000000000000000167361253247152200206100ustar00rootroot00000000000000describe("mediator", function() local Mediator = require 'mediator' local c, testfn, testfn2, testfn3 before_each(function() m = Mediator() c = Mediator.Channel("test") testfn = function() end testfn2 = function() end testfn3 = function() end end) after_each(function() m = nil c = nil testfn = nil testfn2 = nil testfn3 = nil end) it("can register subscribers", function() local sub1 = c:addSubscriber(testfn) assert.are.equal(#c.callbacks, 1) assert.are.equal(c.callbacks[1].fn, testfn) end) it("can register lots of subscribers", function() local sub1 = c:addSubscriber(testfn) local sub2 = c:addSubscriber(testfn2) assert.are.equal(#c.callbacks, 2) assert.are.equal(c.callbacks[2].fn, sub2.fn) end) it("can register subscribers with specified priorities", function() local sub1 = c:addSubscriber(testfn) local sub2 = c:addSubscriber(testfn2) local sub3 = c:addSubscriber(testfn3, { priority = 1 }) assert.are.equal(c.callbacks[1].fn, sub3.fn) end) it("can return subscribers", function() local sub1 = c:addSubscriber(testfn) local sub2 = c:addSubscriber(testfn2) gotten = c:getSubscriber(sub1.id) assert.are.equal(gotten.value, sub1) end) it("can change subscriber priority forward after being added", function() local sub1 = c:addSubscriber(testfn) local sub2 = c:addSubscriber(testfn2) c:setPriority(sub2.id, 1) assert.are.equal(c.callbacks[1], sub2) end) it("can change subscriber priority backwards after being added", function() local sub1 = c:addSubscriber(testfn) local sub2 = c:addSubscriber(testfn2) c:setPriority(sub1.id, 2) assert.are.equal(c.callbacks[2], sub1) end) it("can add subchannels", function() c:addChannel("level2") assert.are_not.equal(c.channels["level2"], nil) end) it("can check if a subchannel has been added", function() c:addChannel("level2") assert.is.truthy(c:hasChannel("level2"), true) end) it("can return channels", function() c:addChannel("level2") assert.is_not.equal(c:getChannel("level2"), nil) end) it("can remove subscribers by id", function() local sub1 = c:addSubscriber(testfn) local sub2 = c:addSubscriber(testfn2) c:removeSubscriber(sub2.id) assert.is.equal(c:getSubscriber(sub2.id), nil) end) it("can return a subscriber registered to a subchannel", function() c:addChannel("level2") local sub1 = c.channels["level2"]:addSubscriber(testfn) gotten = c:getSubscriber(sub1.id) assert.are.equal(gotten.value, sub1) end) it("can remove a subscriber registered to a subchannel", function() c:addChannel("level2") local sub1 = c.channels["level2"]:addSubscriber(testfn) c:removeSubscriber(sub1.id) assert.is.equal(c.channels["level2"]:getSubscriber(sub1.id), nil) end) it("can publish to a channel", function() local olddata = { test = false } local data = { test = true } local assertFn = function(data) olddata = data end local sub1 = c:addSubscriber(assertFn) c:publish({}, data) assert.is.truthy(olddata.test) end) it("ignores if you publish to a nonexistant subchannel", function() assert.is_not.error(function() m:publish({ "nope" }, data) end) end) it("ignores if you publish to a nonexistant subchannel with subchannels", function() assert.is_not.error(function() m:publish({ "nope", "wat" }, data) end) end) it("sends all the publish arguments to subscribers", function() local data = { test = true } local arguments local assertFn = function(data, wat, seven) arguments = { data, wat, seven } end local sub1 = c:addSubscriber(assertFn) c:publish({}, "test", data, "wat", "seven") assert.are.equal(#arguments, 3) end) it("can stop propagation", function() local olddata = { test = 0 } local data = { test = 1 } local data2 = { test = 2 } local assertFn = function(data) olddata = data end local assertFn2 = function(data) olddata = data2 end local sub1 = c:addSubscriber(assertFn) local sub2 = c:addSubscriber(assertFn2) c:publish({}, data) assert.are.equal(olddata.test, 1) end) it("publishes to parent channels", function() local olddata = { test = false } local data = { test = true } local assertFn = function(...) olddata = data end c:addChannel("level2") local sub1 = c.channels["level2"]:addSubscriber(assertFn) c.channels["level2"]:publish({}, data) assert.is.truthy(olddata.test) end) it("can return a channel from the mediator level", function() assert.is_not.equal(m:getChannel({"test", "level2"}), nil) assert.are.equal(m:getChannel({"test", "level2"}), m:getChannel({"test"}):getChannel("level2")) end) it("can publish to channels from the mediator level", function() local assertFn = function(data, channel) olddata = data end local s = m:subscribe({"test"}, assertFn) assert.is_not.equal(m:getChannel({ "test" }):getSubscriber(s.id), nil) end) it("publishes to the proper subchannel", function() local a = spy.new(function() end) local b = spy.new(function() end) m:subscribe({ "request", "a" }, a) m:subscribe({ "request", "b" }, b) m:publish({ "request", "a" }) m:publish({ "request", "b" }) assert.spy(a).was.called(1) assert.spy(b).was.called(1) end) it("can return a subscriber at the mediator level", function() local assertFn = function(data, channel) olddata = data end local s = m:subscribe({"test"}, assertFn) assert.is_not.equal(m:getSubscriber(s.id, { "test" }), nil) end) it("can remove a subscriber at the mediator level", function() local assertFn = function(data) olddata = data end local s = m:subscribe({"test"}, assertFn) assert.is_not.equal(m:getSubscriber(s.id, { "test" }), nil) m:removeSubscriber(s.id, {"test"}) assert.are.equal(m:getSubscriber(s.id, { "test" }), nil) end) it("can publish to a subscriber at the mediator level", function() local olddata = "wat" local assertFn = function(data) olddata = data end local s = m:subscribe({"test"}, assertFn) m:publish({ "test" }, "hi") assert.are.equal(olddata, "hi") end) it("can publish a subscriber to all parents at the mediator level", function() local olddata = "wat" local olddata2 = "watwat" local assertFn = function(data) olddata = data return nil, true end local assertFn2 = function(data) olddata2 = data return nil, true end c:addChannel("level2") local s = m:subscribe({ "test", "level2" }, assertFn) local s2 = m:subscribe({ "test" }, assertFn2) m:publish({ "test", "level2" }, "didn't read lol") assert.are.equal(olddata, "didn't read lol") assert.are.equal(olddata2, "didn't read lol") end) it("has predicates", function() local olddata = "wat" local olddata2 = "watwat" local assertFn = function(data) olddata = data end local assertFn2 = function(data) olddata2 = data end local predicate = function() return false end c:addChannel("level2") local s = m:subscribe({"test","level2"}, assertFn) local s2 = m:subscribe({"test"}, assertFn2, { predicate = predicate }) m:publish({"test", "level2"}, "didn't read lol") assert.are.equal(olddata, "didn't read lol") assert.are_not.equal(olddata2, "didn't read lol") end) end) mediator_lua-1.1.2-0/src/000077500000000000000000000000001253247152200151075ustar00rootroot00000000000000mediator_lua-1.1.2-0/src/mediator.lua000066400000000000000000000077621253247152200174320ustar00rootroot00000000000000local function getUniqueId(obj) return tonumber(tostring(obj):match(':%s*[0xX]*(%x+)'), 16) end local function Subscriber(fn, options) local sub = { options = options or {}, fn = fn, channel = nil, update = function(self, options) if options then self.fn = options.fn or self.fn self.options = options.options or self.options end end } sub.id = getUniqueId(sub) return sub end -- Channel class and functions -- local function Channel(namespace, parent) return { stopped = false, namespace = namespace, callbacks = {}, channels = {}, parent = parent, addSubscriber = function(self, fn, options) local callback = Subscriber(fn, options) local priority = (#self.callbacks + 1) options = options or {} if options.priority and options.priority >= 0 and options.priority < priority then priority = options.priority end table.insert(self.callbacks, priority, callback) return callback end, getSubscriber = function(self, id) for i=1, #self.callbacks do local callback = self.callbacks[i] if callback.id == id then return { index = i, value = callback } end end local sub for _, channel in pairs(self.channels) do sub = channel:getSubscriber(id) if sub then break end end return sub end, setPriority = function(self, id, priority) local callback = self:getSubscriber(id) if callback.value then table.remove(self.callbacks, callback.index) table.insert(self.callbacks, priority, callback.value) end end, addChannel = function(self, namespace) self.channels[namespace] = Channel(namespace, self) return self.channels[namespace] end, hasChannel = function(self, namespace) return namespace and self.channels[namespace] and true end, getChannel = function(self, namespace) return self.channels[namespace] or self:addChannel(namespace) end, removeSubscriber = function(self, id) local callback = self:getSubscriber(id) if callback and callback.value then for _, channel in pairs(self.channels) do channel:removeSubscriber(id) end return table.remove(self.callbacks, callback.index) end end, publish = function(self, result, ...) for i = 1, #self.callbacks do local callback = self.callbacks[i] -- if it doesn't have a predicate, or it does and it's true then run it if not callback.options.predicate or callback.options.predicate(...) then -- just take the first result and insert it into the result table local value, continue = callback.fn(...) if value then table.insert(result, value) end if not continue then return result end end end if parent then return parent:publish(result, ...) else return result end end } end -- Mediator class and functions -- local Mediator = setmetatable( { Channel = Channel, Subscriber = Subscriber }, { __call = function (fn, options) return { channel = Channel('root'), getChannel = function(self, channelNamespace) local channel = self.channel for i=1, #channelNamespace do channel = channel:getChannel(channelNamespace[i]) end return channel end, subscribe = function(self, channelNamespace, fn, options) return self:getChannel(channelNamespace):addSubscriber(fn, options) end, getSubscriber = function(self, id, channelNamespace) return self:getChannel(channelNamespace):getSubscriber(id) end, removeSubscriber = function(self, id, channelNamespace) return self:getChannel(channelNamespace):removeSubscriber(id) end, publish = function(self, channelNamespace, ...) return self:getChannel(channelNamespace):publish({}, ...) end } end }) return Mediator