local glue = require'glue' local objc = require'objc' local ffi = require'ffi' local pp = require'pp' io.stdout:setvbuf'no' io.stderr:setvbuf'no' setmetatable(_G, {__index = objc}) --test options local subprocess = false --run each bridgesupport test in a subprocess objc.debug.lazyfuncs = true objc.debug.checkredef = false objc.debug.printcdecl = false objc.debug.loaddeps = false objc.debug.loadtypes = true local bsdir = '_bridgesupport' --path where *.bridgesupport files are on Windows (tree or flat doesn't matter) local luajit = ffi.os == 'Windows' and 'luajit' or './luajit' --luajit command for subprocess running if ffi.os == 'OSX' then objc.load'Foundation' pool = NSAutoreleasePool:new() end --test helpers local function printf(...) print(string.format(...)) end local function hr() print(('-'):rep(80)) end local n = 0 local function genname(prefix) if not prefix then return genname'MyClass' end n = n + 1 return prefix..n end local function errpcall(patt, ...) --pcall that should fail with a specific message local ok, err = pcall(...) assert(not ok) assert(err:find(patt)) end --test namespace local test = {} --{name = test_func} local eyetest = {} --{name = test_func} local demo = {} local tests = {tests = test, ['eye tests'] = eyetest, demos = demo} function test.parsing() assert(stype_ctype('[8^c]', 'arr') == 'char *arr[8]') --array of pointers assert(stype_ctype('^[8c]', 'arr') == 'char (*arr)[8]') --pointer to array assert(stype_ctype('[8[4c]]', 'arr') == 'char arr[8][4]') --multi-dim. array assert(stype_ctype('[3^[8^c]]', 'arr') == 'char *(*arr[3])[8]') assert(stype_ctype('{?="x"i"y"i""(?="ux"I"uy"I)}', nil, 'cdef') == 'struct {\n\tint x;\n\tint y;\n\tunion {\n\t\tunsigned int ux;\n\t\tunsigned int uy;\n\t};\n}' ) --nested unnamed anonymous structs local function mtype_ctype(mtype, ...) return ftype_ctype(mtype_ftype(mtype), ...) end assert(mtype_ctype('@"Class"@:{_NSRect={_NSPoint=ff}{_NSSize=ff}}^{?}^?', 'globalFunction') == 'id globalFunction (id, SEL, struct _NSRect, void *, void *)') --unseparated method args assert(mtype_ctype('{_NSPoint=ff}iii', nil, true) == 'void (*) (int, int, int)') --struct return value not supported assert(mtype_ctype('iii{_NSPoint=ff}ii', nil, true) == 'int (*) (int, int)') --pass-by-value struct not supported, stop at first encounter assert(mtype_ctype('{_NSPoint=ff}ii{_NSPoint=ff}i', nil, true) == 'void (*) (int, int)') --combined case end function eyetest.indent() --_NXEvent (test indent for nested unnamed anonymous structs) print(stype_ctype('{?="type"i"location"{?="x"i"y"i}"time"Q"flags"i"window"I"service_id"Q"ext_pid"i"data"(?="mouse"{?="subx"C"suby"C"eventNum"s"click"i"pressure"C"buttonNumber"C"subType"C"reserved2"C"reserved3"i"tablet"(?="point"{_NXTabletPointData="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s}"proximity"{_NXTabletProximityData="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s})}"mouseMove"{?="dx"i"dy"i"subx"C"suby"C"subType"C"reserved1"C"reserved2"i"tablet"(?="point"{_NXTabletPointData="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s}"proximity"{_NXTabletProximityData="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s})}"key"{?="origCharSet"S"repeat"s"charSet"S"charCode"S"keyCode"S"origCharCode"S"reserved1"i"keyboardType"I"reserved2"i"reserved3"i"reserved4"i"reserved5"[4i]}"tracking"{?="reserved"s"eventNum"s"trackingNum"i"userData"i"reserved1"i"reserved2"i"reserved3"i"reserved4"i"reserved5"i"reserved6"[4i]}"scrollWheel"{?="deltaAxis1"s"deltaAxis2"s"deltaAxis3"s"reserved1"s"fixedDeltaAxis1"i"fixedDeltaAxis2"i"fixedDeltaAxis3"i"pointDeltaAxis1"i"pointDeltaAxis2"i"pointDeltaAxis3"i"reserved8"[4i]}"zoom"{?="deltaAxis1"s"deltaAxis2"s"deltaAxis3"s"reserved1"s"fixedDeltaAxis1"i"fixedDeltaAxis2"i"fixedDeltaAxis3"i"pointDeltaAxis1"i"pointDeltaAxis2"i"pointDeltaAxis3"i"reserved8"[4i]}"compound"{?="reserved"s"subType"s"misc"(?="F"[11f]"L"[11i]"S"[22s]"C"[44c])}"tablet"{?="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s"reserved"[4i]}"proximity"{?="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s"reserved2"[4i]})}', nil, 'cdef')) end function eyetest.tostring() print(objc.NSNumber:numberWithDouble(0)) print(objc.NSString:alloc():initWithUTF8String'') --empty string = NSFConstantString with hi address print(objc.NSString:alloc():initWithUTF8String'asdjfah') --real object end --test parsing of bridgesupport files. --works on Windows too - just copy your bridgesupport files into whatever you set `bsdir` above. function eyetest.bridgesupport(bsfile) local function list_func(cmd) return function() return coroutine.wrap(function() local f = io.popen(cmd) for s in f:lines() do coroutine.yield(s) end f:close() end) end end local bsfiles if ffi.os == 'Windows' then bsfiles = list_func('dir /B /S '..bsdir..'\\*.bridgesupport') elseif ffi.os == 'OSX' then bsfiles = list_func('find /System/Library/frameworks -name \'*.bridgesupport\'') else error'can\'t run on this OS' end local loaded = {} local n = 0 local objc_load = objc.debug.load_framework --keep it, we'll patch it function objc.debug.load_framework(path) --either `name.bridgesupport` or `name.framework` or `name.framework/name` local name if path:match'%.bridgesupport$' then name = path:match'([^/\\]+)%.bridgesupport$' else name = path:match'/([^/]+)%.framework$' or path:match'([^/]+)$' if ffi.os == 'Windows' then path = bsdir..'\\'..name..'.bridgesupport' else path = path .. '/Resources/BridgeSupport/' .. name .. '.bridgesupport' end end if loaded[name] then return end loaded[name] = true if glue.canopen(path) then if ffi.os == 'OSX' then --load the dylib first (needed for function aliases) local dpath = path:gsub('Resources/BridgeSupport/.*$', name) if glue.canopen(dpath) then pcall(ffi.load, dpath, true) end --load the dylib with inlines first (needed for function aliases) local dpath = path:gsub('bridgesupport$', 'dylib') if glue.canopen(dpath) then pcall(ffi.load, dpath, true) end end objc.debug.load_bridgesupport(path) n = n + 1 --print(n, '', name) else print('! not found', name, path) end end local function status() pp('errors', objc.debug.errcount) print('globals: '..objc.debug.cnames.global[1]) print('structs: '..objc.debug.cnames.struct[1]) end if bsfile then objc.debug.load_framework(bsfile) else for bsfile in bsfiles() do if bsfile:match'Python' then print('skipping '..bsfile) --python bridgesupport files are non-standard and deprecated else --print(); print(bsfile); print(('='):rep(80)) if subprocess then os.execute(luajit..' '..arg[0]..' bridgesupport '..bsfile) else objc.debug.load_framework(bsfile) end end end status() end objc.debug.load_framework = objc_load --put it back end function test.selectors() assert(tostring(SEL'se_lec_tor') == 'se:lec:tor') assert(tostring(SEL'se_lec_tor_') == 'se:lec:tor:') assert(tostring(SEL'__se_lec_tor') == '__se:lec:tor') assert(tostring(SEL'__se:lec:tor:') == '__se:lec:tor:') end --class, superclass, metaclass, class protocols function test.class() --arg. checking errpcall('already', class, 'NSObject', 'NSString') errpcall('superclass', class, genname(), 'MyUnknownClass') errpcall('protocol', class, genname(), 'NSObject ') --class overloaded constructors local cls = class('MyClassX', false) --root class assert(classname(cls) == 'MyClassX') assert(not superclass(cls)) --derived class local cls = class(genname(), 'NSArray') assert(isa(cls, 'NSArray')) --derived + conforming local cls = class(genname(), 'NSArray ') assert(isa(cls, 'NSArray')) assert(conforms(cls, 'NSStreamDelegate')) assert(conforms(cls, 'NSLocking')) local t = {0} for proto in protocols(cls) do t[proto:name()] = true t[1] = t[1] + 1 end assert(t[1] == 2) assert(t.NSStreamDelegate) assert(t.NSLocking) --class hierarchy queries assert(superclass(cls) == NSArray) assert(metaclass(cls)) assert(superclass(metaclass(cls)) == metaclass'NSArray') assert(metaclass(superclass(cls)) == metaclass'NSArray') assert(metaclass(metaclass(cls)) == nil) assert(isa(cls, 'NSObject')) assert(ismetaclass(metaclass(cls))) assert(isclass(cls)) assert(not ismetaclass(cls)) assert(not isobj(cls)) assert(isclass(metaclass(cls))) local obj = cls:new() assert(isobj(obj)) assert(not isclass(obj)) end function test.refcount() local cls = class(genname(), 'NSObject') local inst, inst2, inst3 inst = cls:new() assert(inst:retainCount() == 1) inst2 = inst:retain() --same class, new cdata, new reference assert(inst:retainCount() == 2) inst3 = inst:retain() assert(inst:retainCount() == 3) inst3 = nil --release() on gc collectgarbage() assert(inst:retainCount() == 2) inst3 = inst:retain() assert(inst:retainCount() == 3) inst:release() --manual release() assert(inst:retainCount() == 2) inst = nil --object already disowned by inst, refcount should not decrease collectgarbage() assert(inst2:retainCount() == 2) inst, inst2, inst3 = nil collectgarbage() end function test.luavars() local cls = class(genname(), 'NSObject') --class vars cls.myclassvar = 'doh1' assert(cls.myclassvar == 'doh1') --intialized cls.myclassvar = 'doh' assert(cls.myclassvar == 'doh') --updated --inst vars local inst = cls:new() inst.myinstvar = 'DOH1' assert(inst.myinstvar == 'DOH1') --initialized inst.myinstvar = 'DOH' assert(inst.myinstvar == 'DOH') --updated --class vars from instances assert(inst.myclassvar == 'doh') --class vars are readable from instances inst.myclassvar = 'doh2' assert(cls.myclassvar == 'doh2') --and they can be updated from instances assert(inst.myclassvar == 'doh2') --soft ref counting local inst2 = inst:retain() assert(inst.myinstvar == 'DOH') --2 refs inst = nil collectgarbage() assert(inst2.myinstvar == 'DOH') --1 ref inst2:release() --0 refs; instance gone, vars gone (no way to test, memory was freed) assert(cls.myclassvar == 'doh2') --class vars still there local i = 0 function NSObject:myMethod() i = i + 1 end local str = toobj'hello' --create a NSString instance, which is a NSObject str:myMethod() --instance method (str passed as self) objc.NSString:myMethod() --class method (NSString passed as self) assert(i == 2) function NSObject:myMethod() i = i - 1 end --override str:myMethod() --instance method (str passed as self) objc.NSString:myMethod() --class method (NSString passed as self) assert(i == 0) end function test.override() objc.debug.logtopics.addmethod = true local cls = class(genname(), 'NSObject') local metacls = metaclass(cls) local obj = cls:new() local instdesc = 'hello-instance' local classdesc = 'hello-class' function metacls:description() --override the class method return classdesc --note: we can return the string directly. end function cls:description() --override the instance method return instdesc --note: we can return the string directly. end assert(objc.tolua(cls:description()) == classdesc) --class method was overriden assert(objc.tolua(obj:description()) == instdesc) --instance method was overriden and it's different --subclass and test again local cls2 = class(genname(), cls) local metacls2 = metaclass(cls2) local obj2 = cls2:new() function metacls2:description() --override the class method return objc.callsuper(self, 'description'):UTF8String() .. '2' end function cls2:description(callsuper) --override the instance method return objc.callsuper(self, 'description'):UTF8String() .. '2' end assert(objc.tolua(cls2:description()) == classdesc..'2') --class method was overriden assert(objc.tolua(obj2:description()) == instdesc..'2') --instance method was overriden and it's different end function test.ivars() local obj = NSDocInfo:new() if ffi.abi'64bit' then assert(ffi.typeof(obj.time) == ffi.typeof'long long') else assert(type(obj.time) == 'number') end assert(type(obj.mode) == 'number') --unsigned short assert(ffi.typeof(obj.flags) == ffi.typeof(obj.flags)) --anonymous struct (assert that it was cached) obj.time = 123 assert(obj.time == 123) assert(obj.flags.isDir == 0) obj.flags.isDir = 3 --1 bit assert(obj.flags.isDir == 1) --1 bit was set (so this is not a luavar or anything) end test.properties = objc.with_properties(function() --TODO: find another class with r/w properties. NSProgress is not public on 10.7. local pr = NSProgress:progressWithTotalUnitCount(123) assert(pr.totalUnitCount == 123) --as initialized pr.totalUnitCount = 321 --read/write property assert(pr.totalUnitCount == 321) assert(not pcall(function() pr.indeterminate = true end)) --attempt to set read-only property assert(pr.indeterminate == false) end) local timebase, last_time function timediff() objc.load'System' local time time = mach_absolute_time() if not timebase then timebase = ffi.new'mach_timebase_info_data_t' mach_timebase_info(timebase) end local d = tonumber(time - (last_time or 0)) * timebase.numer / timebase.denom / 10^9 last_time = time return d end function test.blocks() --objc.debug.logtopics.block = true local times = 20000 timediff() --take 1: creating blocks in inner loops with automatic memory management of blocks. local s = NSString:alloc():initWithUTF8String'line1\nline2\nline3' for i=1,times do local t = {} --note: the signature of the block arg for enumerateLinesUsingBlock was taken from bridgesupport. s:enumerateLinesUsingBlock(function(line, pstop) t[#t+1] = line:UTF8String() if #t == 2 then --stop at line 2 pstop[0] = 1 end end) assert(#t == 2) assert(t[1] == 'line1') assert(t[2] == 'line2') --note: callbacks are slow, expensive to create, and limited in number. we have to release them often! if i % 200 == 0 then collectgarbage() end end printf('take 1: block in loop (%d times): %4.2fs', times, timediff()) --take 2: creating a single block in the outer loop (we must give its type). local t local blk = toarg(NSString, 'enumerateLinesUsingBlock', 1, function(line, pstop) t[#t+1] = line:UTF8String() if #t == 2 then --stop at line 2 pstop[0] = 1 end end) local s = NSString:alloc():initWithUTF8String'line1\nline2\nline3' for i=1,times do t = {} s:enumerateLinesUsingBlock(blk) assert(#t == 2) assert(t[1] == 'line1') assert(t[2] == 'line2') end printf('take 2: single block (%d times): %4.2fs', times, timediff()) end function test.tolua() local n = toobj(123.5) assert(isa(n, 'NSNumber')) assert(tolua(n) == 123.5) local s = toobj'hello' assert(isa(s, 'NSString')) assert(tolua(s) == 'hello') local a = {1,2,6,7} local t = toobj(a) assert(t:count() == #a) for i=1,#a do assert(t:objectAtIndex(i-1):doubleValue() == a[i]) end a = tolua(t) assert(#a == 4) assert(a[3] == 6) local d = {a = 1, b = 'baz', d = {1,2,3}, [{x=1}] = {y=2}} local t = toobj(d) assert(t:count() == 4) assert(tolua(t:valueForKey(toobj'a')) == d.a) assert(tolua(t:valueForKey(toobj'b')) == d.b) assert(tolua(t:valueForKey(toobj'd'))[2] == 2) end function test.args() local s = NSString:alloc():initWithUTF8String'\xE2\x82\xAC' --euro symbol --return string assert(s:UTF8String() == '\xE2\x82\xAC') --return boolean (doesn't work for methods) assert(s:isAbsolutePath() == false) --return null assert(type(s:cStringUsingEncoding(NSASCIIStringEncoding)) == 'nil') --selector arg assert(s:respondsToSelector'methodForSelector:' == true) --class arg assert(NSArray:isSubclassOfClass'NSObject' == true) assert(NSArray:isSubclassOfClass'XXX' == false) --string arg assert(NSString:alloc():initWithString('hey'):UTF8String() == 'hey') --table arg for array local a = NSArray:alloc():initWithArray{6,25,5} assert(a:objectAtIndex(1):doubleValue() == 25) --table arg for dictionary local d = NSDictionary:alloc():initWithDictionary{a=5,b=7} assert(d:valueForKey('b'):doubleValue() == 7) end function demo.window() objc.load'AppKit' local NSApp = class('NSApp', 'NSApplication ') --we need to add methods to the class before creating any objects! --note: NSApplicationDelegate is an informal protocol brought from bridgesupport. function NSApp:applicationShouldTerminateAfterLastWindowClosed() print'last window closed...' collectgarbage() return true end function NSApp:applicationShouldTerminate() print'terminating...' return true end local app = NSApp:sharedApplication() app:setDelegate(app) app:setActivationPolicy(NSApplicationActivationPolicyRegular) local NSWin = class('NSWin', 'NSWindow ') --we need to add methods to the class before creating any objects! --note: NSWindowDelegate is a formal protocol brought from the runtime. function NSWin:windowWillClose() print'window will close...' end local style = bit.bor( NSTitledWindowMask, NSClosableWindowMask, NSMiniaturizableWindowMask, NSResizableWindowMask) local win = NSWin:alloc():initWithContentRect_styleMask_backing_defer( NSMakeRect(300, 300, 500, 300), style, NSBackingStoreBuffered, false) win:setDelegate(win) win:setTitle"▀▄▀▄▀▄ [ Lua Rulez ] ▄▀▄▀▄▀" app:activateIgnoringOtherApps(true) win:makeKeyAndOrderFront(nil) app:run() end function demo.speech() objc.load'AppKit' local speech = NSSpeechSynthesizer:new() voiceid = NSSpeechSynthesizer:availableVoices():objectAtIndex(11) speech:setVoice(voiceid) speech:startSpeakingString'Calm, fitter, healthier, and more productive; A pig. In a cage. On antibiotics.' while speech:isSpeaking() do os.execute'sleep 1' end end function demo.http() --what a dense word soup just to make a http request objc.load'AppKit' local app = NSApplication:sharedApplication() local post = NSString:stringWithFormat('firstName=%@&lastName=%@&eMail=%@&message=%@', toobj'Dude', toobj'Edud', toobj'x@y.com', toobj'message') local postData = post:dataUsingEncoding(NSUTF8StringEncoding) local postLength = NSString:stringWithFormat('%ld', postData:length()) NSLog('Post data: %@', post) local request = NSMutableURLRequest:new() request:setURL(NSURL:URLWithString'http://posttestserver.com/post.php') request:setHTTPMethod'POST' request:setValue_forHTTPHeaderField(postLength, 'Content-Length') request:setValue_forHTTPHeaderField('application/x-www-form-urlencoded', 'Content-Type') request:setHTTPBody(postData) NSLog('%@', request) local CD = class('ConnDelegate', 'NSObject ') function CD:connection_didReceiveData(conn, data) self.webData:appendData(data) NSLog'Connection received data' end function CD:connection_didReceiveResponse(conn, response) NSLog'Connection received response' NSLog('%@', response:description()) end function CD:connection_didFailWithError(conn, err) NSLog('Connection error: %@', err:localizedDescription()) app:terminate(nil) end function CD:connectionDidFinishLoading(conn) NSLog'Connection finished loading' local html = NSString:alloc():initWithBytes_length_encoding(self.webData:mutableBytes(), self.webData:length(), NSUTF8StringEncoding) NSLog('OUTPUT:\n%@', html) app:terminate(nil) end local cd = ConnDelegate:new() cd.webData = NSMutableData:new() local conn = NSURLConnection:alloc():initWithRequest_delegate_startImmediately(request, cd, false) conn:start() app:run() end function demo.http_gcd() objc.load'AppKit' local app = NSApplication:sharedApplication() local url = NSURL:URLWithString'http://posttestserver.com/post.php' local req = NSURLRequest:requestWithURL(url) local queue = dispatch.main_queue --dispatch.get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) objc.debug.logtopics.block = true local n = 0 local blk = block(function() n = n + 1 print('called', n) local response = ffi.new'id[1]' local err = ffi.new'id[1]' local data = NSURLConnection:sendSynchronousRequest_returningResponse_error(req, response, err) print(tolua(NSString:alloc():initWithBytes_length_encoding( data:mutableBytes(), data:length(), NSUTF8StringEncoding))) if n == 2 then print'---- Done. Hit Ctrl+C twice ----' end end) dispatch.async(queue, blk) --increase refcount dispatch.async(queue, blk) --increase refcount print'queued' blk = nil; collectgarbage() --decrease refcount (stil queued) print'released' app:run() end -- inspection ------------------------------------------------------------------------------------------------------------ local function load_many_frameworks() objc.debug.loaddeps = true for s in string.gmatch([[ AGL AVFoundation AVKit Accelerate Accounts AddressBook AppKit AppKitScripting AppleScriptKit AppleScriptObjC AppleShareClientCore ApplicationServices AudioToolbox AudioUnit AudioVideoBridging Automator CFNetwork CalendarStore Carbon Cocoa Collaboration CoreAudio CoreAudioKit CoreData CoreFoundation CoreGraphics CoreLocation CoreMIDI CoreMedia CoreMediaIO CoreServices CoreText CoreVideo CoreWLAN DVComponentGlue DVDPlayback DirectoryService DiscRecording DiscRecordingUI DiskArbitration DrawSprocket EventKit ExceptionHandling FWAUserLib ForceFeedback Foundation GLKit GLUT GSS GameController GameKit ICADevices IMServicePlugIn IOBluetooth IOBluetoothUI IOKit IOSurface ImageCaptureCore ImageIO InputMethodKit InstallerPlugins InstantMessage JavaFrameEmbedding JavaScriptCore Kerberos LDAP LatentSemanticMapping MapKit MediaAccessibility MediaLibrary MediaToolbox NetFS OSAKit OpenAL OpenCL OpenDirectory OpenGL PCSC PreferencePanes PubSub QTKit Quartz QuartzCore QuickLook SceneKit ScreenSaver Scripting ScriptingBridge Security SecurityFoundation SecurityInterface ServiceManagement Social SpriteKit StoreKit SyncServices System SystemConfiguration TWAIN Tcl Tk VideoDecodeAcceleration VideoToolbox WebKit ]], '([^\n\r]+)') do pcall(objc.load, s) end objc.debug.loaddeps = false end function eyetest.inspect_classes() load_many_frameworks() inspect.classes() end function eyetest.inspect_protocols() load_many_frameworks() inspect.protocols() end function eyetest.inspect_class_properties(cls) load_many_frameworks() inspect.class_properties(cls) end function eyetest.inspect_protocol_properties(proto) load_many_frameworks() inspect.protocol_properties(proto) end local function req(s) return s and s ~= '' and s or nil end function eyetest.inspect_class_methods(cls, inst) load_many_frameworks() inspect.class_methods(req(cls), inst == 'inst') end function eyetest.inspect_protocol_methods(proto, inst, required) load_many_frameworks() inspect.protocol_methods(req(proto), inst == 'inst', required == 'required') end function eyetest.inspect_class_ivars(cls) load_many_frameworks() inspect.class_ivars(req(cls)) end function eyetest.inspect_class(cls) load_many_frameworks() inspect.class(cls) end function eyetest.inspect_protocol(proto) load_many_frameworks() inspect.protocol(proto) end function eyetest.inspect_find(patt) load_many_frameworks() inspect.find(patt) end -------------- local function test_all(tests, ...) for k,v in glue.sortedpairs(tests) do if k ~= 'all' then print(k) hr() tests[k](...) end end end function test.all(...) test_all(test) end --cmdline interface local function run(...) local testname = ... if not testname then print('Usage: '..luajit..' '..arg[0]..' ') for k,t in glue.sortedpairs(tests) do printf('%s:', k) for k in glue.sortedpairs(t) do print('', k) end end else local test = test[testname] or eyetest[testname] or demo[testname] if not test then printf('Invalid test "%s"', tostring(testname)) os.exit(1) end test(select(2, ...)) print'ok' end end run(...)