summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/lua-stdlib/spec
diff options
context:
space:
mode:
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-stdlib/spec')
-rw-r--r--Data/BuiltIn/Libraries/lua-stdlib/spec/debug_spec.yaml222
-rw-r--r--Data/BuiltIn/Libraries/lua-stdlib/spec/io_spec.yaml439
-rw-r--r--Data/BuiltIn/Libraries/lua-stdlib/spec/math_spec.yaml99
-rw-r--r--Data/BuiltIn/Libraries/lua-stdlib/spec/package_spec.yaml202
-rw-r--r--Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua416
-rw-r--r--Data/BuiltIn/Libraries/lua-stdlib/spec/std_spec.yaml444
-rw-r--r--Data/BuiltIn/Libraries/lua-stdlib/spec/string_spec.yaml549
-rw-r--r--Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml589
8 files changed, 2960 insertions, 0 deletions
diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/debug_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/debug_spec.yaml
new file mode 100644
index 0000000..5102723
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/debug_spec.yaml
@@ -0,0 +1,222 @@
+# General Lua Libraries for Lua 5.1, 5.2 & 5.3
+# Copyright (C) 2011-2018 stdlib authors
+
+before: |
+ base_module = 'debug'
+ this_module = 'std.debug'
+ global_table = '_G'
+
+ extend_base = {'getfenv', 'setfenv', 'say', 'trace'}
+
+ M = require(this_module)
+
+
+specify std.debug:
+- context when required:
+ - context by name:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by=this_module}).
+ to_equal {}
+ - it does not touch the core debug table:
+ expect(show_apis {added_to=base_module, by=this_module}).
+ to_equal {}
+ - it contains apis from the core debug table:
+ expect(show_apis {from=base_module, not_in=this_module}).
+ to_contain.a_permutation_of(extend_base)
+
+ - context via the std module:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by='std'}).
+ to_equal {}
+ - it does not touch the core debug table:
+ expect(show_apis {added_to=base_module, by='std'}).
+ to_equal {}
+
+
+- describe debug:
+ - it does nothing when std._debug is disabled:
+ expect(luaproc [[
+ require 'std._debug'(false)
+ require 'std.debug'('nothing to see here')
+ ]]).not_to_contain_error 'nothing to see here'
+ - it writes to stderr when std._debug is not set:
+ expect(luaproc [[
+ require 'std.debug'('debugging')
+ ]]).to_contain_error 'debugging'
+ - it writes to stderr when std._debug is enabled:
+ expect(luaproc [[
+ require 'std._debug'(true)
+ require 'std.debug'('debugging')
+ ]]).to_contain_error 'debugging'
+ - it writes to stderr when std._debug.level is not set:
+ expect(luaproc [[
+ require 'std._debug'()
+ require 'std.debug'('debugging')
+ ]]).to_contain_error 'debugging'
+ - it writes to stderr when std._debug.level is specified:
+ expect(luaproc [[
+ require 'std._debug'.level = 0
+ require 'std.debug'('debugging')
+ ]]).to_contain_error 'debugging'
+ expect(luaproc [[
+ require 'std._debug'.level = 1
+ require 'std.debug'('debugging')
+ ]]).to_contain_error 'debugging'
+ expect(luaproc [[
+ require 'std._debug'.level = 2
+ require 'std.debug'('debugging')
+ ]]).to_contain_error 'debugging'
+
+
+- describe say:
+ - it uses normalize.str:
+ expect(luaproc [[require 'std.debug'.say {'debugging'}]]).
+ to_contain_error(require 'std.normalize'.str {'debugging'})
+ - context when std._debug is disabled:
+ - before:
+ preamble = [[
+ require 'std._debug'(false)
+ ]]
+ - it does nothing when message level is not set:
+ expect(luaproc(preamble .. [[
+ require 'std.debug'.say 'nothing to see here'
+ ]])).not_to_contain_error 'nothing to see here'
+ - it does nothing when message is set:
+ for _, level in next, {-999, 0, 1, 2, 999} do
+ expect(luaproc(preamble .. [[
+ require 'std.debug'.say(]] .. level .. [[, 'nothing to see here')
+ ]])).not_to_contain_error 'nothing to see here'
+ end
+ - context when std._debug is not set:
+ - it writes to stderr when message level is not set:
+ expect(luaproc [[
+ require 'std.debug'.say 'debugging'
+ ]]).to_contain_error 'debugging'
+ - it writes to stderr when message level is 1 or lower:
+ for _, level in next, {-999, 0, 1} do
+ expect(luaproc([[
+ require 'std.debug'.say(]] .. level .. [[, 'debugging')
+ ]])).to_contain_error 'debugging'
+ end
+ - it does nothing when message level is 2 or higher:
+ for _, level in next, {2, 999} do
+ expect(luaproc([[
+ require 'std.debug'.say(]] .. level .. [[, 'nothing to see here')
+ ]])).not_to_contain_error 'nothing to see here'
+ end
+ - context when std._debug is enabled:
+ - before:
+ preamble = [[
+ require 'std._debug'(true)
+ ]]
+ - it writes to stderr when message level is not set:
+ expect(luaproc(preamble .. [[
+ require 'std.debug'.say 'debugging'
+ ]])).to_contain_error 'debugging'
+ - it writes to stderr when message level is 1 or lower:
+ for _, level in next, {-999, 0, 1} do
+ expect(luaproc(preamble .. [[
+ require 'std.debug'.say(]] .. level .. [[, 'debugging')
+ ]])).to_contain_error 'debugging'
+ end
+ - it does nothing when message level is 2 or higher:
+ for _, level in next, {2, 999} do
+ expect(luaproc(preamble .. [[
+ require 'std.debug'.say(]] .. level .. [[, 'nothing to see here')
+ ]])).not_to_contain_error 'nothing to see here'
+ end
+ - context when std._debug.level is not set:
+ - it writes to stderr when message level is not set:
+ expect(luaproc [[
+ require 'std.debug'.say 'debugging'
+ ]]).to_contain_error 'debugging'
+ - it writes to stderr when message level is 1 or lower:
+ for _, level in next, {-999, 0, 1} do
+ expect(luaproc([[
+ require 'std.debug'.say(]] .. level .. [[, 'debugging')
+ ]])).to_contain_error 'debugging'
+ end
+ - it does nothing when message level is 2 or higher:
+ for _, level in next, {2, 999} do
+ expect(luaproc([[
+ require 'std.debug'.say(]] .. level .. [[, 'nothing to see here')
+ ]])).not_to_contain_error 'nothing to see here'
+ end
+ - context when std._debug.level is specified:
+ - it writes to stderr when message level is 1 or lower:
+ for _, level in next, {0, 1, 2} do
+ expect(luaproc([[
+ require 'std._debug'.level = ]] .. level .. [[
+ require 'std.debug'.say 'debugging'
+ ]])).to_contain_error 'debugging'
+ end
+ - it does nothing when message level is higher than debug level:
+ expect(luaproc [[
+ require 'std._debug'.level = 2
+ require 'std.debug'.say(3, 'nothing to see here')
+ ]]).not_to_contain_error 'nothing to see here'
+ - it writes to stderr when message level equals debug level:
+ expect(luaproc [[
+ require 'std._debug'.level = 2
+ require 'std.debug'.say(2, 'debugging')
+ ]]).to_contain_error 'debugging'
+ - it writes to stderr when message level is lower than debug level:
+ expect(luaproc [[
+ require 'std._debug'.level = 2
+ require 'std.debug'.say(1, 'debugging')
+ ]]).to_contain_error 'debugging'
+
+
+- describe trace:
+ - before:
+ f = init(M, this_module, 'trace')
+
+ - it does nothing when debug hint is disabled:
+ expect(luaproc [[
+ require 'std._debug'(false)
+ require 'std.debug'
+ os.exit(0)
+ ]]).to_succeed_with ''
+ - it does nothing when debug hint is not set:
+ expect(luaproc [[
+ require 'std.debug'
+ os.exit(0)
+ ]]).to_succeed_with ''
+ - it does nothing when debug hint is enabled:
+ expect(luaproc [[
+ require 'std._debug'(true)
+ require 'std.debug'
+ os.exit(0)
+ ]]).to_succeed_with ''
+ - it enables automatically when std._debug.call is set: |
+ expect(luaproc [[
+ require 'std._debug'.call = true
+ require 'std.debug'
+ os.exit(1)
+ ]]).to_fail_while_containing ':3 call exit'
+ - it is enabled manually with debug.sethook: |
+ expect(luaproc [[
+ local debug = require 'std.debug'
+ debug.sethook(debug.trace, 'cr')
+ os.exit(1)
+ ]]).to_fail_while_containing ':3 call exit'
+ - it writes call trace log to standard error: |
+ expect(luaproc [[
+ local debug = require 'std.debug'
+ debug.sethook(debug.trace, 'cr')
+ os.exit(0)
+ ]]).to_contain_error ':3 call exit'
+ - it traces lua calls: |
+ expect(luaproc [[
+ local debug = require 'std.debug' -- line 1
+ local function incr(i) return i + 1 end -- line 2
+ debug.sethook(debug.trace, 'cr') -- line 3
+ os.exit(incr(41)) -- line 4
+ ]]).to_fail_while_matching '.*:4 call incr <2:.*:4 return incr <2:.*'
+ - it traces C api calls: |
+ expect(luaproc [[
+ local debug = require 'std.debug'
+ local function incr(i) return i + 1 end
+ debug.sethook(debug.trace, 'cr')
+ os.exit(incr(41))
+ ]]).to_fail_while_matching '.*:4 call exit %[C%]%s$'
diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/io_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/io_spec.yaml
new file mode 100644
index 0000000..67b850f
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/io_spec.yaml
@@ -0,0 +1,439 @@
+# General Lua Libraries for Lua 5.1, 5.2 & 5.3
+# Copyright (C) 2011-2018 stdlib authors
+
+before: |
+ base_module = 'io'
+ this_module = 'std.io'
+ global_table = '_G'
+
+ extend_base = {'catdir', 'catfile', 'die', 'dirname',
+ 'process_files', 'readlines', 'shell', 'slurp',
+ 'splitdir', 'warn', 'writelines'}
+
+ dirsep = string.match(package.config, '^([^\n]+)\n')
+
+ M = require(this_module)
+
+
+specify std.io:
+- context when required:
+ - context by name:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by=this_module}).
+ to_equal {}
+ - it does not touch the core io table:
+ expect(show_apis {added_to=base_module, by=this_module}).
+ to_equal {}
+ - it contains apis from the core io table:
+ expect(show_apis {from=base_module, not_in=this_module}).
+ to_contain.a_permutation_of(extend_base)
+
+ - context via the std module:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by='std'}).
+ to_equal {}
+ - it does not touch the core io table:
+ expect(show_apis {added_to=base_module, by='std'}).
+ to_equal {}
+
+
+- describe catdir:
+ - before: |
+ f = M.catdir
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.io.catdir(string*)')
+
+ - it treats initial empty string as root directory:
+ expect(f('')).to_be(dirsep)
+ expect(f('', '')).to_be(dirsep)
+ expect(f('', 'root')).to_be(dirsep .. 'root')
+ - it returns a single argument unchanged:
+ expect(f('hello')).to_be 'hello'
+ - it joins multiple arguments with platform directory separator:
+ expect(f('one', 'two')).to_be('one' .. dirsep .. 'two')
+ expect(f('1', '2', '3', '4', '5')).
+ to_be(table.concat({'1', '2', '3', '4', '5'}, dirsep))
+
+
+- describe catfile:
+ - before:
+ f = M.catfile
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.io.catfile(string*)')
+
+ - it treats initial empty string as root directory:
+ expect(f('', '')).to_be(dirsep)
+ expect(f('', 'root')).to_be(dirsep .. 'root')
+ - it returns a single argument unchanged:
+ expect(f('')).to_be ''
+ expect(f('hello')).to_be 'hello'
+ - it joins multiple arguments with platform directory separator:
+ expect(f('one', 'two')).to_be('one' .. dirsep .. 'two')
+ expect(f('1', '2', '3', '4', '5')).
+ to_be(table.concat({'1', '2', '3', '4', '5'}, dirsep))
+
+
+- describe die:
+ - before: |
+ script = [[require 'std.io'.die "By 'eck!"]]
+
+ f = M.die
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.io.die(string, ?any*)')
+
+ - it outputs a message to stderr: |
+ expect(luaproc(script)).to_fail_while_matching ": By 'eck!\n"
+ - it ignores `prog.line` without `prog.file` or `prog.name`: |
+ script = [[prog = {line=125};]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": By 'eck!\n"
+ - it ignores `opts.line` without `opts.program`: |
+ script = [[opts = {line=99};]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": By 'eck!\n"
+ - it prefixes `prog.name` if any: |
+ script = [[prog = {name='name'};]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": name: By 'eck!\n"
+ - it appends `prog.line` if any, to `prog.name`: |
+ script = [[prog = {line=125, name='name'};]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": name:125: By 'eck!\n"
+ - it prefixes `prog.file` if any: |
+ script = [[prog = {file='file'};]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": file: By 'eck!\n"
+ - it appends `prog.line` if any, to `prog.name`: |
+ script = [[prog = {file='file', line=125};]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": file:125: By 'eck!\n"
+ - it prefers `prog.name` to `prog.file` or `opts.program`: |
+ script = [[
+ prog = {file='file', name='name'}
+ opts = {program='program'}
+ ]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": name: By 'eck!\n"
+ - it appends `prog.line` if any to `prog.name` over anything else: |
+ script = [[
+ prog = {file='file', line=125, name='name'}
+ opts = {line=99, program='program'}
+ ]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": name:125: By 'eck!\n"
+ - it prefers `prog.file` to `opts.program`: |
+ script = [[
+ prog = {file='file'}; opts = {program='program'}
+ ]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": file: By 'eck!\n"
+ - it appends `prog.line` if any to `prog.file` over using `opts`: |
+ script = [[
+ prog = {file='file', line=125}
+ opts = {line=99, program='program'}
+ ]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": file:125: By 'eck!\n"
+ - it prefixes `opts.program` if any: |
+ script = [[opts = {program='program'};]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": program: By 'eck!\n"
+ - it appends `opts.line` if any, to `opts.program`: |
+ script = [[opts = {line=99, program='program'};]] .. script
+ expect(luaproc(script)).to_fail_while_matching ": program:99: By 'eck!\n"
+
+
+- describe dirname:
+ - before:
+ f = M.dirname
+ path = table.concat({'', 'one', 'two', 'three'}, dirsep)
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.io.dirname(string)')
+
+ - it removes final separator and following:
+ expect(f(path)).to_be(table.concat({'', 'one', 'two'}, dirsep))
+
+
+- describe process_files:
+ - before:
+ name = 'Makefile'
+ names = {'LICENSE.md', 'Makefile', 'README.md'}
+ ascript = [[
+ require 'std.io'.process_files(function(a)
+ print(a)
+ end)
+ ]]
+ lscript = [[
+ require 'std.io'.process_files('=print(_1)')
+ ]]
+ iscript = [[
+ require 'std.io'.process_files(function(_, i)
+ print(i)
+ end)
+ ]]
+ catscript = [[
+ require 'std.io'.process_files(function()
+ io.write(io.input():read '*a')
+ end)
+ ]]
+
+ f = M.process_files
+
+ - context with bad arguments: |
+ badargs.diagnose(f, 'std.io.process_files(func)')
+
+ examples {
+ ["it diagnoses non-file 'arg' elements"] = function()
+ expect(luaproc(ascript, 'not-an-existing-file')).to_contain_error.any_of {
+ "cannot open file 'not-an-existing-file'", -- Lua 5.2
+ "bad argument #1 to 'input' (not-an-existing-file:", -- Lua 5.1
+ }
+ end
+ }
+
+ - it defaults to `-` if no arguments were passed:
+ expect(luaproc(ascript)).to_output '-\n'
+ - it iterates over arguments with supplied function:
+ expect(luaproc(ascript, name)).to_output(name .. '\n')
+ expect(luaproc(ascript, names)).
+ to_output(table.concat(names, '\n') .. '\n')
+ - it passes argument numbers to supplied function:
+ expect(luaproc(iscript, names)).to_output '1\n2\n3\n'
+ - it sets each file argument as the default input:
+ expect(luaproc(catscript, name)).to_output(concat_file_content(name))
+ expect(luaproc(catscript, names)).
+ to_output(concat_file_content(unpack(names)))
+ - it processes io.stdin if no arguments were passed:
+ ## FIXME: where does that closing newline come from??
+ expect(luaproc(catscript, nil, 'some\nlines\nof input')).to_output 'some\nlines\nof input\n'
+ - it processes io.stdin for `-` argument:
+ ## FIXME: where does that closing newline come from??
+ expect(luaproc(catscript, '-', 'some\nlines\nof input')).to_output 'some\nlines\nof input\n'
+
+
+- describe readlines:
+ - before: |
+ name = 'Makefile'
+ h = io.open(name)
+ lines = {}
+ for l in h:lines() do
+ lines[#lines + 1] = l
+ end
+ h:close()
+
+ defaultin = io.input()
+
+ f, badarg = init(M, this_module, 'readlines')
+ - after:
+ if io.type(defaultin) ~= 'closed file' then
+ io.input(defaultin)
+ end
+
+ - context with bad arguments: |
+ badargs.diagnose(f, 'std.io.readlines(?file|string)')
+
+ if have_typecheck then
+ examples {
+ ['it diagnoses non-existent file'] = function()
+ expect(f 'not-an-existing-file').
+ to_raise "bad argument #1 to 'std.io.readlines'(" -- system dependent error message
+ end
+ }
+ closed = io.open(name, 'r') closed:close()
+ examples {
+ ['it diagnoses closed file argument'] = function()
+ expect(f(closed)).to_raise(badarg(1, '?file|string', 'closed file'))
+ end
+ }
+ end
+
+ - it closes file handle upon completion:
+ h = io.open(name)
+ expect(io.type(h)).not_to_be 'closed file'
+ f(h)
+ expect(io.type(h)).to_be 'closed file'
+ - it reads lines from an existing named file:
+ expect(f(name)).to_equal(lines)
+ - it reads lines from an open file handle:
+ expect(f(io.open(name))).to_equal(lines)
+ - it reads from default input stream with no arguments:
+ io.input(name)
+ expect(f()).to_equal(lines)
+
+
+- describe shell:
+ - before:
+ f = M.shell
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.io.shell(string)')
+
+ - it returns the output from a shell command string:
+ expect(f [[printf '%s\n' 'foo' 'bar']]).to_be 'foo\nbar\n'
+
+
+- describe slurp:
+ - before: |
+ name = 'Makefile'
+ h = io.open(name)
+ content = h:read '*a'
+ h:close()
+
+ defaultin = io.input()
+ f, badarg = init(M, this_module, 'slurp')
+ - after:
+ if io.type(defaultin) ~= 'closed file' then
+ io.input(defaultin)
+ end
+
+ - context with bad arguments: |
+ badargs.diagnose(f, 'std.io.slurp(?file|string)')
+
+ if have_typecheck then
+ examples {
+ ['it diagnoses non-existent file'] = function()
+ expect(f 'not-an-existing-file').
+ to_raise "bad argument #1 to 'std.io.slurp'(" -- system dependent error message
+ end
+ }
+ closed = io.open(name, 'r') closed:close()
+ examples {
+ ['it diagnoses closed file argument'] = function()
+ expect(f(closed)).to_raise(badarg(1, '?file|string', 'closed file'))
+ end
+ }
+ end
+
+ - it reads content from an existing named file:
+ expect(f(name)).to_be(content)
+ - it reads content from an open file handle:
+ expect(f(io.open(name))).to_be(content)
+ - it closes file handle upon completion:
+ h = io.open(name)
+ expect(io.type(h)).not_to_be 'closed file'
+ f(h)
+ expect(io.type(h)).to_be 'closed file'
+ - it reads from default input stream with no arguments:
+ io.input(name)
+ expect(f()).to_be(content)
+
+
+- describe splitdir:
+ - before:
+ f = M.splitdir
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.io.splitdir(string)')
+
+ - it returns a filename as a one element list:
+ expect(f('hello')).to_equal {'hello'}
+ - it splits root directory in two empty elements:
+ expect(f(dirsep)).to_equal {'', ''}
+ - it returns initial empty string for absolute path:
+ expect(f(dirsep .. 'root')).to_equal {'', 'root'}
+ - it returns multiple components split at platform directory separator:
+ expect(f('one' .. dirsep .. 'two')).to_equal {'one', 'two'}
+ expect(f(table.concat({'1', '2', '3', '4', '5'}, dirsep))).
+ to_equal {'1', '2', '3', '4', '5'}
+
+
+- describe warn:
+ - before:
+ script = [[require 'std.io'.warn 'Ayup!']]
+ f = M.warn
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.io.warn(string, ?any*)')
+
+ - it outputs a message to stderr:
+ expect(luaproc(script)).to_output_error 'Ayup!\n'
+ - it ignores `prog.line` without `prog.file`, `prog.name` or `opts.program`:
+ script = [[prog = {line=125};]] .. script
+ expect(luaproc(script)).to_output_error 'Ayup!\n'
+ - it prefixes `prog.name` if any: |
+ script = [[prog = {name='name'};]] .. script
+ expect(luaproc(script)).to_output_error 'name: Ayup!\n'
+ - it appends `prog.line` if any, to `prog.name`: |
+ script = [[prog = {line=125, name='name'};]] .. script
+ expect(luaproc(script)).to_output_error 'name:125: Ayup!\n'
+ - it prefixes `prog.file` if any: |
+ script = [[prog = {file='file'};]] .. script
+ expect(luaproc(script)).to_output_error 'file: Ayup!\n'
+ - it appends `prog.line` if any, to `prog.name`: |
+ script = [[prog = {file='file', line=125};]] .. script
+ expect(luaproc(script)).to_output_error 'file:125: Ayup!\n'
+ - it prefers `prog.name` to `prog.file` or `opts.program`: |
+ script = [[
+ prog = {file='file', name='name'}
+ opts = {program='program'}
+ ]] .. script
+ expect(luaproc(script)).to_output_error 'name: Ayup!\n'
+ - it appends `prog.line` if any to `prog.name` over anything else: |
+ script = [[
+ prog = {file='file', line=125, name='name'}
+ opts = {line=99, program='program'}
+ ]] .. script
+ expect(luaproc(script)).to_output_error 'name:125: Ayup!\n'
+ - it prefers `prog.file` to `opts.program`: |
+ script = [[
+ prog = {file='file'}; opts = {program='program'}
+ ]] .. script
+ expect(luaproc(script)).to_output_error 'file: Ayup!\n'
+ - it appends `prog.line` if any to `prog.file` over using `opts`: |
+ script = [[
+ prog = {file='file', line=125}
+ opts = {line=99, program='program'}
+ ]] .. script
+ expect(luaproc(script)).to_output_error 'file:125: Ayup!\n'
+ - it prefixes `opts.program` if any: |
+ script = [[opts = {program='program'};]] .. script
+ expect(luaproc(script)).to_output_error 'program: Ayup!\n'
+ - it appends `opts.line` if any, to `opts.program`: |
+ script = [[opts = {line=99, program='program'};]] .. script
+ expect(luaproc(script)).to_output_error 'program:99: Ayup!\n'
+
+
+- describe writelines:
+ - before: |
+ name = os.tmpname()
+ h = io.open(name, 'w')
+ lines = M.readlines(io.open 'Makefile')
+
+ defaultout = io.output()
+ f, badarg = init(M, this_module, 'writelines')
+ - after:
+ if io.type(defaultout) ~= 'closed file' then
+ io.output(defaultout)
+ end
+ h:close()
+ os.remove(name)
+
+ - context with bad arguments:
+ - 'it diagnoses argument #1 type not FILE*, string, number or nil':
+ if have_typecheck then
+ expect(f(false)).to_raise(badarg(1, '?file|string|number', 'boolean'))
+ end
+ - 'it diagnoses argument #2 type not string, number or nil':
+ if have_typecheck then
+ expect(f(1, false)).to_raise(badarg(2, 'string|number', 'boolean'))
+ end
+ - 'it diagnoses argument #3 type not string, number or nil':
+ if have_typecheck then
+ expect(f(1, 2, false)).to_raise(badarg(3, 'string|number', 'boolean'))
+ end
+ - it diagnoses closed file argument: |
+ closed = io.open(name, 'r') closed:close()
+ if have_typecheck then
+ expect(f(closed)).to_raise(badarg(1, '?file|string|number', 'closed file'))
+ end
+
+ - it does not close the file handle upon completion:
+ expect(io.type(h)).not_to_be 'closed file'
+ f(h, 'foo')
+ expect(io.type(h)).not_to_be 'closed file'
+ - it writes lines to an open file handle:
+ f(h, unpack(lines))
+ h:flush()
+ expect(M.readlines(io.open(name))).to_equal(lines)
+ - it accepts number valued arguments:
+ f(h, 1, 2, 3)
+ h:flush()
+ expect(M.readlines(io.open(name))).to_equal {'1', '2', '3'}
+ - it writes to default output stream with non-file first argument:
+ io.output(h)
+ f(unpack(lines))
+ h:flush()
+ expect(M.readlines(io.open(name))).to_equal(lines)
diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/math_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/math_spec.yaml
new file mode 100644
index 0000000..ed08753
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/math_spec.yaml
@@ -0,0 +1,99 @@
+# General Lua Libraries for Lua 5.1, 5.2 & 5.3
+# Copyright (C) 2011-2018 stdlib authors
+
+before:
+ base_module = 'math'
+ this_module = 'std.math'
+ global_table = '_G'
+
+ extend_base = {'floor', 'round'}
+
+ M = require(this_module)
+
+
+specify std.math:
+- context when required:
+ - context by name:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by=this_module}).
+ to_equal {}
+ - it does not touch the core math table:
+ expect(show_apis {added_to=base_module, by=this_module}).
+ to_equal {}
+ - it contains apis from the core math table:
+ expect(show_apis {from=base_module, not_in=this_module}).
+ to_contain.a_permutation_of(extend_base)
+
+ - context via the std module:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by='std'}).
+ to_equal {}
+ - it does not touch the core math table:
+ expect(show_apis {added_to=base_module, by='std'}).
+ to_equal {}
+
+
+- describe floor:
+ - before:
+ f = M.floor
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.math.floor(number, ?int)')
+
+ - it rounds to the nearest smaller integer:
+ expect(f(1.2)).to_be(1)
+ expect(f(1.9)).to_be(1)
+ expect(f(999e-2)).to_be(9)
+ expect(f(999e-3)).to_be(0)
+ - it rounds down to specified number of decimal places:
+ expect(f(1.2345, 0)).to_be(1.0)
+ expect(f(1.2345, 1)).to_be(1.2)
+ expect(f(1.2345, 2)).to_be(1.23)
+ expect(f(9.9999, 2)).to_be(9.99)
+ expect(f(99999e-3, 3)).to_be(99999e-3)
+ expect(f(99999e-4, 3)).to_be(9999e-3)
+ expect(f(99999e-5, 3)).to_be(999e-3)
+
+
+- describe round:
+ - before:
+ f = M.round
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.math.round(number, ?int)')
+
+ - it rounds to the nearest integer:
+ expect(f(1.2)).to_be(1)
+ expect(f(1.9)).to_be(2)
+ expect(f(949e-2)).to_be(9)
+ expect(f(999e-2)).to_be(10)
+ - it rounds to specified number of decimal places:
+ expect(f(1.234, 0)).to_be(1.0)
+ expect(f(5.678, 0)).to_be(6.0)
+ expect(f(1.234, 1)).to_be(1.2)
+ expect(f(5.678, 1)).to_be(5.7)
+ expect(f(1.234, 2)).to_be(1.23)
+ expect(f(5.678, 2)).to_be(5.68)
+ expect(f(9.999, 2)).to_be(10)
+ expect(f(11111e-2, 3)).to_be(11111e-2)
+ expect(f(99999e-2, 3)).to_be(99999e-2)
+ expect(f(11111e-3, 3)).to_be(11111e-3)
+ expect(f(99999e-3, 3)).to_be(99999e-3)
+ expect(f(11111e-4, 3)).to_be(1111e-3)
+ expect(f(99999e-4, 3)).to_be(10)
+ expect(f(99999e-5, 3)).to_be(1)
+ - it rounds negative values correctly:
+ expect(f(-1.234, 0)).to_be(-1.0)
+ expect(f(-5.678, 0)).to_be(-6.0)
+ expect(f(-1.234, 1)).to_be(-1.2)
+ expect(f(-5.678, 1)).to_be(-5.7)
+ expect(f(-1.234, 2)).to_be(-1.23)
+ expect(f(-5.678, 2)).to_be(-5.68)
+ expect(f(-9.999, 2)).to_be(-10)
+ expect(f(-11111e-2, 3)).to_be(-11111e-2)
+ expect(f(-99999e-2, 3)).to_be(-99999e-2)
+ expect(f(-11111e-3, 3)).to_be(-11111e-3)
+ expect(f(-99999e-3, 3)).to_be(-99999e-3)
+ expect(f(-11111e-4, 3)).to_be(-1111e-3)
+ expect(f(-99999e-4, 3)).to_be(-10)
+ expect(f(-99999e-5, 3)).to_be(-1)
diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/package_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/package_spec.yaml
new file mode 100644
index 0000000..23ce961
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/package_spec.yaml
@@ -0,0 +1,202 @@
+# General Lua Libraries for Lua 5.1, 5.2 & 5.3
+# Copyright (C) 2011-2018 stdlib authors
+
+before: |
+ base_module = 'package'
+ this_module = 'std.package'
+ global_table = '_G'
+
+ extend_base = {'find', 'insert', 'mappath', 'normalize', 'remove'}
+
+ M = require(this_module)
+
+ path = M.normalize('begin', 'middle', 'end')
+
+ function catfile(...)
+ return table.concat({...}, M.dirsep)
+ end
+ function catpath(...)
+ return table.concat({...}, M.pathsep)
+ end
+
+
+specify std.package:
+- context when required:
+ - context by name:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by=this_module}).
+ to_equal {}
+ - it does not touch the core package table:
+ expect(show_apis {added_to=base_module, by=this_module}).
+ to_equal {}
+ - it contains apis from the core package table:
+ expect(show_apis {from=base_module, not_in=this_module}).
+ to_contain.a_permutation_of(extend_base)
+
+ - context via the std module:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by='std'}).
+ to_equal {}
+ - it does not touch the core package table:
+ expect(show_apis {added_to=base_module, by='std'}).
+ to_equal {}
+
+
+- describe find:
+ - before: |
+ path = table.concat({'begin', 'm%ddl.', 'end'}, M.pathsep)
+
+ f = M.find
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.package.find(string, string, ?int, ?boolean|:plain)')
+
+ - it returns nil for unmatched element:
+ expect(f(path, 'unmatchable')).to_be(nil)
+ - it returns the element index for a matched element:
+ expect(f(path, 'end')).to_be(3)
+ - it returns the element text for a matched element:
+ i, element = f(path, 'e.*n')
+ expect({i, element}).to_equal {1, 'begin'}
+ - it accepts a search start element argument:
+ i, element = f(path, 'e.*n', 2)
+ expect({i, element}).to_equal {3, 'end'}
+ - it works with plain text search strings:
+ expect(f(path, 'm%ddl.')).to_be(nil)
+ i, element = f(path, '%ddl.', 1, ':plain')
+ expect({i, element}).to_equal {2, 'm%ddl.'}
+
+
+- describe insert:
+ - before:
+ f = M.insert
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.package.insert(string, [int], string)')
+
+ - it appends by default:
+ expect(f(path, 'new')).
+ to_be(M.normalize('begin', 'middle', 'end', 'new'))
+ - it prepends with pos set to 1:
+ expect(f(path, 1, 'new')).
+ to_be(M.normalize('new', 'begin', 'middle', 'end'))
+ - it can insert in the middle too:
+ expect(f(path, 2, 'new')).
+ to_be(M.normalize('begin', 'new', 'middle', 'end'))
+ expect(f(path, 3, 'new')).
+ to_be(M.normalize('begin', 'middle', 'new', 'end'))
+ - it normalizes the returned path:
+ path = table.concat({'begin', 'middle', 'end'}, M.pathsep)
+ expect(f(path, 'new')).
+ to_be(M.normalize('begin', 'middle', 'end', 'new'))
+ expect(f(path, 1, './x/../end')).
+ to_be(M.normalize('end', 'begin', 'middle'))
+
+
+- describe mappath:
+ - before: |
+ expected = require 'std.string'.split(path, M.pathsep)
+
+ f = M.mappath
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.package.mappath(string, function, ?any*)')
+
+ - it calls a function with each path element:
+ t = {}
+ f(path, function(e)
+ t[#t + 1] = e
+ end)
+ expect(t).to_equal(expected)
+ - it passes additional arguments through: |
+ reversed = {}
+ for i = #expected, 1, -1 do
+ table.insert(reversed, expected[i])
+ end
+ t = {}
+ f(path, function(e, pos)
+ table.insert(t, pos, e)
+ end, 1)
+ expect(t).to_equal(reversed)
+
+
+- describe normalize:
+ - before:
+ f = M.normalize
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.package.normalize(string*)')
+
+ - context with a single element:
+ - it strips redundant . directories:
+ expect(f './x/./y/.').to_be(catfile('.', 'x', 'y'))
+ - it strips redundant .. directories:
+ expect(f '../x/../y/z/..').to_be(catfile('..', 'y'))
+ expect(f '../x/../y/z/..').to_be(catfile('..', 'y'))
+ expect(f '../../x/../y/z/..').to_be(catfile('..', '..', 'y'))
+ expect(f '../../x/../y/./..').to_be(catfile('..', '..'))
+ expect(f '../../w/x/../../y/z/..').to_be(catfile('..', '..', 'y'))
+ expect(f '../../w/./../.././z/..').to_be(catfile('..', '..', '..'))
+ - it leaves leading .. directories unmolested:
+ expect(f '../../1').to_be(catfile('..', '..', '1'))
+ expect(f './../../1').to_be(catfile('..', '..', '1'))
+ - it normalizes / to platform dirsep:
+ expect(f '/foo/bar').to_be(catfile('', 'foo', 'bar'))
+ - it normalizes ? to platform pathmark:
+ expect(f '?.lua').
+ to_be(catfile('.', M.pathmark .. '.lua'))
+ - it strips redundant trailing /:
+ expect(f '/foo/bar/').to_be(catfile('', 'foo', 'bar'))
+ - it inserts missing ./ for relative paths:
+ for _, path in ipairs {'x', './x'} do
+ expect(f(path)).to_be(catfile('.', 'x'))
+ end
+ - context with multiple elements:
+ - it strips redundant . directories:
+ expect(f('./x/./y/.', 'x')).
+ to_be(catpath(catfile('.', 'x', 'y'), catfile('.', 'x')))
+ - it strips redundant .. directories:
+ expect(f('../x/../y/z/..', 'x')).
+ to_be(catpath(catfile('..', 'y'), catfile('.', 'x')))
+ - it normalizes / to platform dirsep:
+ expect(f('/foo/bar', 'x')).
+ to_be(catpath(catfile('', 'foo', 'bar'), catfile('.', 'x')))
+ - it normalizes ? to platform pathmark:
+ expect(f('?.lua', 'x')).
+ to_be(catpath(catfile('.', M.pathmark .. '.lua'), catfile('.', 'x')))
+ - it strips redundant trailing /:
+ expect(f('/foo/bar/', 'x')).
+ to_be(catpath(catfile('', 'foo', 'bar'), catfile('.', 'x')))
+ - it inserts missing ./ for relative paths:
+ for _, path in ipairs {'x', './x'} do
+ expect(f(path, 'a')).
+ to_be(catpath(catfile('.', 'x'), catfile('.', 'a')))
+ end
+ - it eliminates all but the first equivalent elements:
+ expect(f(catpath('1', 'x', '2', './x', './2', './x/../x'))).
+ to_be(catpath('./1', './x', './2'))
+
+
+- describe remove:
+ - before:
+ f = M.remove
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.package.remove(string, ?int)')
+
+ - it removes the last item by default:
+ expect(f(path)).to_be(M.normalize('begin', 'middle'))
+ - it pops the first item with pos set to 1:
+ expect(f(path, 1)).to_be(M.normalize('middle', 'end'))
+ - it can remove from the middle too:
+ expect(f(path, 2)).to_be(M.normalize('begin', 'end'))
+ - it does not normalize the returned path:
+ path = table.concat({'begin', 'middle', 'end'}, M.pathsep)
+ expect(f(path)).
+ to_be(table.concat({'begin', 'middle'}, M.pathsep))
+
+
+- it splits package.config up:
+ expect(string.format('%s\n%s\n%s\n%s\n%s\n',
+ M.dirsep, M.pathsep, M.pathmark, M.execdir, M.igmark)
+ ).to_contain(package.config)
diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua b/Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua
new file mode 100644
index 0000000..642712f
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/spec_helper.lua
@@ -0,0 +1,416 @@
+--[[
+ General Lua Libraries for Lua 5.1, 5.2 & 5.3
+ Copyright (C) 2011-2018 stdlib authors
+]]
+
+local typecheck
+have_typecheck, typecheck = pcall(require, 'typecheck')
+
+local inprocess = require 'specl.inprocess'
+local hell = require 'specl.shell'
+local std = require 'specl.std'
+
+badargs = require 'specl.badargs'
+
+
+local top_srcdir = os.getenv 'top_srcdir' or '.'
+local top_builddir = os.getenv 'top_builddir' or '.'
+
+package.path = std.package.normalize(
+ top_builddir .. '/lib/?.lua',
+ top_builddir .. '/lib/?/init.lua',
+ top_srcdir .. '/lib/?.lua',
+ top_srcdir .. '/lib/?/init.lua',
+ package.path
+)
+
+
+-- Allow user override of LUA binary used by hell.spawn, falling
+-- back to environment PATH search for 'lua' if nothing else works.
+local LUA = os.getenv 'LUA' or 'lua'
+
+
+-- Simplified version for specifications, does not support functable
+-- valued __len metamethod, so don't write examples that need that!
+function len(x)
+ local __len = getmetatable(x) or {}
+ if type(__len) == 'function' then
+ return __len(x)
+ end
+ if type(x) ~= 'table' then
+ return #x
+ end
+
+ local n = #x
+ for i = 1, n do
+ if x[i] == nil then
+ return i -1
+ end
+ end
+ return n
+end
+
+
+-- Make sure we have a maxn even when _VERSION ~= 5.1
+-- @fixme remove this when we get unpack from specl.std
+maxn = table.maxn or function(t)
+ local n = 0
+ for k in pairs(t) do
+ if type(k) == 'number' and k > n then
+ n = k
+ end
+ end
+ return n
+end
+
+
+pack = table.pack or function(...)
+ return {n=select('#', ...), ...}
+end
+
+
+-- Take care to always unpack upto the highest numeric index, for
+-- consistency across Lua versions.
+local _unpack = table.unpack or unpack
+
+-- @fixme pick this up from specl.std with the next release
+function unpack(t, i, j)
+ return _unpack(t, tonumber(i) or 1, tonumber(j or t.n or len(t)))
+end
+
+
+-- In case we're not using a bleeding edge release of Specl...
+_diagnose = badargs.diagnose
+badargs.diagnose = function(...)
+ if have_typecheck then
+ return _diagnose(...)
+ end
+end
+
+badargs.result = badargs.result or function(fname, i, want, got)
+ if want == nil then -- numbers only for narg error
+ i, want = i - 1, i
+ end
+
+ if got == nil and type(want) == 'number' then
+ local s = "bad result #%d from '%s'(no more than %d result%s expected, got %d)"
+ return s:format(i + 1, fname, i, i == 1 and '' or 's', want)
+ end
+
+ local function showarg(s)
+ return('|' .. s .. '|'):
+ gsub('|%?', '|nil|'):
+ gsub('|nil|', '|no value|'):
+ gsub('|any|', '|any value|'):
+ gsub('|#', '|non-empty '):
+ gsub('|func|', '|function|'):
+ gsub('|file|', '|FILE*|'):
+ gsub('^|', ''):
+ gsub('|$', ''):
+ gsub('|([^|]+)$', 'or %1'):
+ gsub('|', ', ')
+ end
+
+ return string.format("bad result #%d from '%s'(%s expected, got %s)",
+ i, fname, showarg(want), got or 'no value')
+end
+
+
+-- Wrap up badargs function in a succinct single call.
+function init(M, mname, fname)
+ local name =(mname .. '.' .. fname):gsub('^%.', '')
+ return M[fname],
+ function(...)
+ return badargs.format(name, ...)
+ end,
+ function(...)
+ return badargs.result(name, ...)
+ end
+end
+
+
+-- A copy of base.lua:type, so that an unloadable base.lua doesn't
+-- prevent everything else from working.
+function objtype(o)
+ return(getmetatable(o) or {})._type or io.type(o) or type(o)
+end
+
+
+function nop() end
+
+
+-- Error message specifications use this to shorten argument lists.
+-- Copied from functional.lua to avoid breaking all tests if functional
+-- cannot be loaded correctly.
+function bind(f, fix)
+ return function(...)
+ local arg = {}
+ for i, v in pairs(fix) do
+ arg[i] = v
+ end
+ local i = 1
+ for _, v in pairs {...} do
+ while arg[i] ~= nil do
+ i = i + 1
+ end
+ arg[i] = v
+ end
+ return f(unpack(arg))
+ end
+end
+
+
+local function mkscript(code)
+ local f = os.tmpname()
+ local h = io.open(f, 'w')
+ h:write(code)
+ h:close()
+ return f
+end
+
+
+--- Run some Lua code with the given arguments and input.
+-- @string code valid Lua code
+-- @tparam[opt={}] string|table arg single argument, or table of
+-- arguments for the script invocation.
+-- @string[opt] stdin standard input contents for the script process
+-- @treturn specl.shell.Process|nil status of resulting process if
+-- execution was successful, otherwise nil
+function luaproc(code, arg, stdin)
+ local f = mkscript(code)
+ if type(arg) ~= 'table' then
+ arg = {arg}
+ end
+ local cmd = {LUA, f, unpack(arg)}
+ -- inject env and stdin keys separately to avoid truncating `...` in
+ -- cmd constructor
+ cmd.env = {LUA_PATH=package.path, LUA_INIT='', LUA_INIT_5_2=''}
+ cmd.stdin = stdin
+ local proc = hell.spawn(cmd)
+ os.remove(f)
+ return proc
+end
+
+
+--- Check deprecation output when calling a named function in the given module.
+-- Note that the script fragments passed in *argstr* and *objectinit*
+-- can reference the module table as `M`, and(where it would make sense)
+-- an object prototype as `P` and instance as `obj`.
+-- @param deprecate value of `std._debug.deprecate`
+-- @string module dot delimited module path to load
+-- @string fname name of a function in the table returned by requiring *module*
+-- @param[opt=''] args arguments to pass to *fname* call, must be stringifiable
+-- @string[opt=nil] objectinit object initializer to instantiate an
+-- object for object method deprecation check
+-- @treturn specl.shell.Process|nil status of resulting process if
+-- execution was successful, otherwise nil
+function deprecation(deprecate, module, fname, args, objectinit)
+ args = args or ''
+ local script
+ if objectinit == nil then
+ script = string.format([[
+ require 'std._debug'.deprecate = %s
+ M = require '%s'
+ P = M.prototype
+ print(M.%s(%s))
+ ]], tostring(deprecate), module, fname, tostring(args))
+ else
+ script = string.format([[
+ require 'std._debug'.deprecate = %s
+ local M = require '%s'
+ local P = M.prototype
+ local obj = P(%s)
+ print(obj:%s(%s))
+ ]], tostring(deprecate), module, objectinit, fname, tostring(args))
+ end
+ return luaproc(script)
+end
+
+
+--- Concatenate the contents of listed existing files.
+-- @string ... names of existing files
+-- @treturn string concatenated contents of those files
+function concat_file_content(...)
+ local t = {}
+ for _, name in ipairs {...} do
+ h = io.open(name)
+ t[#t + 1] = h:read '*a'
+ end
+ return table.concat(t)
+end
+
+
+local function tabulate_output(code)
+ local proc = luaproc(code)
+ if proc.status ~= 0 then
+ return error(proc.errout)
+ end
+ local r = {}
+ proc.output:gsub('(%S*)[%s]*',
+ function(x)
+ if x ~= '' then
+ r[x] = true
+ end
+ end)
+ return r
+end
+
+
+--- Show changes to tables wrought by a require statement.
+-- There are a few modes to this function, controlled by what named
+-- arguments are given. Lists new keys in T1 after `require 'import'`:
+--
+-- show_apis {added_to=T1, by=import}
+--
+-- List keys returned from `require 'import'`, which have the same
+-- value in T1:
+--
+-- show_apis {from=T1, used_by=import}
+--
+-- List keys from `require 'import'`, which are also in T1 but with
+-- a different value:
+--
+-- show_apis {from=T1, enhanced_by=import}
+--
+-- List keys from T2, which are also in T1 but with a different value:
+--
+-- show_apis {from=T1, enhanced_in=T2}
+--
+-- @tparam table argt one of the combinations above
+-- @treturn table a list of keys according to criteria above
+function show_apis(argt)
+ local added_to, from, not_in, enhanced_in, enhanced_after, by =
+ argt.added_to, argt.from, argt.not_in, argt.enhanced_in,
+ argt.enhanced_after, argt.by
+
+ if added_to and by then
+ return tabulate_output([[
+ local before, after = {}, {}
+ for k in pairs(]] .. added_to .. [[) do
+ before[k] = true
+ end
+
+ local M = require ']] .. by .. [['
+ for k in pairs(]] .. added_to .. [[) do
+ after[k] = true
+ end
+
+ for k in pairs(after) do
+ if not before[k] then
+ print(k)
+ end
+ end
+ ]])
+
+ elseif from and not_in then
+ return tabulate_output([[
+ local _ENV = require 'std.normalize' {
+ from = ']] .. from .. [[',
+ M = require ']] .. not_in .. [[',
+ }
+
+ for k in pairs(M) do
+ -- M[1] is typically the module namespace name, don't match
+ -- that!
+ if k ~= 1 and from[k] ~= M[k] then
+ print(k)
+ end
+ end
+ ]])
+
+ elseif from and enhanced_in then
+ return tabulate_output([[
+ local _ENV = require 'std.normalize' {
+ from = ']] .. from .. [[',
+ M = require ']] .. enhanced_in .. [[',
+ }
+
+ for k, v in pairs(M) do
+ if from[k] ~= M[k] and from[k] ~= nil then
+ print(k)
+ end
+ end
+ ]])
+
+ elseif from and enhanced_after then
+ return tabulate_output([[
+ local _ENV = require 'std.normalize' {
+ from = ']] .. from .. [[',
+ }
+ local before, after = {}, {}
+ for k, v in pairs(from) do
+ before[k] = v
+ end
+ ]] .. enhanced_after .. [[
+ for k, v in pairs(from) do
+ after[k] = v
+ end
+
+ for k, v in pairs(before) do
+ if after[k] ~= nil and after[k] ~= v then
+ print(k)
+ end
+ end
+ ]])
+ end
+
+ assert(false, 'missing argument to show_apis')
+end
+
+
+-- Stub inprocess.capture if necessary; new in Specl 12.
+capture = inprocess.capture or function(f, arg)
+ return nil, nil, f(unpack(arg or {}))
+end
+
+
+do
+ -- Custom matcher for set size and set membership.
+
+ local util = require 'specl.util'
+ local matchers = require 'specl.matchers'
+
+ local Matcher, matchers, q =
+ matchers.Matcher, matchers.matchers, matchers.stringify
+
+ matchers.have_size = Matcher {
+ function(self, actual, expect)
+ local size = 0
+ for _ in pairs(actual) do
+ size = size + 1
+ end
+ return size == expect
+ end,
+
+ actual = 'table',
+
+ format_expect = function(self, expect)
+ return ' a table containing ' .. expect .. ' elements, '
+ end,
+
+ format_any_of = function(self, alternatives)
+ return ' a table with any of ' ..
+ util.concat(alternatives, util.QUOTED) .. ' elements, '
+ end,
+ }
+
+ matchers.have_member = Matcher {
+ function(self, actual, expect)
+ return actual[expect] ~= nil
+ end,
+
+ actual = 'set',
+
+ format_expect = function(self, expect)
+ return ' a set containing ' .. q(expect) .. ', '
+ end,
+
+ format_any_of = function(self, alternatives)
+ return ' a set containing any of ' ..
+ util.concat(alternatives, util.QUOTED) .. ', '
+ end,
+ }
+
+ -- Alias that doesn't tickle sc_error_message_uppercase.
+ matchers.raise = matchers.error
+end
diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/std_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/std_spec.yaml
new file mode 100644
index 0000000..b747abf
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/std_spec.yaml
@@ -0,0 +1,444 @@
+# General Lua Libraries for Lua 5.1, 5.2 & 5.3
+# Copyright (C) 2011-2018 stdlib authors
+
+before: |
+ this_module = 'std'
+ global_table = '_G'
+
+ exported_apis = {'assert', 'elems', 'eval', 'getmetamethod',
+ 'ielems', 'ipairs', 'npairs', 'pairs',
+ 'require', 'ripairs', 'rnpairs'}
+
+ -- Tables with iterator metamethods used by various examples.
+ __pairs = setmetatable({content='a string'}, {
+ __pairs = function(t)
+ return function(x, n)
+ if n < #x.content then
+ return n+1, string.sub(x.content, n+1, n+1)
+ end
+ end, t, 0
+ end,
+ })
+ __index = setmetatable({content='a string'}, {
+ __index = function(t, n)
+ if n <= #t.content then
+ return t.content:sub(n, n)
+ end
+ end,
+ __len = function(t)
+ return #t.content
+ end,
+ })
+
+ M = require(this_module)
+ M.version = nil -- previous specs may have autoloaded it
+
+
+specify std:
+- context when required:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by=this_module}).
+ to_equal {}
+ - it exports the documented apis:
+ t = {}
+ for k in pairs(M) do
+ t[#t + 1] = k
+ end
+ expect(t).to_contain.a_permutation_of(exported_apis)
+
+- context when lazy loading:
+ - it has no submodules on initial load:
+ for _, v in pairs(M) do
+ expect(type(v)).not_to_be 'table'
+ end
+ - it loads submodules on demand:
+ lazy = M.math
+ expect(lazy).to_be(require 'std.math')
+ - it loads submodule functions on demand:
+ expect(M.math.round(3.141592)).to_be(3)
+
+- describe assert:
+ - before:
+ f = M.assert
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.assert(?any, ?string, ?any*)')
+
+ - context when it does not trigger:
+ - it has a truthy initial argument:
+ expect(f(1)).not_to_raise 'any error'
+ expect(f(true)).not_to_raise 'any error'
+ expect(f 'yes').not_to_raise 'any error'
+ expect(f(false == false)).not_to_raise 'any error'
+ - it returns the initial argument:
+ expect(f(1)).to_be(1)
+ expect(f(true)).to_be(true)
+ expect(f 'yes').to_be 'yes'
+ expect(f(false == false)).to_be(true)
+ - context when it triggers:
+ - it has a falsey initial argument:
+ expect(f()).to_raise()
+ expect(f(false)).to_raise()
+ expect(f(1 == 0)).to_raise()
+ - it throws an optional error string:
+ expect(f(false, 'ah boo')).to_raise 'ah boo'
+ - it plugs specifiers with string.format: |
+ expect(f(nil, '%s %d: %q', 'here', 42, 'a string')).
+ to_raise(string.format('%s %d: %q', 'here', 42, 'a string'))
+
+
+- describe elems:
+ - before:
+ f = M.elems
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.elems(table)')
+
+ - it is an iterator over table values:
+ t = {}
+ for e in f {'foo', bar='baz', 42} do
+ t[#t + 1] = e
+ end
+ expect(t).to_contain.a_permutation_of {'foo', 'baz', 42}
+ - it respects __pairs metamethod: |
+ t = {}
+ for v in f(__pairs) do
+ t[#t + 1] = v
+ end
+ expect(t).
+ to_contain.a_permutation_of {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'}
+ - it works for an empty list:
+ t = {}
+ for e in f {} do
+ t[#t + 1] = e
+ end
+ expect(t).to_equal {}
+
+
+- describe eval:
+ - before:
+ f = M.eval
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.eval(string)')
+
+ - it diagnoses invalid lua:
+ # Some internal error when eval tries to call uncompilable '=' code.
+ expect(f '=').to_raise()
+ - it evaluates a string of lua code:
+ expect(f 'math.min(2, 10)').to_be(math.min(2, 10))
+
+
+- describe getmetamethod:
+ - before:
+ f = M.getmetamethod
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.getmetamethod(?any, string)')
+
+ - context with a table:
+ - before:
+ method = function()
+ return 'called'
+ end
+ functor = setmetatable({}, {__call=method})
+ t = setmetatable({}, {
+ _type='table', _method=method, _functor=functor,
+ })
+ - it returns nil for missing metamethods:
+ expect(f(t, 'not a metamethod on t')).to_be(nil)
+ - it returns nil for non-callable metatable entries:
+ expect(f(t, '_type')).to_be(nil)
+ - it returns a method from the metatable:
+ expect(f(t, '_method')).to_be(method)
+ expect(f(t, '_method')()).to_be 'called'
+ - it returns a functor from the metatable:
+ expect(f(t, '_functor')).to_be(functor)
+ expect(f(t, '_functor')()).to_be 'called'
+
+
+- describe ielems:
+ - before:
+ f = M.ielems
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.ielems(table)')
+
+ - it is an iterator over integer-keyed table values:
+ t = {}
+ for e in f {'foo', 42} do
+ t[#t + 1] = e
+ end
+ expect(t).to_equal {'foo', 42}
+ - it ignores the dictionary part of a table:
+ t = {}
+ for e in f {'foo', 42; bar='baz', qux='quux'} do
+ t[#t + 1] = e
+ end
+ expect(t).to_equal {'foo', 42}
+ - it respects __len metamethod:
+ t = {}
+ for v in f(__index) do
+ t[#t + 1] = v
+ end
+ expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'}
+ - it works for an empty list:
+ t = {}
+ for e in f {} do
+ t[#t + 1] = e
+ end
+ expect(t).to_equal {}
+
+
+- describe ipairs:
+ - before:
+ f = M.ipairs
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.ipairs(table)')
+
+ - it is an iterator over integer-keyed table values:
+ t = {}
+ for i, v in f {'foo', 42} do
+ t[i] = v
+ end
+ expect(t).to_equal {'foo', 42}
+ - it ignores the dictionary part of a table:
+ t = {}
+ for i, v in f {'foo', 42; bar='baz', qux='quux'} do
+ t[i] = v
+ end
+ expect(t).to_equal {'foo', 42}
+ - it respects __len metamethod:
+ t = {}
+ for k, v in f(__index) do
+ t[k] = v
+ end
+ expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'}
+ - it works for an empty list:
+ t = {}
+ for i, v in f {} do
+ t[i] = v
+ end
+ expect(t).to_equal {}
+
+
+- describe npairs:
+ - before:
+ f = M.npairs
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.npairs(table)')
+
+ - it is an iterator over integer-keyed table values:
+ t = {}
+ for i, v in f {'foo', 42, nil, nil, 'five'} do
+ t[i] = v
+ end
+ expect(t).to_equal {'foo', 42, nil, nil, 'five'}
+ - it ignores the dictionary part of a table:
+ t = {}
+ for i, v in f {'foo', 42, nil, nil, 'five'; bar='baz', qux='quux'} do
+ t[i] = v
+ end
+ expect(t).to_equal {'foo', 42, nil, nil, 'five'}
+ - it respects __len metamethod:
+ t = {}
+ for _, v in f(setmetatable({[2]=false}, {__len=function(self) return 4 end})) do
+ t[#t + 1] = tostring(v)
+ end
+ expect(table.concat(t, ',')).to_be 'nil,false,nil,nil'
+ - it works for an empty list:
+ t = {}
+ for i, v in f {} do
+ t[i] = v
+ end
+ expect(t).to_equal {}
+
+
+- describe pairs:
+ - before:
+ f = M.pairs
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.pairs(table)')
+
+ - it is an iterator over all table values:
+ t = {}
+ for k, v in f {'foo', bar='baz', 42} do
+ t[k] = v
+ end
+ expect(t).to_equal {'foo', bar='baz', 42}
+ - it respects __pairs metamethod: |
+ t = {}
+ for k, v in f(__pairs) do
+ t[k] = v
+ end
+ expect(t).
+ to_contain.a_permutation_of {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'}
+ - it works for an empty list:
+ t = {}
+ for k, v in f {} do
+ t[k] = v
+ end
+ expect(t).to_equal {}
+
+
+- describe require:
+ - before:
+ f = M.require
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.require(string, ?string, ?string, ?string)')
+
+ - it diagnoses non-existent module:
+ expect(f('module-not-exists', '', '')).to_raise 'module-not-exists'
+ - it diagnoses module too old:
+ expect(f('std', '9999', '9999')).
+ to_raise "require 'std' with at least version 9999,"
+ - it diagnoses module too new:
+ expect(f('std', '0', '0')).
+ to_raise "require 'std' with version less than 0,"
+ - context when the module version is compatible:
+ - it returns the module table:
+ expect(f('std', '0', '9999')).to_be(require 'std')
+ - it places no upper bound by default:
+ expect(f('std', '0')).to_be(require 'std')
+ - it places no lower bound by default:
+ expect(f 'std').to_be(require 'std')
+ - it uses _VERSION when version field is nil:
+ expect(luaproc [[
+ package.loaded['poop'] = {_VERSION='41.1'}
+ f = require 'std'.require
+ print(f('poop', '41', '9999')._VERSION)
+ ]]).to_succeed_with '41.1\n'
+ - context with semantic versioning:
+ - before:
+ std = require 'std'
+ ver = std.version
+ std.version = '1.2.3'
+ - after:
+ std.version = ver
+ - it diagnoses module too old:
+ expect(f('std', '1.2.4')).
+ to_raise "require 'std' with at least version 1.2.4,"
+ expect(f('std', '1.3')).
+ to_raise "require 'std' with at least version 1.3,"
+ expect(f('std', '2.1.2')).
+ to_raise "require 'std' with at least version 2.1.2,"
+ expect(f('std', '2')).
+ to_raise "require 'std' with at least version 2,"
+ expect(f('std', '1.2.10')).
+ to_raise "require 'std' with at least version 1.2.10,"
+ - it diagnoses module too new:
+ expect(f('std', nil, '1.2.2')).
+ to_raise "require 'std' with version less than 1.2.2,"
+ expect(f('std', nil, '1.1')).
+ to_raise "require 'std' with version less than 1.1,"
+ expect(f('std', nil, '1.1.2')).
+ to_raise "require 'std' with version less than 1.1.2,"
+ expect(f('std', nil, '1')).
+ to_raise "require 'std' with version less than 1,"
+ - it returns modules with version in range:
+ expect(f('std')).to_be(std)
+ expect(f('std', '1')).to_be(std)
+ expect(f('std', '1.2.3')).to_be(std)
+ expect(f('std', nil, '2')).to_be(std)
+ expect(f('std', nil, '1.3')).to_be(std)
+ expect(f('std', nil, '1.2.10')).to_be(std)
+ expect(f('std', '1.2.3', '1.2.4')).to_be(std)
+ - context with several numbers in version string:
+ - before:
+ std = require 'std'
+ ver = std.version
+ std.version = 'standard library for Lua 5.3 / 41.0.0'
+ - after:
+ std.version = ver
+ - it diagnoses module too old:
+ expect(f('std', '42')).to_raise()
+ - it diagnoses module too new:
+ expect(f('std', nil, '40')).to_raise()
+ - it returns modules with version in range:
+ expect(f('std')).to_be(std)
+ expect(f('std', '1')).to_be(std)
+ expect(f('std', '41')).to_be(std)
+ expect(f('std', nil, '42')).to_be(std)
+ expect(f('std', '41', '42')).to_be(std)
+
+
+- describe ripairs:
+ - before:
+ f = M.ripairs
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.ripairs(table)')
+
+ - it returns a function, the table and a number:
+ fn, t, i = f {1, 2, 3}
+ expect({type(fn), t, type(i)}).to_equal {'function', {1, 2, 3}, 'number'}
+ - it iterates over the array part of a table:
+ t, u = {1, 2, 3; a=4, b=5, c=6}, {}
+ for i, v in f(t) do
+ u[i] = v
+ end
+ expect(u).to_equal {1, 2, 3}
+ - it returns elements in reverse order:
+ t, u = {'one', 'two', 'five'}, {}
+ for _, v in f(t) do
+ u[#u + 1] = v
+ end
+ expect(u).to_equal {'five', 'two', 'one'}
+ - it respects __len metamethod:
+ t = {}
+ for i, v in f(__index) do
+ t[i] = v
+ end
+ expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'}
+ t = {}
+ for _, v in f(__index) do
+ t[#t + 1] = v
+ end
+ expect(t).to_equal {'g', 'n', 'i', 'r', 't', 's', ' ', 'a'}
+ - it works with the empty list:
+ t = {}
+ for k, v in f {} do
+ t[k] = v
+ end
+ expect(t).to_equal {}
+
+
+- describe rnpairs:
+ - before:
+ f = M.rnpairs
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.rnpairs(table)')
+
+ - it returns a function, the table and a number:
+ fn, t, i = f {1, 2, nil, nil, 3}
+ expect({type(fn), t, type(i)}).
+ to_equal {'function', {1, 2, nil, nil, 3}, 'number'}
+ - it iterates over the array part of a table:
+ t, u = {1, 2, nil, nil, 3; a=4, b=5, c=6}, {}
+ for i, v in f(t) do
+ u[i] = v
+ end
+ expect(u).to_equal {1, 2, nil, nil, 3}
+ - it returns elements in reverse order:
+ t, u, i = {'one', 'two', nil, nil, 'five'}, {}, 1
+ for _, v in f(t) do
+ u[i], i = v, i + 1
+ end
+ expect(u).to_equal {'five', nil, nil, 'two', 'one'}
+ - it respects __len metamethod:
+ t = {}
+ for _, v in f(setmetatable({[2]=false}, {__len=function(self) return 4 end})) do
+ t[#t + 1] = tostring(v)
+ end
+ expect(table.concat(t, ',')).to_be 'nil,nil,false,nil'
+ - it works with the empty list:
+ t = {}
+ for k, v in f {} do
+ t[k] = v
+ end
+ expect(t).to_equal {}
diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/string_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/string_spec.yaml
new file mode 100644
index 0000000..2fa47f2
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/string_spec.yaml
@@ -0,0 +1,549 @@
+# General Lua Libraries for Lua 5.1, 5.2 & 5.3
+# Copyright (C) 2011-2018 stdlib authors
+
+before:
+ base_module = 'string'
+ this_module = 'std.string'
+ global_table = '_G'
+
+ extend_base = {'__concat', '__index',
+ 'caps', 'chomp', 'escape_pattern', 'escape_shell',
+ 'finds', 'format', 'ltrim',
+ 'numbertosi', 'ordinal_suffix', 'pad',
+ 'prettytostring', 'rtrim', 'split',
+ 'tfind', 'trim', 'wrap'}
+
+ M = require(this_module)
+ getmetatable('').__concat = M.__concat
+ getmetatable('').__index = M.__index
+
+specify std.string:
+- before:
+ subject = 'a string \n\n'
+
+- context when required:
+ - context by name:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by=this_module}).
+ to_equal {}
+ - it does not touch the core string table:
+ expect(show_apis {added_to=base_module, by=this_module}).
+ to_equal {}
+ - it contains apis from the core string table:
+ expect(show_apis {from=base_module, not_in=this_module}).
+ to_contain.a_permutation_of(extend_base)
+
+ - context via the std module:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by='std'}).
+ to_equal {}
+ - it does not touch the core string table:
+ expect(show_apis {added_to=base_module, by='std'}).
+ to_equal {}
+
+- describe ..:
+ - it concatenates string arguments:
+ target = 'a string \n\n another string'
+ expect(subject .. ' another string').to_be(target)
+ - it stringifies non-string arguments:
+ argument = {'a table'}
+ expect(subject .. argument).to_be(subject .. '{1="a table"}')
+ - it stringifies nil arguments:
+ argument = nil
+ expect(subject .. argument).
+ to_be(string.format('%s%s', subject, require 'std.normalize'.str(argument)))
+ - it does not perturb the original subject:
+ original = subject
+ newstring = subject .. ' concatenate something'
+ expect(subject).to_be(original)
+
+
+- describe caps:
+ - before:
+ f = M.caps
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.caps(string)')
+
+ - it capitalises words of a string:
+ target = 'A String \n\n'
+ expect(f(subject)).to_be(target)
+ - it changes only the first letter of each word:
+ expect(f 'a stRiNg').to_be 'A StRiNg'
+ - it is available as a string metamethod:
+ expect(('a stRiNg'):caps()).to_be 'A StRiNg'
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject)
+ expect(subject).to_be(original)
+
+
+- describe chomp:
+ - before:
+ target = 'a string \n'
+ f = M.chomp
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.chomp(string)')
+
+ - it removes a single trailing newline from a string:
+ expect(f(subject)).to_be(target)
+ - it does not change a string with no trailing newline:
+ subject = 'a string '
+ expect(f(subject)).to_be(subject)
+ - it is available as a string metamethod:
+ expect(subject:chomp()).to_be(target)
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject)
+ expect(subject).to_be(original)
+
+
+- describe escape_pattern:
+ - before:
+ magic = {}
+ meta = '^$()%.[]*+-?'
+ for i = 1, string.len(meta) do
+ magic[meta:sub(i, i)] = true
+ end
+ f = M.escape_pattern
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.escape_pattern(string)')
+
+ - context with each printable ASCII char:
+ - before:
+ subject, target = '', ''
+ for c = 32, 126 do
+ s = string.char(c)
+ subject = subject .. s
+ if magic[s] then
+ target = target .. '%'
+ end
+ target = target .. s
+ end
+ - 'it inserts a % before any non-alphanumeric in a string':
+ expect(f(subject)).to_be(target)
+ - it is available as a string metamethod:
+ expect(subject:escape_pattern()).to_be(target)
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject)
+ expect(subject).to_be(original)
+
+
+- describe escape_shell:
+ - before:
+ f = M.escape_shell
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.escape_shell(string)')
+
+ - context with each printable ASCII char:
+ - before:
+ subject, target = '', ''
+ for c = 32, 126 do
+ s = string.char(c)
+ subject = subject .. s
+ if s:match('[][ ()\\\'"]') then
+ target = target .. '\\'
+ end
+ target = target .. s
+ end
+ - 'it inserts a \\ before any shell metacharacters':
+ expect(f(subject)).to_be(target)
+ - it is available as a string metamethod:
+ expect(subject:escape_shell()).to_be(target)
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject)
+ expect(subject).to_be(original)
+ - 'it diagnoses non-string arguments':
+ if typecheck then
+ expect(f()).to_raise('string expected')
+ expect(f {'a table'}).to_raise('string expected')
+ end
+
+
+- describe finds:
+ - before:
+ subject = 'abcd'
+ f = M.finds
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.finds(string, string, ?int, ?boolean|:plain)')
+
+ - context given a complex nested list:
+ - before:
+ target = {{1, 2; capt={'a', 'b'}}, {3, 4; capt={'c', 'd'}}}
+ - it creates a list of pattern captures:
+ expect({f(subject, '(.)(.)')}).to_equal({target})
+ - it is available as a string metamethod:
+ expect({subject:finds('(.)(.)')}).to_equal({target})
+ - it creates an empty list where no captures are matched:
+ target = {}
+ expect({f(subject, '(x)')}).to_equal({target})
+ - it creates an empty list for a pattern without captures:
+ target = {{1, 1; capt={}}}
+ expect({f(subject, 'a')}).to_equal({target})
+ - it starts the search at a specified index into the subject:
+ target = {{8, 9; capt={'a', 'b'}}, {10, 11; capt={'c', 'd'}}}
+ expect({f('garbage' .. subject, '(.)(.)', 8)}).to_equal({target})
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject, '...')
+ expect(subject).to_be(original)
+
+
+- describe format:
+ - before:
+ subject = 'string=%s, number=%d'
+
+ f = M.format
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.format(string, ?any*)')
+
+ - it returns a single argument without attempting formatting:
+ expect(f(subject)).to_be(subject)
+ - it is available as a string metamethod:
+ expect(subject:format()).to_be(subject)
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject)
+ expect(subject).to_be(original)
+
+
+- describe ltrim:
+ - before:
+ subject = ' \t\r\n a short string \t\r\n '
+
+ f = M.ltrim
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.ltrim(string, ?string)')
+
+ - it removes whitespace from the start of a string:
+ target = 'a short string \t\r\n '
+ expect(f(subject)).to_equal(target)
+ - it supports custom removal patterns:
+ target = '\r\n a short string \t\r\n '
+ expect(f(subject, '[ \t\n]+')).to_equal(target)
+ - it is available as a string metamethod:
+ target = '\r\n a short string \t\r\n '
+ expect(subject:ltrim('[ \t\n]+')).to_equal(target)
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject, '%W')
+ expect(subject).to_be(original)
+
+
+- describe numbertosi:
+ - before:
+ f = M.numbertosi
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.numbertosi(number|string)')
+
+ - it returns a number using SI suffixes:
+ target = {'1e-9', '1y', '1z', '1a', '1f', '1p', '1n', '1mu', '1m', '1',
+ '1k', '1M', '1G', '1T', '1P', '1E', '1Z', '1Y', '1e9'}
+ subject = {}
+ for n = -28, 28, 3 do
+ m = 10 *(10 ^ n)
+ table.insert(subject, f(m))
+ end
+ expect(subject).to_equal(target)
+ - it coerces string arguments to a number:
+ expect(f '1000').to_be '1k'
+
+
+- describe ordinal_suffix:
+ - before:
+ f = M.ordinal_suffix
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.ordinal_suffix(int|string)')
+
+ - it returns the English suffix for a number:
+ subject, target = {}, {}
+ for n = -120, 120 do
+ suffix = 'th'
+ m = math.abs(n) % 10
+ if m == 1 and math.abs(n) % 100 ~= 11 then
+ suffix = 'st'
+ elseif m == 2 and math.abs(n) % 100 ~= 12 then
+ suffix = 'nd'
+ elseif m == 3 and math.abs(n) % 100 ~= 13 then
+ suffix = 'rd'
+ end
+ table.insert(target, n .. suffix)
+ table.insert(subject, n .. f(n))
+ end
+ expect(subject).to_equal(target)
+ - it coerces string arguments to a number:
+ expect(f '-91').to_be 'st'
+
+
+- describe pad:
+ - before:
+ width = 20
+
+ f = M.pad
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.pad(string, int, ?string)')
+
+ - context when string is shorter than given width:
+ - before:
+ subject = 'short string'
+ - it right pads a string to the given width with spaces:
+ target = 'short string '
+ expect(f(subject, width)).to_be(target)
+ - it left pads a string to the given negative width with spaces:
+ width = -width
+ target = ' short string'
+ expect(f(subject, width)).to_be(target)
+ - it is available as a string metamethod:
+ target = 'short string '
+ expect(subject:pad(width)).to_be(target)
+
+ - context when string is longer than given width:
+ - before:
+ subject = "a string that's longer than twenty characters"
+ - it truncates a string to the given width:
+ target = "a string that's long"
+ expect(f(subject, width)).to_be(target)
+ - it left pads a string to given width with spaces:
+ width = -width
+ target = 'an twenty characters'
+ expect(f(subject, width)).to_be(target)
+ - it is available as a string metamethod:
+ target = "a string that's long"
+ expect(subject:pad(width)).to_be(target)
+
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject, width)
+ expect(subject).to_be(original)
+
+
+- describe prettytostring:
+ - before:
+ f = M.prettytostring
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.prettytostring(?any, ?string, ?string)')
+
+ - it renders nil exactly like system tostring:
+ expect(f(nil)).to_be(tostring(nil))
+ - it renders booleans exactly like system tostring:
+ expect(f(true)).to_be(tostring(true))
+ expect(f(false)).to_be(tostring(false))
+ - it renders numbers exactly like system tostring:
+ n = 8723643
+ expect(f(n)).to_be(tostring(n))
+ - it renders functions exactly like system tostring:
+ expect(f(f)).to_be(tostring(f))
+ - it renders strings with format '%q' styling:
+ s = 'a string'
+ expect(f(s)).to_be(string.format('%q', s))
+ - it renders empty tables as a pair of braces:
+ expect(f {}).to_be('{\n}')
+ - it renders an array prettily:
+ a = {'one', 'two', 'three'}
+ expect(f(a, '')).
+ to_be '{\n[1] = "one",\n[2] = "two",\n[3] = "three",\n}'
+ - it renders a table prettily:
+ t = {one=true, two=2, three={3}}
+ expect(f(t, '')).
+ to_be '{\none = true,\nthree =\n{\n[1] = 3,\n},\ntwo = 2,\n}'
+ - it renders table keys in table.sort order:
+ t = {one=3, two=5, three=4, four=2, five=1}
+ expect(f(t, '')).
+ to_be '{\nfive = 1,\nfour = 2,\none = 3,\nthree = 4,\ntwo = 5,\n}'
+ - it renders keys with invalid symbol names in long hand:
+ t = {_=0, word=0, ['?']=1, ['a-key']=1, ['[]']=1}
+ expect(f(t, '')).
+ to_be '{\n["?"] = 1,\n["[]"] = 1,\n_ = 0,\n["a-key"] = 1,\nword = 0,\n}'
+
+
+- describe rtrim:
+ - before:
+ subject = ' \t\r\n a short string \t\r\n '
+
+ f = M.rtrim
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.rtrim(string, ?string)')
+
+ - it removes whitespace from the end of a string:
+ target = ' \t\r\n a short string'
+ expect(f(subject)).to_equal(target)
+ - it supports custom removal patterns:
+ target = ' \t\r\n a short string \t\r'
+ expect(f(subject, '[ \t\n]+')).to_equal(target)
+ - it is available as a string metamethod:
+ target = ' \t\r\n a short string \t\r'
+ expect(subject:rtrim('[ \t\n]+')).to_equal(target)
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject, '%W')
+ expect(subject).to_be(original)
+
+
+- describe split:
+ - before:
+ target = {'first', 'the second one', 'final entry'}
+ subject = table.concat(target, ', ')
+
+ f = M.split
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.split(string, ?string)')
+
+ - it falls back to '%s+' when no pattern is given:
+ expect(f(subject)).
+ to_equal {'first,', 'the', 'second', 'one,', 'final', 'entry'}
+ - it returns a one-element list for an empty string:
+ expect(f('', ', ')).to_equal {''}
+ - it makes a table of substrings delimited by a separator:
+ expect(f(subject, ', ')).to_equal(target)
+ - it returns n+1 elements for n separators:
+ expect(f(subject, 'zero')).to_have_size(1)
+ expect(f(subject, 'c')).to_have_size(2)
+ expect(f(subject, 's')).to_have_size(3)
+ expect(f(subject, 't')).to_have_size(4)
+ expect(f(subject, 'e')).to_have_size(5)
+ - it returns an empty string element for consecutive separators:
+ expect(f('xyzyzxy', 'yz')).to_equal {'x', '', 'xy'}
+ - it returns an empty string element when starting with separator:
+ expect(f('xyzyzxy', 'xyz')).to_equal {'', 'yzxy'}
+ - it returns an empty string element when ending with separator:
+ expect(f('xyzyzxy', 'zxy')).to_equal {'xyzy', ''}
+ - it returns a table of 1-character strings for '' separator:
+ expect(f('abcdef', '')).to_equal {'', 'a', 'b', 'c', 'd', 'e', 'f', ''}
+ - it is available as a string metamethod:
+ expect(subject:split ', ').to_equal(target)
+ expect(('/foo/bar/baz.quux'):split '/').
+ to_equal {'', 'foo', 'bar', 'baz.quux'}
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject, 'e')
+ expect(subject).to_be(original)
+ - it takes a Lua pattern as a separator:
+ expect(f(subject, '%s+')).
+ to_equal {'first,', 'the', 'second', 'one,', 'final', 'entry'}
+
+
+- describe tfind:
+ - before:
+ subject = 'abc'
+
+ f = M.tfind
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.tfind(string, string, ?int, ?boolean|:plain)')
+
+ - it creates a list of pattern captures:
+ target = {1, 3, {'a', 'b', 'c'}}
+ expect({f(subject, '(.)(.)(.)')}).to_equal(target)
+ - it creates an empty list where no captures are matched:
+ target = {nil, nil, {}}
+ expect({f(subject, '(x)(y)(z)')}).to_equal(target)
+ - it creates an empty list for a pattern without captures:
+ target = {1, 1, {}}
+ expect({f(subject, 'a')}).to_equal(target)
+ - it starts the search at a specified index into the subject:
+ target = {8, 10, {'a', 'b', 'c'}}
+ expect({f('garbage' .. subject, '(.)(.)(.)', 8)}).to_equal(target)
+ - it is available as a string metamethod:
+ target = {8, 10, {'a', 'b', 'c'}}
+ expect({('garbage' .. subject):tfind('(.)(.)(.)', 8)}).to_equal(target)
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject, '...')
+ expect(subject).to_be(original)
+
+
+- describe trim:
+ - before:
+ subject = ' \t\r\n a short string \t\r\n '
+
+ f = M.trim
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.trim(string, ?string)')
+
+ - it removes whitespace from each end of a string:
+ target = 'a short string'
+ expect(f(subject)).to_equal(target)
+ - it supports custom removal patterns:
+ target = '\r\n a short string \t\r'
+ expect(f(subject, '[ \t\n]+')).to_equal(target)
+ - it is available as a string metamethod:
+ target = '\r\n a short string \t\r'
+ expect(subject:trim('[ \t\n]+')).to_equal(target)
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject, '%W')
+ expect(subject).to_be(original)
+
+
+- describe wrap:
+ - before:
+ subject = 'This is a collection of Lua libraries for Lua 5.1 ' ..
+ 'and 5.2. The libraries are copyright by their authors 2000' ..
+ '-2015 (see the AUTHORS file for details), and released und' ..
+ 'er the MIT license (the same license as Lua itself). There' ..
+ ' is no warranty.'
+
+ f = M.wrap
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.string.wrap(string, ?int, ?int, ?int)')
+
+ - it inserts newlines to wrap a string:
+ target = 'This is a collection of Lua libraries for Lua 5.1 a' ..
+ 'nd 5.2. The libraries are\ncopyright by their authors 2000' ..
+ '-2015 (see the AUTHORS file for details), and\nreleased un' ..
+ 'der the MIT license (the same license as Lua itself). Ther' ..
+ 'e is no\nwarranty.'
+ expect(f(subject)).to_be(target)
+ - it honours a column width parameter:
+ target = 'This is a collection of Lua libraries for Lua 5.1 a' ..
+ 'nd 5.2. The libraries\nare copyright by their authors 2000' ..
+ '-2015 (see the AUTHORS file for\ndetails), and released un' ..
+ 'der the MIT license (the same license as Lua\nitself). The' ..
+ 're is no warranty.'
+ expect(f(subject, 72)).to_be(target)
+ - it supports indenting by a fixed number of columns:
+ target = ' This is a collection of Lua libraries for L' ..
+ 'ua 5.1 and 5.2. The\n libraries are copyright by th' ..
+ 'eir authors 2000-2015 (see the\n AUTHORS file for d' ..
+ 'etails), and released under the MIT license\n (the ' ..
+ 'same license as Lua itself). There is no warranty.'
+ expect(f(subject, 72, 8)).to_be(target)
+ - context given a long unwrapped string:
+ - before:
+ target = ' This is a collection of Lua libraries for Lua 5' ..
+ '.1 and 5.2.\n The libraries are copyright by their author' ..
+ 's 2000-2015 (see\n the AUTHORS file for details), and rel' ..
+ 'eased under the MIT\n license (the same license as Lua it' ..
+ 'self). There is no\n warranty.'
+ - it can indent the first line differently:
+ expect(f(subject, 64, 2, 4)).to_be(target)
+ - it is available as a string metamethod:
+ expect(subject:wrap(64, 2, 4)).to_be(target)
+ - it does not perturb the original subject:
+ original = subject
+ newstring = f(subject, 55, 5)
+ expect(subject).to_be(original)
+ - it diagnoses indent greater than line width:
+ expect(f(subject, 10, 12)).to_raise('less than the line width')
+ expect(f(subject, 99, 99)).to_raise('less than the line width')
+ - it diagnoses non-string arguments:
+ if have_typecheck then
+ expect(f()).to_raise('string expected')
+ expect(f {'a table'}).to_raise('string expected')
+ end
diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml
new file mode 100644
index 0000000..d9fed0c
--- /dev/null
+++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml
@@ -0,0 +1,589 @@
+# General Lua Libraries for Lua 5.1, 5.2 & 5.3
+# Copyright (C) 2011-2018 stdlib authors
+
+before: |
+ base_module = 'table'
+ this_module = 'std.table'
+ global_table = '_G'
+
+ extend_base = {'clone', 'clone_select', 'depair', 'empty',
+ 'enpair', 'insert', 'invert', 'keys', 'maxn',
+ 'merge', 'merge_select', 'new',
+ 'pack', 'project', 'remove', 'size', 'sort',
+ 'unpack', 'values'}
+
+ M = require(this_module)
+
+
+specify std.table:
+- context when required:
+ - context by name:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by=this_module}).
+ to_equal {}
+ - it does not touch the core table table:
+ expect(show_apis {added_to=base_module, by=this_module}).
+ to_equal {}
+ - it contains apis from the core table table:
+ expect(show_apis {from=base_module, not_in=this_module}).
+ to_contain.a_permutation_of(extend_base)
+
+ - context via the std module:
+ - it does not touch the global table:
+ expect(show_apis {added_to=global_table, by='std'}).to_equal {}
+ - it does not touch the core table table:
+ expect(show_apis {added_to=base_module, by='std'}).to_equal {}
+
+
+- describe clone:
+ - before:
+ subject = {k1={'v1'}, k2={'v2'}, k3={'v3'}}
+ withmt = setmetatable(M.clone(subject), {'meta!'})
+
+ f = M.clone
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.clone(table, [table], ?boolean|:nometa)')
+
+ - it does not just return the subject:
+ expect(f(subject)).not_to_be(subject)
+ - it does copy the subject:
+ expect(f(subject)).to_equal(subject)
+ - it only makes a shallow copy of field values:
+ expect(f(subject).k1).to_be(subject.k1)
+ - it does not perturb the original subject:
+ target = {k1=subject.k1, k2=subject.k2, k3=subject.k3}
+ copy = f(subject)
+ expect(subject).to_equal(target)
+ expect(subject).to_be(subject)
+
+ - context with metatables:
+ - it copies the metatable by default:
+ expect(getmetatable(f(withmt))).to_be(getmetatable(withmt))
+ - it treats non-table arg2 as nometa parameter:
+ expect(getmetatable(f(withmt, ':nometa'))).to_be(nil)
+ - it treats table arg2 as a map parameter:
+ expect(getmetatable(f(withmt, {}))).to_be(getmetatable(withmt))
+ - it supports 3 arguments with nometa as arg3:
+ expect(getmetatable(f(withmt, {}, ':nometa'))).to_be(nil)
+
+ - context when renaming some keys:
+ - it renames during cloning:
+ target = {newkey=subject.k1, k2=subject.k2, k3=subject.k3}
+ expect(f(subject, {k1 = 'newkey'})).to_equal(target)
+ - it does not perturb the value in the renamed key field:
+ expect(f(subject, {k1 = 'newkey'}).newkey).to_be(subject.k1)
+
+
+- describe clone_select:
+ - before:
+ subject = {k1={'v1'}, k2={'v2'}, k3={'v3'}}
+ withmt = setmetatable(M.clone(subject), {'meta!'})
+
+ f = M.clone_select
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.clone_select(table, [table], ?boolean|:nometa)')
+
+ - it does not just return the subject:
+ expect(f(subject)).not_to_be(subject)
+ - it copies the keys selected:
+ expect(f(subject, {'k1', 'k2'})).to_equal({k1={'v1'}, k2={'v2'}})
+ - it does copy the subject when supplied with a full list of keys:
+ expect(f(subject, {'k1', 'k2', 'k3'})).to_equal(subject)
+ - it only makes a shallow copy:
+ expect(f(subject, {'k1'}).k1).to_be(subject.k1)
+ - it does not perturb the original subject:
+ target = {k1=subject.k1, k2=subject.k2, k3=subject.k3}
+ copy = f(subject, {'k1', 'k2', 'k3'})
+ expect(subject).to_equal(target)
+ expect(subject).to_be(subject)
+
+ - context with metatables:
+ - it treats non-table arg2 as nometa parameter:
+ expect(getmetatable(f(withmt, ':nometa'))).to_be(nil)
+ - it treats table arg2 as a map parameter:
+ expect(getmetatable(f(withmt, {}))).to_be(getmetatable(withmt))
+ expect(getmetatable(f(withmt, {'k1'}))).to_be(getmetatable(withmt))
+ - it supports 3 arguments with nometa as arg3:
+ expect(getmetatable(f(withmt, {}, ':nometa'))).to_be(nil)
+ expect(getmetatable(f(withmt, {'k1'}, ':nometa'))).to_be(nil)
+
+
+- describe depair:
+ - before:
+ t = {'first', 'second', third = 4}
+ l = M.enpair(t)
+
+ f = M.depair
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.depair(list of lists)')
+
+ - it returns a primitive table:
+ expect(objtype(f(l))).to_be 'table'
+ - it works with an empty table:
+ expect(f {}).to_equal {}
+ - it is the inverse of enpair:
+ expect(f(l)).to_equal(t)
+
+
+- describe empty:
+ - before:
+ f = M.empty
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.empty(table)')
+
+ - it returns true for an empty table:
+ expect(f {}).to_be(true)
+ expect(f {nil}).to_be(true)
+ - it returns false for a non-empty table:
+ expect(f {'stuff'}).to_be(false)
+ expect(f {{}}).to_be(false)
+ expect(f {false}).to_be(false)
+
+
+- describe enpair:
+ - before:
+ t = {'first', 'second', third = 4}
+ l = M.enpair(t)
+
+ f = M.enpair
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.enpair(table)')
+
+ - it returns a table:
+ expect(objtype(f(t))).to_be 'table'
+ - it works for an empty table:
+ expect(f {}).to_equal {}
+ - it turns a table into a table of pairs:
+ expect(f(t)).to_equal {{1, 'first'}, {2, 'second'}, {'third', 4}}
+ - it is the inverse of depair:
+ expect(f(t)).to_equal(l)
+
+
+- describe insert:
+ - before:
+ f, badarg = init(M, this_module, 'insert')
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.insert(table, [int], any)')
+
+ examples {
+ ['it diagnoses more than 2 arguments with no pos'] = function()
+ pending '#issue 76'
+ expect(f({}, false, false)).to_raise(badarg(3))
+ end
+ }
+ examples {
+ ['it diagnoses out of bounds pos arguments'] = function()
+ expect(f({}, 0, 'x')).to_raise 'position 0 out of bounds'
+ expect(f({}, 2, 'x')).to_raise 'position 2 out of bounds'
+ expect(f({1}, 5, 'x')).to_raise 'position 5 out of bounds'
+ end
+ }
+
+ - it returns the modified table:
+ t = {}
+ expect(f(t, 1)).to_be(t)
+ - it append a new element at the end by default:
+ expect(f({1, 2}, 'x')).to_equal {1, 2, 'x'}
+ - it fills holes by default:
+ expect(f({1, 2, [5]=3}, 'x')).to_equal {1, 2, 'x', [5]=3}
+ - it respects __len when appending:
+ t = setmetatable({1, 2, [5]=3}, {__len = function() return 42 end})
+ expect(f(t, 'x')).to_equal {1, 2, [5]=3, [43]='x'}
+ - it moves other elements up if necessary:
+ expect(f({1, 2}, 1, 'x')).to_equal {'x', 1, 2}
+ expect(f({1, 2}, 2, 'x')).to_equal {1, 'x', 2}
+ expect(f({1, 2}, 3, 'x')).to_equal {1, 2, 'x'}
+ - it inserts a new element according to pos argument:
+ expect(f({}, 1, 'x')).to_equal {'x'}
+
+
+- describe invert:
+ - before:
+ subject = {k1=1, k2=2, k3=3}
+
+ f = M.invert
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.invert(table)')
+
+ - it returns a new table:
+ expect(f(subject)).not_to_be(subject)
+ - it inverts keys and values in the returned table:
+ expect(f(subject)).to_equal {'k1', 'k2', 'k3'}
+ - it is reversible:
+ expect(f(f(subject))).to_equal(subject)
+ - it seems to copy a list of 1..n numbers:
+ subject = {1, 2, 3}
+ expect(f(subject)).to_copy(subject)
+
+
+- describe keys:
+ - before:
+ subject = {k1=1, k2=2, k3=3}
+
+ f = M.keys
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.keys(table)')
+
+ - it returns an empty list when subject is empty:
+ expect(f {}).to_equal {}
+ - it makes a list of table keys:
+ cmp = function(a, b)
+ return a < b
+ end
+ expect(M.sort(f(subject), cmp)).to_equal {'k1', 'k2', 'k3'}
+ - it does not guarantee stable ordering:
+ subject = {}
+ -- is this a good test? there is a vanishingly small possibility the
+ -- returned table will have all 10000 keys in the same order...
+ for i = 10000, 1, -1 do
+ table.insert(subject, i)
+ end
+ expect(f(subject)).not_to_equal(subject)
+
+
+- describe maxn:
+ - before:
+ f = M.maxn
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.maxn(table)')
+
+ - it returns the largest numeric key of a table:
+ expect(f {'a', 'b', 'c'}).to_be(3)
+ expect(f {1, 2, 5, a=10, 3}).to_be(4)
+ - it works with an empty table:
+ expect(f {}).to_be(0)
+ - it ignores holes:
+ expect(f {1, 2, [5]=3}).to_be(5)
+ - it ignores __len metamethod:
+ t = setmetatable({1, 2, [5]=3}, {__len = function() return 42 end})
+ expect(f(t)).to_be(5)
+
+
+- describe merge:
+ - before: |
+ -- Additional merge keys which are moderately unusual
+ t1 = {k1={'v1'}, k2='if', k3={'?'}}
+ t2 = {['if']=true, [{'?'}]=false, _='underscore', k3=t1.k1}
+ t1mt = setmetatable(M.clone(t1), {'meta!'})
+ target = {}
+ for k, v in pairs(t1) do
+ target[k] = v
+ end
+ for k, v in pairs(t2) do
+ target[k] = v
+ end
+
+ f, badarg = init(M, this_module, 'merge')
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.merge(table, table, [table], ?boolean|:nometa)')
+
+ examples {
+ ['it diagnoses more than 2 arguments with no pos'] = function()
+ pending '#issue 76'
+ expect(f({}, {}, ':nometa', false)).to_raise(badarg(4))
+ end
+ }
+
+ - it does not create a whole new table:
+ expect(f(t1, t2)).to_be(t1)
+ - it does not change t1 when t2 is empty:
+ expect(f(t1, {})).to_be(t1)
+ - it copies t2 when t1 is empty:
+ expect(f({}, t1)).to_copy(t1)
+ - it merges keys from t2 into t1:
+ expect(f(t1, t2)).to_equal(target)
+ - it gives precedence to values from t2:
+ original = M.clone(t1)
+ m = f(t1, t2) -- Merge is destructive, do it once only.
+ expect(m.k3).to_be(t2.k3)
+ expect(m.k3).not_to_be(original.k3)
+ - it only makes a shallow copy of field values:
+ expect(f({}, t1).k1).to_be(t1.k1)
+
+ - context with metatables:
+ - it copies the metatable by default:
+ expect(getmetatable(f({}, t1mt))).to_be(getmetatable(t1mt))
+ expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt))
+ - it treats non-table arg3 as nometa parameter:
+ expect(getmetatable(f({}, t1mt, ':nometa'))).to_be(nil)
+ - it treats table arg3 as a map parameter:
+ expect(getmetatable(f({}, t1mt, {}))).to_be(getmetatable(t1mt))
+ expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt))
+ - it supports 4 arguments with nometa as arg4:
+ expect(getmetatable(f({}, t1mt, {}, ':nometa'))).to_be(nil)
+ expect(getmetatable(f({}, t1mt, {'k1'}, ':nometa'))).to_be(nil)
+
+ - context when renaming some keys:
+ - it renames during merging:
+ target = {newkey=t1.k1, k2=t1.k2, k3=t1.k3}
+ expect(f({}, t1, {k1 = 'newkey'})).to_equal(target)
+ - it does not perturb the value in the renamed key field:
+ expect(f({}, t1, {k1 = 'newkey'}).newkey).to_be(t1.k1)
+
+
+- describe merge_select:
+ - before: |
+ -- Additional merge keys which are moderately unusual
+ tablekey = {'?'}
+ t1 = {k1={'v1'}, k2='if', k3={'?'}}
+ t1mt = setmetatable(M.clone(t1), {'meta!'})
+ t2 = {['if']=true, [tablekey]=false, _='underscore', k3=t1.k1}
+ t2keys = {'if', tablekey, '_', 'k3'}
+ target = {}
+ for k, v in pairs(t1) do
+ target[k] = v
+ end
+ for k, v in pairs(t2) do
+ target[k] = v
+ end
+
+ f, badarg = init(M, this_module, 'merge_select')
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.merge_select(table, table, [table], ?boolean|:nometa)')
+
+ examples {
+ ['it diagnoses more than 2 arguments with no pos'] = function()
+ pending '#issue 76'
+ expect(f({}, {}, ':nometa', false)).to_raise(badarg(4))
+ end
+ }
+
+ - it does not create a whole new table:
+ expect(f(t1, t2)).to_be(t1)
+ - it does not change t1 when t2 is empty:
+ expect(f(t1, {})).to_be(t1)
+ - it does not change t1 when key list is empty:
+ expect(f(t1, t2, {})).to_be(t1)
+ - it copies the named fields:
+ expect(f({}, t2, t2keys)).to_equal(t2)
+ - it makes a shallow copy:
+ expect(f({}, t1, {'k1'}).k1).to_be(t1.k1)
+ - it copies exactly named fields of t2 when t1 is empty:
+ expect(f({}, t1, {'k1', 'k2', 'k3'})).to_copy(t1)
+ - it merges keys from t2 into t1:
+ expect(f(t1, t2, t2keys)).to_equal(target)
+ - it gives precedence to values from t2:
+ original = M.clone(t1)
+ m = f(t1, t2, t2keys) -- Merge is destructive, do it once only.
+ expect(m.k3).to_be(t2.k3)
+ expect(m.k3).not_to_be(original.k3)
+
+ - context with metatables:
+ - it copies the metatable by default:
+ expect(getmetatable(f({}, t1mt))).to_be(getmetatable(t1mt))
+ expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt))
+ - it treats non-table arg3 as nometa parameter:
+ expect(getmetatable(f({}, t1mt, ':nometa'))).to_be(nil)
+ - it treats table arg3 as a map parameter:
+ expect(getmetatable(f({}, t1mt, {}))).to_be(getmetatable(t1mt))
+ expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt))
+ - it supports 4 arguments with nometa as arg4:
+ expect(getmetatable(f({}, t1mt, {}, ':nometa'))).to_be(nil)
+ expect(getmetatable(f({}, t1mt, {'k1'}, ':nometa'))).to_be(nil)
+
+
+- describe new:
+ - before:
+ f = M.new
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.new(?any, ?table)')
+
+ - context when not setting a default:
+ - before: default = nil
+ - it returns a new table when nil is passed:
+ expect(f(default, nil)).to_equal {}
+ - it returns any table passed in:
+ t = {'unique table'}
+ expect(f(default, t)).to_be(t)
+
+ - context when setting a default:
+ - before:
+ default = 'default'
+ - it returns a new table when nil is passed:
+ expect(f(default, nil)).to_equal {}
+ - it returns any table passed in:
+ t = {'unique table'}
+ expect(f(default, t)).to_be(t)
+
+ - it returns the stored value for existing keys:
+ t = f('default')
+ v = {'unique value'}
+ t[1] = v
+ expect(t[1]).to_be(v)
+ - it returns the constructor default for unset keys:
+ t = f('default')
+ expect(t[1]).to_be 'default'
+ - it returns the actual default object:
+ default = {'unique object'}
+ t = f(default)
+ expect(t[1]).to_be(default)
+
+
+- describe pack:
+ - before:
+ unpack = unpack or table.unpack
+ t = {'one', 'two', 'five', n=3}
+ f = M.pack
+ - it creates an empty table with no arguments:
+ expect(f()).to_equal {n=0}
+ - it creates a table with arguments as elements:
+ expect(f('one', 'two', 'five')).to_equal(t)
+ - it is the inverse operation to unpack:
+ expect(f(unpack(t))).to_equal(t)
+ - it saves the tuple length in field n:
+ expect(f(1, 2, 5).n).to_be(3)
+ expect(f('', false, nil).n).to_be(3)
+ expect(f(nil, nil, nil).n).to_be(3)
+
+
+- describe project:
+ - before:
+ l = {
+ {first = false, second = true, third = true},
+ {first = 1, second = 2, third = 3},
+ {first = '1st', second = '2nd', third = '3rd'},
+ }
+
+ f = M.project
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.project(any, list of tables)')
+
+ - it returns a table:
+ expect(objtype(f('third', l))).to_be 'table'
+ - it works with an empty table:
+ expect(f('third', {})).to_equal {}
+ - it projects a table of fields from a table of tables:
+ expect(f('third', l)).to_equal {true, 3, '3rd'}
+ - it projects fields with a falsey value correctly:
+ expect(f('first', l)).to_equal {false, 1, '1st'}
+
+
+- describe remove:
+ - before:
+ f = M.remove
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.remove(table, ?int)')
+
+ examples {
+ ['it diagnoses out of bounds pos arguments'] = function()
+ expect(f({1}, 0)).to_raise 'position 0 out of bounds'
+ expect(f({1}, 3)).to_raise 'position 3 out of bounds'
+ expect(f({1}, 5)).to_raise 'position 5 out of bounds'
+ end
+ }
+
+ - it returns the removed element:
+ t = {'one', 'two', 'five'}
+ expect(f({'one', 2, 5}, 1)).to_be 'one'
+ - it removes an element from the end by default:
+ expect(f {1, 2, 'five'}).to_be 'five'
+ - it ignores holes:
+ t = {'second', 'first', [5]='invisible'}
+ expect(f(t)).to_be 'first'
+ expect(f(t)).to_be 'second'
+ - it respects __len when defaulting pos:
+ t = setmetatable({1, 2, [43]='invisible'}, {__len = function() return 42 end})
+ expect(f(t)).to_be(nil)
+ expect(f(t)).to_be(nil)
+ expect(t).to_equal {1, 2, [43]='invisible'}
+ - it moves other elements down if necessary:
+ t = {1, 2, 5, 'third', 'first', 'second', 42}
+ expect(f(t, 5)).to_be 'first'
+ expect(t).to_equal {1, 2, 5, 'third', 'second', 42}
+ expect(f(t, 5)).to_be 'second'
+ expect(t).to_equal {1, 2, 5, 'third', 42}
+ expect(f(t, 4)).to_be 'third'
+ expect(t).to_equal {1, 2, 5, 42}
+
+
+- describe size:
+ - before: |
+ -- - 1 - ------- 2 ------- -- 3 --
+ subject = {'one', {{'two'}, 'three'}, four=5}
+
+ f = M.size
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.size(table)')
+
+ - it counts the number of keys in a table:
+ expect(f(subject)).to_be(3)
+ - it counts no keys in an empty table:
+ expect(f {}).to_be(0)
+
+
+- describe sort:
+ - before:
+ subject = {5, 2, 4, 1, 0, 3}
+ target = {0, 1, 2, 3, 4, 5}
+ cmp = function(a, b) return a < b end
+
+ f = M.sort
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.sort(table, ?function)')
+
+ - it sorts elements in place:
+ f(subject, cmp)
+ expect(subject).to_equal(target)
+ - it returns the sorted table:
+ expect(f(subject, cmp)).to_equal(target)
+
+
+- describe unpack:
+ - before:
+ t = {'one', 'two', 'five'}
+ f = M.unpack
+ - it returns nil for an empty table:
+ expect(f {}).to_be(nil)
+ - it returns numeric indexed table elements:
+ expect({f(t)}).to_equal(t)
+ - it respects __len metamethod:
+ function two(t)
+ return setmetatable(t, {__len=function() return 2 end})
+ end
+ expect(pack(f(two {})).n).to_be(2)
+ expect(pack(f(two(t))).n).to_be(2)
+ - it returns holes in numeric indices as nil:
+ expect({f {nil, 2}}).to_equal {[2] = 2}
+ expect({f {nil, nil, 3}}).to_equal {[3] = 3}
+ expect({f {1, nil, nil, 4}}).to_equal {1, [4] = 4}
+ - it is the inverse operation to pack:
+ expect({f(M.pack('one', 'two', 'five'))}).to_equal(t)
+
+
+- describe values:
+ - before:
+ subject = {k1={1}, k2={2}, k3={3}}
+
+ f = M.values
+
+ - context with bad arguments:
+ badargs.diagnose(f, 'std.table.values(table)')
+
+ - it returns an empty list when subject is empty:
+ expect(f {}).to_equal {}
+ - it makes a list of table values:
+ cmp = function(a, b) return a[1] < b[1] end
+ expect(M.sort(f(subject), cmp)).to_equal {{1}, {2}, {3}}
+ - it does guarantee stable ordering:
+ subject = {}
+ -- is this a good test? or just requiring an implementation quirk?
+ for i = 10000, 1, -1 do
+ table.insert(subject, i)
+ end
+ expect(f(subject)).to_equal(subject)