1*2bc180efSBaptiste Daroussin--[[ 2*2bc180efSBaptiste Daroussin LYAML binding for Lua 5.1, 5.2, 5.3 & 5.4 3*2bc180efSBaptiste Daroussin Copyright (C) 2013-2022 Gary V. Vaughan 4*2bc180efSBaptiste Daroussin]] 5*2bc180efSBaptiste Daroussin 6*2bc180efSBaptiste Daroussindo 7*2bc180efSBaptiste Daroussin local std = require 'specl.std' 8*2bc180efSBaptiste Daroussin local spawn = require 'specl.shell'.spawn 9*2bc180efSBaptiste Daroussin local objdir = spawn('./build-aux/luke --value=objdir').output 10*2bc180efSBaptiste Daroussin 11*2bc180efSBaptiste Daroussin 12*2bc180efSBaptiste Daroussin package.path = std.package.normalize( 13*2bc180efSBaptiste Daroussin './lib/?.lua', 14*2bc180efSBaptiste Daroussin './lib/?/init.lua', 15*2bc180efSBaptiste Daroussin package.path 16*2bc180efSBaptiste Daroussin ) 17*2bc180efSBaptiste Daroussin package.cpath = std.package.normalize( 18*2bc180efSBaptiste Daroussin './' .. objdir:match("^objdir='(.*)'") .. '/?.so', 19*2bc180efSBaptiste Daroussin './' .. objdir:match("^objdir='(.*)'") .. '/?.dll', 20*2bc180efSBaptiste Daroussin package.cpath 21*2bc180efSBaptiste Daroussin ) 22*2bc180efSBaptiste Daroussinend 23*2bc180efSBaptiste Daroussin 24*2bc180efSBaptiste Daroussinlocal hell = require 'specl.shell' 25*2bc180efSBaptiste Daroussin 26*2bc180efSBaptiste Daroussin 27*2bc180efSBaptiste Daroussinyaml = require 'yaml' 28*2bc180efSBaptiste Daroussin 29*2bc180efSBaptiste DaroussinBOM = string.char(254, 255) -- UTF-16 Byte Order Mark 30*2bc180efSBaptiste Daroussin 31*2bc180efSBaptiste Daroussin-- Allow use of bare 'pack' and 'unpack' even in Lua > 5.2. 32*2bc180efSBaptiste Daroussinpack = table.pack or function(...) return {n = select('#', ...), ...} end 33*2bc180efSBaptiste Daroussinunpack = table.unpack or unpack 34*2bc180efSBaptiste Daroussinlist = pack 35*2bc180efSBaptiste Daroussin 36*2bc180efSBaptiste Daroussin 37*2bc180efSBaptiste Daroussinfunction dump(e) 38*2bc180efSBaptiste Daroussin print(std.string.prettytostring(e)) 39*2bc180efSBaptiste Daroussinend 40*2bc180efSBaptiste Daroussin 41*2bc180efSBaptiste Daroussin 42*2bc180efSBaptiste Daroussinfunction github_issue(n) 43*2bc180efSBaptiste Daroussin return 'see http://github.com/gvvaughan/lyaml/issues/' .. tostring(n) 44*2bc180efSBaptiste Daroussinend 45*2bc180efSBaptiste Daroussin 46*2bc180efSBaptiste Daroussin 47*2bc180efSBaptiste Daroussin-- Output a list of event tables to the given emitter. 48*2bc180efSBaptiste Daroussinfunction emitevents(emitter, list) 49*2bc180efSBaptiste Daroussin for _, v in ipairs(list) do 50*2bc180efSBaptiste Daroussin if type(v) == 'string' then 51*2bc180efSBaptiste Daroussin ok, msg = emitter.emit {type=v} 52*2bc180efSBaptiste Daroussin elseif type(v) == 'table' then 53*2bc180efSBaptiste Daroussin ok, msg = emitter.emit(v) 54*2bc180efSBaptiste Daroussin else 55*2bc180efSBaptiste Daroussin error 'expected table or string argument' 56*2bc180efSBaptiste Daroussin end 57*2bc180efSBaptiste Daroussin 58*2bc180efSBaptiste Daroussin if not ok then 59*2bc180efSBaptiste Daroussin error(msg) 60*2bc180efSBaptiste Daroussin elseif ok and msg then 61*2bc180efSBaptiste Daroussin return msg 62*2bc180efSBaptiste Daroussin end 63*2bc180efSBaptiste Daroussin end 64*2bc180efSBaptiste Daroussinend 65*2bc180efSBaptiste Daroussin 66*2bc180efSBaptiste Daroussin 67*2bc180efSBaptiste Daroussin-- Create a new emitter and send STREAM_START, listed events and STREAM_END. 68*2bc180efSBaptiste Daroussinfunction emit(list) 69*2bc180efSBaptiste Daroussin local emitter = yaml.emitter() 70*2bc180efSBaptiste Daroussin emitter.emit {type='STREAM_START'} 71*2bc180efSBaptiste Daroussin emitevents(emitter, list) 72*2bc180efSBaptiste Daroussin local _, msg = emitter.emit {type='STREAM_END'} 73*2bc180efSBaptiste Daroussin return msg 74*2bc180efSBaptiste Daroussinend 75*2bc180efSBaptiste Daroussin 76*2bc180efSBaptiste Daroussin 77*2bc180efSBaptiste Daroussin-- Create a new parser for STR, and consume the first N events. 78*2bc180efSBaptiste Daroussinfunction consume(n, str) 79*2bc180efSBaptiste Daroussin local e = yaml.parser(str) 80*2bc180efSBaptiste Daroussin for n = 1, n do 81*2bc180efSBaptiste Daroussin e() 82*2bc180efSBaptiste Daroussin end 83*2bc180efSBaptiste Daroussin return e 84*2bc180efSBaptiste Daroussinend 85*2bc180efSBaptiste Daroussin 86*2bc180efSBaptiste Daroussin 87*2bc180efSBaptiste Daroussin-- Return a new table with only elements of T that have keys listed 88*2bc180efSBaptiste Daroussin-- in the following arguments. 89*2bc180efSBaptiste Daroussinfunction filter(t, ...) 90*2bc180efSBaptiste Daroussin local u = {} 91*2bc180efSBaptiste Daroussin for _, k in ipairs {...} do 92*2bc180efSBaptiste Daroussin u[k] = t[k] 93*2bc180efSBaptiste Daroussin end 94*2bc180efSBaptiste Daroussin return u 95*2bc180efSBaptiste Daroussinend 96*2bc180efSBaptiste Daroussin 97*2bc180efSBaptiste Daroussin 98*2bc180efSBaptiste Daroussinfunction iscallable(x) 99*2bc180efSBaptiste Daroussin return type(x) == 'function' or type((getmetatable(x) or {}).__call) == 'function' 100*2bc180efSBaptiste Daroussinend 101*2bc180efSBaptiste Daroussin 102*2bc180efSBaptiste Daroussin 103*2bc180efSBaptiste Daroussinlocal function mkscript(code) 104*2bc180efSBaptiste Daroussin local f = os.tmpname() 105*2bc180efSBaptiste Daroussin local h = io.open(f, 'w') 106*2bc180efSBaptiste Daroussin -- TODO: Move this into specl, or expose arguments so that we can 107*2bc180efSBaptiste Daroussin -- turn this on and off based on specl `--coverage` arg. 108*2bc180efSBaptiste Daroussin h:write "pcall(require, 'luacov')" 109*2bc180efSBaptiste Daroussin h:write(code) 110*2bc180efSBaptiste Daroussin h:close() 111*2bc180efSBaptiste Daroussin return f 112*2bc180efSBaptiste Daroussinend 113*2bc180efSBaptiste Daroussin 114*2bc180efSBaptiste Daroussin 115*2bc180efSBaptiste Daroussin-- Allow user override of LUA binary used by hell.spawn, falling 116*2bc180efSBaptiste Daroussin-- back to environment PATH search for 'lua' if nothing else works. 117*2bc180efSBaptiste Daroussinlocal LUA = os.getenv 'LUA' or 'lua' 118*2bc180efSBaptiste Daroussin 119*2bc180efSBaptiste Daroussin 120*2bc180efSBaptiste Daroussin--- Run some Lua code with the given arguments and input. 121*2bc180efSBaptiste Daroussin-- @string code valid Lua code 122*2bc180efSBaptiste Daroussin-- @tparam[opt={}] string|table arg single argument, or table of 123*2bc180efSBaptiste Daroussin-- arguments for the script invocation. 124*2bc180efSBaptiste Daroussin-- @string[opt] stdin standard input contents for the script process 125*2bc180efSBaptiste Daroussin-- @treturn specl.shell.Process|nil status of resulting process if 126*2bc180efSBaptiste Daroussin-- execution was successful, otherwise nil 127*2bc180efSBaptiste Daroussinfunction luaproc(code, arg, stdin) 128*2bc180efSBaptiste Daroussin local f = mkscript(code) 129*2bc180efSBaptiste Daroussin if type(arg) ~= 'table' then arg = {arg} end 130*2bc180efSBaptiste Daroussin local cmd = {LUA, f, unpack(arg)} 131*2bc180efSBaptiste Daroussin -- inject env and stdin keys separately to avoid truncating `...` in 132*2bc180efSBaptiste Daroussin -- cmd constructor 133*2bc180efSBaptiste Daroussin cmd.env = { LUA_PATH=package.path, LUA_INIT='', LUA_INIT_5_2='' } 134*2bc180efSBaptiste Daroussin cmd.stdin = stdin 135*2bc180efSBaptiste Daroussin local proc = hell.spawn(cmd) 136*2bc180efSBaptiste Daroussin os.remove(f) 137*2bc180efSBaptiste Daroussin return proc 138*2bc180efSBaptiste Daroussinend 139*2bc180efSBaptiste Daroussin 140*2bc180efSBaptiste Daroussin 141*2bc180efSBaptiste Daroussinlocal function tabulate_output(code) 142*2bc180efSBaptiste Daroussin local proc = luaproc(code) 143*2bc180efSBaptiste Daroussin if proc.status ~= 0 then return error(proc.errout) end 144*2bc180efSBaptiste Daroussin local r = {} 145*2bc180efSBaptiste Daroussin proc.output:gsub('(%S*)[%s]*', 146*2bc180efSBaptiste Daroussin function(x) 147*2bc180efSBaptiste Daroussin if x ~= '' then r[x] = true end 148*2bc180efSBaptiste Daroussin end) 149*2bc180efSBaptiste Daroussin return r 150*2bc180efSBaptiste Daroussinend 151*2bc180efSBaptiste Daroussin 152*2bc180efSBaptiste Daroussin 153*2bc180efSBaptiste Daroussin--- Show changes to tables wrought by a require statement. 154*2bc180efSBaptiste Daroussin-- There are a few modes to this function, controlled by what named 155*2bc180efSBaptiste Daroussin-- arguments are given. Lists new keys in T1 after `require "import"`: 156*2bc180efSBaptiste Daroussin-- 157*2bc180efSBaptiste Daroussin-- show_apis {added_to=T1, by=import} 158*2bc180efSBaptiste Daroussin-- 159*2bc180efSBaptiste Daroussin-- @tparam table argt one of the combinations above 160*2bc180efSBaptiste Daroussin-- @treturn table a list of keys according to criteria above 161*2bc180efSBaptiste Daroussinfunction show_apis(argt) 162*2bc180efSBaptiste Daroussin return tabulate_output([[ 163*2bc180efSBaptiste Daroussin local before, after = {}, {} 164*2bc180efSBaptiste Daroussin for k in pairs(]] .. argt.added_to .. [[) do 165*2bc180efSBaptiste Daroussin before[k] = true 166*2bc180efSBaptiste Daroussin end 167*2bc180efSBaptiste Daroussin 168*2bc180efSBaptiste Daroussin local M = require ']] .. argt.by .. [[' 169*2bc180efSBaptiste Daroussin for k in pairs(]] .. argt.added_to .. [[) do 170*2bc180efSBaptiste Daroussin after[k] = true 171*2bc180efSBaptiste Daroussin end 172*2bc180efSBaptiste Daroussin 173*2bc180efSBaptiste Daroussin for k in pairs(after) do 174*2bc180efSBaptiste Daroussin if not before[k] then print(k) end 175*2bc180efSBaptiste Daroussin end 176*2bc180efSBaptiste Daroussin ]]) 177*2bc180efSBaptiste Daroussinend 178*2bc180efSBaptiste Daroussin 179*2bc180efSBaptiste Daroussin 180*2bc180efSBaptiste Daroussin 181*2bc180efSBaptiste Daroussin--[[ ========= ]]-- 182*2bc180efSBaptiste Daroussin--[[ Call Spy. ]]-- 183*2bc180efSBaptiste Daroussin--[[ ========= ]]-- 184*2bc180efSBaptiste Daroussin 185*2bc180efSBaptiste Daroussin 186*2bc180efSBaptiste Daroussinspy = function(fn) 187*2bc180efSBaptiste Daroussin return setmetatable({}, { 188*2bc180efSBaptiste Daroussin __call = function(self, ...) 189*2bc180efSBaptiste Daroussin self[#self + 1] = list(...) 190*2bc180efSBaptiste Daroussin return fn(...) 191*2bc180efSBaptiste Daroussin end, 192*2bc180efSBaptiste Daroussin }) 193*2bc180efSBaptiste Daroussinend 194*2bc180efSBaptiste Daroussin 195*2bc180efSBaptiste Daroussin 196*2bc180efSBaptiste Daroussindo 197*2bc180efSBaptiste Daroussin --[[ ================ ]]-- 198*2bc180efSBaptiste Daroussin --[[ Custom matchers. ]]-- 199*2bc180efSBaptiste Daroussin --[[ ================ ]]-- 200*2bc180efSBaptiste Daroussin 201*2bc180efSBaptiste Daroussin local matchers = require 'specl.matchers' 202*2bc180efSBaptiste Daroussin local eqv = require 'specl.std'.operator.eqv 203*2bc180efSBaptiste Daroussin local str = require 'specl.std'.string.tostring 204*2bc180efSBaptiste Daroussin 205*2bc180efSBaptiste Daroussin local Matcher, matchers = matchers.Matcher, matchers.matchers 206*2bc180efSBaptiste Daroussin local concat = table.concat 207*2bc180efSBaptiste Daroussin 208*2bc180efSBaptiste Daroussin 209*2bc180efSBaptiste Daroussin matchers.be_called_with = Matcher { 210*2bc180efSBaptiste Daroussin function(self, actual, expected) 211*2bc180efSBaptiste Daroussin for i,v in ipairs(expected or {}) do 212*2bc180efSBaptiste Daroussin if not eqv(actual[i], v) then 213*2bc180efSBaptiste Daroussin return false 214*2bc180efSBaptiste Daroussin end 215*2bc180efSBaptiste Daroussin end 216*2bc180efSBaptiste Daroussin return true 217*2bc180efSBaptiste Daroussin end, 218*2bc180efSBaptiste Daroussin 219*2bc180efSBaptiste Daroussin actual = 'argmuents', 220*2bc180efSBaptiste Daroussin 221*2bc180efSBaptiste Daroussin format_expect = function(self, expect) 222*2bc180efSBaptiste Daroussin return ' arguments (' .. str(expect) .. '), ' 223*2bc180efSBaptiste Daroussin end, 224*2bc180efSBaptiste Daroussin } 225*2bc180efSBaptiste Daroussin 226*2bc180efSBaptiste Daroussin matchers.be_callable = Matcher { 227*2bc180efSBaptiste Daroussin function(self, actual, _) 228*2bc180efSBaptiste Daroussin return iscallable(actual) 229*2bc180efSBaptiste Daroussin end, 230*2bc180efSBaptiste Daroussin 231*2bc180efSBaptiste Daroussin actual = 'callable', 232*2bc180efSBaptiste Daroussin 233*2bc180efSBaptiste Daroussin format_expect = function(self, expect) 234*2bc180efSBaptiste Daroussin return ' callable, ' 235*2bc180efSBaptiste Daroussin end, 236*2bc180efSBaptiste Daroussin } 237*2bc180efSBaptiste Daroussin 238*2bc180efSBaptiste Daroussin matchers.be_falsey = Matcher { 239*2bc180efSBaptiste Daroussin function(self, actual, _) 240*2bc180efSBaptiste Daroussin return not actual and true or false 241*2bc180efSBaptiste Daroussin end, 242*2bc180efSBaptiste Daroussin 243*2bc180efSBaptiste Daroussin actual = 'falsey', 244*2bc180efSBaptiste Daroussin 245*2bc180efSBaptiste Daroussin format_expect = function(self, expect) 246*2bc180efSBaptiste Daroussin return ' falsey, ' 247*2bc180efSBaptiste Daroussin end, 248*2bc180efSBaptiste Daroussin } 249*2bc180efSBaptiste Daroussin 250*2bc180efSBaptiste Daroussin matchers.be_truthy = Matcher { 251*2bc180efSBaptiste Daroussin function(self, actual, _) 252*2bc180efSBaptiste Daroussin return actual and true or false 253*2bc180efSBaptiste Daroussin end, 254*2bc180efSBaptiste Daroussin 255*2bc180efSBaptiste Daroussin actual = 'truthy', 256*2bc180efSBaptiste Daroussin 257*2bc180efSBaptiste Daroussin format_expect = function(self, expect) 258*2bc180efSBaptiste Daroussin return ' truthy, ' 259*2bc180efSBaptiste Daroussin end, 260*2bc180efSBaptiste Daroussin } 261*2bc180efSBaptiste Daroussin 262*2bc180efSBaptiste Daroussin matchers.have_type = Matcher { 263*2bc180efSBaptiste Daroussin function(self, actual, expected) 264*2bc180efSBaptiste Daroussin return type(actual) == expected or (getmetatable(actual) or {})._type == expected 265*2bc180efSBaptiste Daroussin end, 266*2bc180efSBaptiste Daroussin 267*2bc180efSBaptiste Daroussin actual = 'type', 268*2bc180efSBaptiste Daroussin 269*2bc180efSBaptiste Daroussin format_expect = function(self, expect) 270*2bc180efSBaptiste Daroussin local article = 'a' 271*2bc180efSBaptiste Daroussin if match(expect, '^[aehiou]') then 272*2bc180efSBaptiste Daroussin article = 'an' 273*2bc180efSBaptiste Daroussin end 274*2bc180efSBaptiste Daroussin return concat{' ', article, ' ', expect, ', '} 275*2bc180efSBaptiste Daroussin end 276*2bc180efSBaptiste Daroussin } 277*2bc180efSBaptiste Daroussinend 278