diff --git a/.gitignore b/.gitignore
index a5c98a2..dbac14c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,26 +1,9 @@
-/*.rockspec
-/.autom4te.cfg
-/.gitmodules
-/COPYING
-/ChangeLog
-/GNUmakefile
+*~
+.DS_Store
+/*.src.rock
/Makefile
-/Makefile.am
-/Makefile.in
-/aclocal.m4
-/autom4te.cache
-/build-aux
-/config.log
-/config.status
-/configure
-/lib/files
-/lib/index.html
-/lib/luadoc.css
-/lib/modules
-/lib/std.lua
-/lua-stdlib-*.tar.gz
-/luarocks
-/m4/ax_compare_version.m4
-/m4/ax_lua.m4
-/m4/slingshot.m4
-/travis.yml.in
+/build-aux/config.ld
+/doc
+/lib/std/version.lua
+/luacov.*.out
+/stdlib-*.tar.gz
diff --git a/.luacov b/.luacov
new file mode 100644
index 0000000..40b9d6a
--- /dev/null
+++ b/.luacov
@@ -0,0 +1,52 @@
+return {
+ -- filename to store stats collected
+ ["statsfile"] = "luacov.stats.out",
+
+ -- filename to store report
+ ["reportfile"] = "luacov.report.out",
+
+ -- luacov.stats file updating frequency.
+ -- The lower this value - the more frequenty results will be written out to luacov.stats
+ -- You may want to reduce this value for short lived scripts (to for example 2) to avoid losing coverage data.
+ ["savestepsize"] = 100,
+
+ -- Run reporter on completion? (won't work for ticks)
+ runreport = true,
+
+ -- Delete stats file after reporting?
+ deletestats = false,
+
+ -- Process Lua code loaded from raw strings
+ -- (that is, when the 'source' field in the debug info
+ -- does not start with '@')
+ codefromstrings = false,
+
+ -- Patterns for files to include when reporting
+ -- all will be included if nothing is listed
+ -- (exclude overrules include, do not include
+ -- the .lua extension, path separator is always '/')
+ ["include"] = {
+ "lib/std/_base$",
+ "lib/std/debug$",
+ "lib/std/init$",
+ "lib/std/io$",
+ "lib/std/math$",
+ "lib/std/package$",
+ "lib/std/string$",
+ "lib/std/table$",
+ --"lib/std/version$",
+ },
+
+ -- Patterns for files to exclude when reporting
+ -- all will be included if nothing is listed
+ -- (exclude overrules include, do not include
+ -- the .lua extension, path separator is always '/')
+ ["exclude"] = {
+ "luacov$",
+ "luacov/reporter$",
+ "luacov/defaults$",
+ "luacov/runner$",
+ "luacov/stats$",
+ "luacov/tick$",
+ },
+}
diff --git a/.prev-version b/.prev-version
deleted file mode 100644
index 8f92bfd..0000000
--- a/.prev-version
+++ /dev/null
@@ -1 +0,0 @@
-35
diff --git a/.travis.yml b/.travis.yml
index 6811576..92f4f73 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,60 +1,34 @@
-# Lua is not officially supported, but an erlang environment will do.
-language: erlang
+language: python
+
+sudo: false
env:
- global:
- - PACKAGE=lua-stdlib
- - ROCKSPEC=$PACKAGE-git-1.rockspec
- - LUAROCKS_CONFIG=build-aux/luarocks-config.lua
- - LUAROCKS_BASE=luarocks-2.0.13
- - LUAROCKS="$LUA $HOME/bin/luarocks"
- - GENDOC=luarocks/bin/luadoc
- - SPECL=bin/specl
matrix:
- - LUA=lua5.1 LUA_INCDIR=/usr/include/lua5.1
- - LUA=lua5.2 LUA_INCDIR=/usr/include/lua5.2
- - LUA=luajit-2.0.0-beta9 LUA_INCDIR=/usr/include/luajit-2.0
+ - LUA="lua=5.3"
+ - LUA="lua=5.2"
+ - LUA="lua=5.1"
+ - LUA="luajit=2.1"
+ - LUA="luajit=2.0"
+
+before_install:
+ - pip install hererocks
+ - hererocks here -r^ --$LUA --patch
+ - export PATH=$PWD/here/bin:$PATH
-# Tool setup.
install:
- - sudo apt-get install help2man
- - sudo apt-get install luajit
- - sudo apt-get install libluajit-5.1-dev
- - sudo apt-get install lua5.1
- - sudo apt-get install liblua5.1-dev
- - sudo apt-get install lua5.2
- - sudo apt-get install liblua5.2-dev
- # Luadoc and Ldoc work best on Travis with Lua 5.1.
- - sudo apt-get install luarocks
- - sudo luarocks install luadoc
- - mkdir -p luarocks/bin
- - sed 's|^exec "[^"]*"|exec lua5.1|' `which luadoc` > $GENDOC
- - chmod a+rx $GENDOC
- # Install a recent luarocks release locally for everything else.
- - wget http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz
- - tar zxvpf $LUAROCKS_BASE.tar.gz
- - cd $LUAROCKS_BASE
- - ./configure
- --prefix=$HOME --lua-version=5.1 --lua-suffix=5.1
- --with-lua-include="/usr/include/lua5.1"
- - make all install
- - cd ..
+ - luarocks install ldoc
+ - luarocks install ansicolors
+ - luarocks install specl
+ - luarocks install luacov
-# Configure and build.
script:
- - ./bootstrap
- - ./configure LUA="$LUA"
- - make $LUAROCKS_CONFIG
- LUA="$LUA" LUA_INCDIR="$LUA_INCDIR" V=1
- || cat $LUAROCKS_CONFIG config.log
- # Set Lua and Shell paths up for local luarocks tree.
- - eval `$LUAROCKS path`
- - export PATH=`pwd`/luarocks/bin:$PATH
- - $LUAROCKS install lyaml; $LUAROCKS install specl;
- - make rockspecs LUAROCKS="$LUAROCKS" V=1
- || { $LUAROCKS path; cat $ROCKSPEC; }
- # LuaRocks make will fail if dependencies are missing.
- - $LUAROCKS make $ROCKSPEC LUA="$LUA"
- # Use bin/specl if we built it, or else the specl rock we just installed.
- - test -f "$SPECL" || SPECL=luarocks/bin/specl;
- make check SPECL="$SPECL" V=1
+ - make
+ - luarocks make
+ - make check SPECL_OPTS='-vfreport --coverage'
+
+after_success:
+ - tail luacov.report.out
+ - bash <(curl -s https://codecov.io/bash) -v
+
+notifications:
+ slack: aspirinc:JyWeNrIdS0J5nf2Pn2BS1cih
diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644
index 0cbc134..0000000
--- a/AUTHORS
+++ /dev/null
@@ -1,28 +0,0 @@
- Stdlib's contributors
- ---------------------
-
-This file lists major contributors to stdlib. If you think you should
-be on it, please tell the mailing list (see README for the address).
-Thanks also to all those who have contributed bug fixes, suggestions
-and support.
-
-
-Reuben Thomas started and maintains the standard libraries project,
-wrote many of the libraries, and integrated code from other authors.
-
-John Belmonte helped set the project up on lua-users, and contributed
-to the early organisation of the libraries.
-
-The call trace debugging code is based on test/trace-calls.lua from
-the Lua 5.0 distribution.
-
-Jamie Webb contributed several miscellaneous functions from his
-private standard library.
-
-Johann Hibschman supplied the code on which math.floor and math.round
-were based.
-
-Diego Nehab wrote the original version of the mbox parser module.
-
-Gary V. Vaughan contributed table key support to the tree module, and
-the memoize implementation.
diff --git a/AUTHORS.md b/AUTHORS.md
new file mode 100644
index 0000000..6589984
--- /dev/null
+++ b/AUTHORS.md
@@ -0,0 +1,27 @@
+# Stdlib's contributors
+
+This file lists major contributors to _stdlib_. If you think you
+should be on it, please raise a [github][] issue. Thanks also to all
+those who have contributed bug fixes, suggestions and support.
+
+Gary V. Vaughan now maintains _stdlib_, having rewritten and reorganised
+the libraries for hygiene, consistent argument type checking in debug
+mode, and object orientation, in addition to adding a lot of new
+functionality.
+
+Reuben Thomas started the standard libraries project, wrote many of the
+libraries, and integrated code from other authors.
+
+John Belmonte helped set the project up on lua-users, and contributed
+to the early organisation of the libraries.
+
+The call trace debugging code is based on test/trace-calls.lua from
+the Lua 5.0 distribution.
+
+Jamie Webb contributed several miscellaneous functions from his
+private standard library.
+
+Johann Hibschman supplied the code on which math.floor and math.round
+were based.
+
+[github]: https://github.com/lua-stdlib/lua-stdlib/issues
diff --git a/INSTALL b/INSTALL
deleted file mode 100644
index 64c6937..0000000
--- a/INSTALL
+++ /dev/null
@@ -1,364 +0,0 @@
-Installation Instructions
-*************************
-
-Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
-2006, 2007, 2008, 2009 Free Software Foundation, Inc.
-
- Copying and distribution of this file, with or without modification,
-are permitted in any medium without royalty provided the copyright
-notice and this notice are preserved. This file is offered as-is,
-without warranty of any kind.
-
-Basic Installation
-==================
-
- Briefly, the shell commands `./configure; make; make install' should
-configure, build, and install this package. The following
-more-detailed instructions are generic; see the `README' file for
-instructions specific to this package. Some packages provide this
-`INSTALL' file but do not implement all of the features documented
-below. The lack of an optional feature in a given package is not
-necessarily a bug. More recommendations for GNU packages can be found
-in *note Makefile Conventions: (standards)Makefile Conventions.
-
- The `configure' shell script attempts to guess correct values for
-various system-dependent variables used during compilation. It uses
-those values to create a `Makefile' in each directory of the package.
-It may also create one or more `.h' files containing system-dependent
-definitions. Finally, it creates a shell script `config.status' that
-you can run in the future to recreate the current configuration, and a
-file `config.log' containing compiler output (useful mainly for
-debugging `configure').
-
- It can also use an optional file (typically called `config.cache'
-and enabled with `--cache-file=config.cache' or simply `-C') that saves
-the results of its tests to speed up reconfiguring. Caching is
-disabled by default to prevent problems with accidental use of stale
-cache files.
-
- If you need to do unusual things to compile the package, please try
-to figure out how `configure' could check whether to do them, and mail
-diffs or instructions to the address given in the `README' so they can
-be considered for the next release. If you are using the cache, and at
-some point `config.cache' contains results you don't want to keep, you
-may remove or edit it.
-
- The file `configure.ac' (or `configure.in') is used to create
-`configure' by a program called `autoconf'. You need `configure.ac' if
-you want to change it or regenerate `configure' using a newer version
-of `autoconf'.
-
- The simplest way to compile this package is:
-
- 1. `cd' to the directory containing the package's source code and type
- `./configure' to configure the package for your system.
-
- Running `configure' might take a while. While running, it prints
- some messages telling which features it is checking for.
-
- 2. Type `make' to compile the package.
-
- 3. Optionally, type `make check' to run any self-tests that come with
- the package, generally using the just-built uninstalled binaries.
-
- 4. Type `make install' to install the programs and any data files and
- documentation. When installing into a prefix owned by root, it is
- recommended that the package be configured and built as a regular
- user, and only the `make install' phase executed with root
- privileges.
-
- 5. Optionally, type `make installcheck' to repeat any self-tests, but
- this time using the binaries in their final installed location.
- This target does not install anything. Running this target as a
- regular user, particularly if the prior `make install' required
- root privileges, verifies that the installation completed
- correctly.
-
- 6. You can remove the program binaries and object files from the
- source code directory by typing `make clean'. To also remove the
- files that `configure' created (so you can compile the package for
- a different kind of computer), type `make distclean'. There is
- also a `make maintainer-clean' target, but that is intended mainly
- for the package's developers. If you use it, you may have to get
- all sorts of other programs in order to regenerate files that came
- with the distribution.
-
- 7. Often, you can also type `make uninstall' to remove the installed
- files again. In practice, not all packages have tested that
- uninstallation works correctly, even though it is required by the
- GNU Coding Standards.
-
- 8. Some packages, particularly those that use Automake, provide `make
- distcheck', which can by used by developers to test that all other
- targets like `make install' and `make uninstall' work correctly.
- This target is generally not run by end users.
-
-Compilers and Options
-=====================
-
- Some systems require unusual options for compilation or linking that
-the `configure' script does not know about. Run `./configure --help'
-for details on some of the pertinent environment variables.
-
- You can give `configure' initial values for configuration parameters
-by setting variables in the command line or in the environment. Here
-is an example:
-
- ./configure CC=c99 CFLAGS=-g LIBS=-lposix
-
- *Note Defining Variables::, for more details.
-
-Compiling For Multiple Architectures
-====================================
-
- You can compile the package for more than one kind of computer at the
-same time, by placing the object files for each architecture in their
-own directory. To do this, you can use GNU `make'. `cd' to the
-directory where you want the object files and executables to go and run
-the `configure' script. `configure' automatically checks for the
-source code in the directory that `configure' is in and in `..'. This
-is known as a "VPATH" build.
-
- With a non-GNU `make', it is safer to compile the package for one
-architecture at a time in the source code directory. After you have
-installed the package for one architecture, use `make distclean' before
-reconfiguring for another architecture.
-
- On MacOS X 10.5 and later systems, you can create libraries and
-executables that work on multiple system types--known as "fat" or
-"universal" binaries--by specifying multiple `-arch' options to the
-compiler but only a single `-arch' option to the preprocessor. Like
-this:
-
- ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
- CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
- CPP="gcc -E" CXXCPP="g++ -E"
-
- This is not guaranteed to produce working output in all cases, you
-may have to build one architecture at a time and combine the results
-using the `lipo' tool if you have problems.
-
-Installation Names
-==================
-
- By default, `make install' installs the package's commands under
-`/usr/local/bin', include files under `/usr/local/include', etc. You
-can specify an installation prefix other than `/usr/local' by giving
-`configure' the option `--prefix=PREFIX', where PREFIX must be an
-absolute file name.
-
- You can specify separate installation prefixes for
-architecture-specific files and architecture-independent files. If you
-pass the option `--exec-prefix=PREFIX' to `configure', the package uses
-PREFIX as the prefix for installing programs and libraries.
-Documentation and other data files still use the regular prefix.
-
- In addition, if you use an unusual directory layout you can give
-options like `--bindir=DIR' to specify different values for particular
-kinds of files. Run `configure --help' for a list of the directories
-you can set and what kinds of files go in them. In general, the
-default for these options is expressed in terms of `${prefix}', so that
-specifying just `--prefix' will affect all of the other directory
-specifications that were not explicitly provided.
-
- The most portable way to affect installation locations is to pass the
-correct locations to `configure'; however, many packages provide one or
-both of the following shortcuts of passing variable assignments to the
-`make install' command line to change installation locations without
-having to reconfigure or recompile.
-
- The first method involves providing an override variable for each
-affected directory. For example, `make install
-prefix=/alternate/directory' will choose an alternate location for all
-directory configuration variables that were expressed in terms of
-`${prefix}'. Any directories that were specified during `configure',
-but not in terms of `${prefix}', must each be overridden at install
-time for the entire installation to be relocated. The approach of
-makefile variable overrides for each directory variable is required by
-the GNU Coding Standards, and ideally causes no recompilation.
-However, some platforms have known limitations with the semantics of
-shared libraries that end up requiring recompilation when using this
-method, particularly noticeable in packages that use GNU Libtool.
-
- The second method involves providing the `DESTDIR' variable. For
-example, `make install DESTDIR=/alternate/directory' will prepend
-`/alternate/directory' before all installation names. The approach of
-`DESTDIR' overrides is not required by the GNU Coding Standards, and
-does not work on platforms that have drive letters. On the other hand,
-it does better at avoiding recompilation issues, and works well even
-when some directory options were not specified in terms of `${prefix}'
-at `configure' time.
-
-Optional Features
-=================
-
- If the package supports it, you can cause programs to be installed
-with an extra prefix or suffix on their names by giving `configure' the
-option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
-
- Some packages pay attention to `--enable-FEATURE' options to
-`configure', where FEATURE indicates an optional part of the package.
-They may also pay attention to `--with-PACKAGE' options, where PACKAGE
-is something like `gnu-as' or `x' (for the X Window System). The
-`README' should mention any `--enable-' and `--with-' options that the
-package recognizes.
-
- For packages that use the X Window System, `configure' can usually
-find the X include and library files automatically, but if it doesn't,
-you can use the `configure' options `--x-includes=DIR' and
-`--x-libraries=DIR' to specify their locations.
-
- Some packages offer the ability to configure how verbose the
-execution of `make' will be. For these packages, running `./configure
---enable-silent-rules' sets the default to minimal output, which can be
-overridden with `make V=1'; while running `./configure
---disable-silent-rules' sets the default to verbose, which can be
-overridden with `make V=0'.
-
-Particular systems
-==================
-
- On HP-UX, the default C compiler is not ANSI C compatible. If GNU
-CC is not installed, it is recommended to use the following options in
-order to use an ANSI C compiler:
-
- ./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
-
-and if that doesn't work, install pre-built binaries of GCC for HP-UX.
-
- On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
-parse its `%s*Corefunction (.-)
{l[1], ..., l[#l], x}
-local function append (l, x)
- local r = {unpack (l)}
- table.insert (r, x)
- return r
-end
-
---- Compare two lists element by element left-to-right
--- @param l first list
--- @param m second list
--- @return -1 if l is less than m, 0 if they
--- are the same, and 1 if l is greater than m
-local function compare (l, m)
- for i = 1, math.min (#l, #m) do
- if l[i] < m[i] then
- return -1
- elseif l[i] > m[i] then
- return 1
- end
- end
- if #l < #m then
- return -1
- elseif #l > #m then
- return 1
- end
- return 0
-end
-
---- An iterator over the elements of a list.
--- @param l list to iterate over
--- @return iterator function which returns successive elements of the list
--- @return the list l as above
--- @return true
-local function elems (l)
- local n = 0
- return function (l)
- n = n + 1
- if n <= #l then
- return l[n]
- end
- end,
- l, true
-end
-
---- Concatenate lists.
--- @param ... lists
--- @return {l1[1], ...,
--- l1[#l1], ..., ln[1], ...,
--- ln[#ln]}
-local function concat (...)
- local r = new ()
- for l in elems ({...}) do
- for v in elems (l) do
- table.insert (r, v)
- end
- end
- return r
-end
-
-local function _leaves (it, tr)
- local function visit (n)
- if type (n) == "table" then
- for _, v in it (n) do
- visit (v)
- end
- else
- coroutine.yield (n)
- end
- end
- return coroutine.wrap (visit), tr
-end
-
---- Tree iterator which returns just numbered leaves, in order.
--- @param tr tree to iterate over
--- @return iterator function
--- @return the tree, as above
-local function ileaves (tr)
- return _leaves (ipairs, tr)
-end
-
---- Tree iterator which returns just leaves.
--- @param tr tree to iterate over
--- @return iterator function
--- @return the tree, as above
-local function leaves (tr)
- return _leaves (pairs, tr)
-end
-
--- Metamethods for lists
--- It would be nice to define this in `list.lua`, but then we
--- couldn't keep `new` here, and other modules that really only
--- need `list.new` (as opposed to the entire `std.list` API) get
--- caught in a dependency loop.
-local metatable = {
- -- list .. table = list.concat
- __concat = concat,
-
- -- list == list retains its referential meaning
- --
- -- list < list = list.compare returns < 0
- __lt = function (l, m) return compare (l, m) < 0 end,
-
- -- list <= list = list.compare returns <= 0
- __le = function (l, m) return compare (l, m) <= 0 end,
-
- __append = append,
-}
-
---- List constructor.
--- Needed in order to use metamethods.
--- @param t list (as a table), or nil for empty list
--- @return list (with list metamethods)
-function new (l)
- return setmetatable (l or {}, metatable)
-end
-
-
---- Make a shallow copy of a table, including any metatable (for a
--- deep copy, use tree.clone).
--- @param t table
--- @param nometa if non-nil don't copy metatable
--- @return copy of table
-local function clone (t, nometa)
- local u = {}
- if not nometa then
- setmetatable (u, getmetatable (t))
- end
- for i, v in pairs (t) do
- u[i] = v
- end
- return u
-end
-
---- Clone a table, renaming some keys.
--- @param map table {old_key=new_key, ...}
--- @param t table to copy
--- @return copy of table
-local function clone_rename (map, t)
- local r = clone (t)
- for i, v in pairs (map) do
- r[v] = t[i]
- r[i] = nil
- end
- return r
-end
-
---- Merge one table into another. u is merged into t.
--- @param t first table
--- @param u second table
--- @return first table
-local function merge (t, u)
- for i, v in pairs (u) do
- t[i] = v
- end
- return t
-end
-
-local M = {
- append = append,
- clone = clone,
- clone_rename = clone_rename,
- compare = compare,
- concat = concat,
- elems = elems,
- ileaves = ileaves,
- leaves = leaves,
- merge = merge,
- new = new,
-
- -- list metatable
- _list_mt = metatable,
-}
-
-return M
diff --git a/lib/std/debug.lua b/lib/std/debug.lua
index 5629de4..cab29ff 100644
--- a/lib/std/debug.lua
+++ b/lib/std/debug.lua
@@ -1,96 +1,156 @@
---- Additions to the debug module
-
-local init = require "std.debug_init"
-local io = require "std.io"
-local list = require "std.list"
-local string = require "std.string"
-
---- To activate debugging set _DEBUG either to any true value
--- (equivalent to {level = 1}), or as documented below.
--- @class table
--- @name _DEBUG
--- @field level debugging level
--- @field call do call trace debugging
--- @field std do standard library debugging (run examples & test code)
-
-
---- Print a debugging message
--- @param n debugging level, defaults to 1
--- @param ... objects to print (as for print)
-local function say (n, ...)
- local level = 1
- local arg = {n, ...}
- if type (arg[1]) == "number" then
- level = arg[1]
- table.remove (arg, 1)
- end
- if init._DEBUG and
- ((type (init._DEBUG) == "table" and type (init._DEBUG.level) == "number" and
- init._DEBUG.level >= level)
- or level <= 1) then
- io.writelines (io.stderr, table.concat (list.map (string.tostring, arg), "\t"))
- end
+--[[
+ General Lua Libraries for Lua 5.1, 5.2 & 5.3
+ Copyright (C) 2002-2018 stdlib authors
+]]
+--[[--
+ Additions to the core debug module.
+
+ The module table returned by `std.debug` also contains all of the entries
+ from the core debug table. An hygienic way to import this module, then, is
+ simply to override the core `debug` locally:
+
+ local debug = require 'std.debug'
+
+ @corelibrary std.debug
+]]
+
+
+local _ENV = require 'std.normalize' {
+ 'debug',
+ _debug = require 'std._debug',
+ concat = 'table.concat',
+ huge = 'math.huge',
+ max = 'math.max',
+ merge = 'table.merge',
+ stderr = 'io.stderr',
+}
+
+
+
+--[[ =============== ]]--
+--[[ Implementation. ]]--
+--[[ =============== ]]--
+
+
+local function say(n, ...)
+ local level, argt = n, {...}
+ if type(n) ~= 'number' then
+ level, argt = 1, {n, ...}
+ end
+ if _debug.level ~= huge and
+ ((type(_debug.level) == 'number' and _debug.level >= level) or level <= 1)
+ then
+ local t = {}
+ for k, v in pairs(argt) do
+ t[k] = str(v)
+ end
+ stderr:write(concat(t, '\t') .. '\n')
+ end
end
---- Trace function calls
--- Use as debug.sethook (trace, "cr"), which is done automatically
--- when _DEBUG.call is set.
--- Based on test/trace-calls.lua from the Lua distribution.
--- @class function
--- @name trace
--- @param event event causing the call
+
local level = 0
-local function trace (event)
- local t = debug.getinfo (3)
- local s = " >>> " .. string.rep (" ", level)
- if t ~= nil and t.currentline >= 0 then
- s = s .. t.short_src .. ":" .. t.currentline .. " "
- end
- t = getinfo (2)
- if event == "call" then
- level = level + 1
- else
- level = math.max (level - 1, 0)
- end
- if t.what == "main" then
- if event == "call" then
- s = s .. "begin " .. t.short_src
- else
- s = s .. "end " .. t.short_src
- end
- elseif t.what == "Lua" then
- s = s .. event .. " " .. (t.name or "(Lua)") .. " <" ..
- t.linedefined .. ":" .. t.short_src .. ">"
- else
- s = s .. event .. " " .. (t.name or "(C)") .. " [" .. t.what .. "]"
- end
- io.writelines (io.stderr, s)
+
+local function trace(event)
+ local t = debug.getinfo(3)
+ local s = ' >>> '
+ for i = 1, level do
+ s = s .. ' '
+ end
+ if t ~= nil and t.currentline >= 0 then
+ s = s .. t.short_src .. ':' .. t.currentline .. ' '
+ end
+ t = debug.getinfo(2)
+ if event == 'call' then
+ level = level + 1
+ else
+ level = max(level - 1, 0)
+ end
+ if t.what == 'main' then
+ if event == 'call' then
+ s = s .. 'begin ' .. t.short_src
+ else
+ s = s .. 'end ' .. t.short_src
+ end
+ elseif t.what == 'Lua' then
+ s = s .. event .. ' ' ..(t.name or '(Lua)') .. ' <' ..
+ t.linedefined .. ':' .. t.short_src .. '>'
+ else
+ s = s .. event .. ' ' ..(t.name or '(C)') .. ' [' .. t.what .. ']'
+ end
+ stderr:write(s .. '\n')
end
--- Set hooks according to init._DEBUG
-if type (init._DEBUG) == "table" and init._DEBUG.call then
- debug.sethook (trace, "cr")
+-- Set hooks according to _debug
+if _debug.call then
+ debug.sethook(trace, 'cr')
end
+
+
local M = {
- say = say,
- trace = trace,
+ --- Function Environments
+ -- @section environments
+
+ --- Extend `debug.getfenv` to unwrap functables correctly.
+ -- @function getfenv
+ -- @tparam int|function|functable fn target function, or stack level
+ -- @treturn table environment of *fn*
+ getfenv = getfenv,
+
+ --- Extend `debug.setfenv` to unwrap functables correctly.
+ -- @function setfenv
+ -- @tparam function|functable fn target function
+ -- @tparam table env new function environment
+ -- @treturn function *fn*
+ setfenv = setfenv,
+
+
+ --- Functions
+ -- @section functions
+
+ --- Print a debugging message to `io.stderr`.
+ -- Display arguments passed through `std.tostring` and separated by tab
+ -- characters when `std._debug` hinting is `true` and *n* is 1 or less;
+ -- or `std._debug.level` is a number greater than or equal to *n*. If
+ -- `std._debug` hinting is false or nil, nothing is written.
+ -- @function say
+ -- @int[opt=1] n debugging level, smaller is higher priority
+ -- @param ... objects to print(as for print)
+ -- @usage
+ -- local _debug = require 'std._debug'
+ -- _debug.level = 3
+ -- say(2, '_debug status level:', _debug.level)
+ say = say,
+
+ --- Trace function calls.
+ -- Use as debug.sethook(trace, 'cr'), which is done automatically
+ -- when `std._debug.call` is set.
+ -- Based on test/trace-calls.lua from the Lua distribution.
+ -- @function trace
+ -- @string event event causing the call
+ -- @usage
+ -- local _debug = require 'std._debug'
+ -- _debug.call = true
+ -- local debug = require 'std.debug'
+ trace = trace,
}
-for k, v in pairs (debug) do
- M[k] = M[k] or v
-end
----
--- The global function debug is an abbreviation for
--- debug.say (1, ...)
--- @class function
--- @name debug
+--- Metamethods
+-- @section metamethods
+
+--- Equivalent to calling `debug.say(1, ...)`
+-- @function __call
-- @see say
+-- @usage
+-- local debug = require 'std.debug'
+-- debug 'oh noes!'
local metatable = {
- __call = function (self, ...)
- say (1, ...)
- end,
+ __call = function(self, ...)
+ M.say(1, ...)
+ end,
}
-return setmetatable (M, metatable)
+
+return setmetatable(merge(debug, M), metatable)
diff --git a/lib/std/debug_init.lua b/lib/std/debug_init.lua
deleted file mode 100644
index f008777..0000000
--- a/lib/std/debug_init.lua
+++ /dev/null
@@ -1,10 +0,0 @@
--- Debugging is on by default
-local M = {
- _DEBUG = true,
-}
-
-if _G._DEBUG ~= nil then
- M._DEBUG = _G._DEBUG
-end
-
-return M
diff --git a/lib/std/functional.lua b/lib/std/functional.lua
deleted file mode 100644
index 0fded29..0000000
--- a/lib/std/functional.lua
+++ /dev/null
@@ -1,178 +0,0 @@
---- Adds to the existing global functions
-
-local list = require "std.base"
-
-
---- Return given metamethod, if any, or nil.
--- @param x object to get metamethod of
--- @param n name of metamethod to get
--- @return metamethod function or nil if no metamethod or not a
--- function
-local function metamethod (x, n)
- local _, m = pcall (function (x)
- return getmetatable (x)[n]
- end,
- x)
- if type (m) ~= "function" then
- m = nil
- end
- return m
-end
-
---- Identity function.
--- @param ...
--- @return the arguments passed to the function
-local function id (...)
- return ...
-end
-
---- Partially apply a function.
--- @param f function to apply partially
--- @param ... arguments to bind
--- @return function with ai already bound
-local function bind (f, ...)
- local fix = {...}
- return function (...)
- return f (unpack (list.concat (fix, {...})))
- end
-end
-
---- Curry a function.
--- @param f function to curry
--- @param n number of arguments
--- @return curried version of f
-local function curry (f, n)
- if n <= 1 then
- return f
- else
- return function (x)
- return curry (bind (f, x), n - 1)
- end
- end
-end
-
---- Compose functions.
--- @param f1...fn functions to compose
--- @return composition of f1 ... fn
-local function compose (...)
- local arg = {...}
- local fns, n = arg, #arg
- return function (...)
- local arg = {...}
- for i = n, 1, -1 do
- arg = {fns[i] (unpack (arg))}
- end
- return unpack (arg)
- end
-end
-
---- Memoize a function, by wrapping it in a functable.
--- @param fn function that returns a single result
--- @return memoized function
-local function memoize (fn)
- return setmetatable ({}, {
- __call = function (self, ...)
- local k = tostring ({...})
- local v = self[k]
- if v == nil then
- v = fn (...)
- self[k] = v
- end
- return v
- end
- })
-end
-
---- Evaluate a string.
--- @param s string
--- @return value of string
-local function eval (s)
- return loadstring ("return " .. s)()
-end
-
---- Collect the results of an iterator.
--- @param i iterator
--- @return results of running the iterator on its arguments
-local function collect (i, ...)
- local t = {}
- for e in i (...) do
- table.insert (t, e)
- end
- return t
-end
-
---- Map a function over an iterator.
--- @param f function
--- @param i iterator
--- @return result table
-local function map (f, i, ...)
- local t = {}
- for e in i (...) do
- local r = f (e)
- if r then
- table.insert (t, r)
- end
- end
- return t
-end
-
---- Filter an iterator with a predicate.
--- @param p predicate
--- @param i iterator
--- @return result table containing elements e for which p (e)
-local function filter (p, i, ...)
- local t = {}
- for e in i (...) do
- if p (e) then
- table.insert (t, e)
- end
- end
- return t
-end
-
---- Fold a binary function into an iterator.
--- @param f function
--- @param d initial first argument
--- @param i iterator
--- @return result
-local function fold (f, d, i, ...)
- local r = d
- for e in i (...) do
- r = f (r, e)
- end
- return r
-end
-
---- Functional forms of infix operators.
--- Defined here so that other modules can write to it.
--- @class table
--- @name op
-local op = {
- ["[]"] = function (t, s) return t[s] end,
- ["+"] = function (a, b) return a + b end,
- ["-"] = function (a, b) return a - b end,
- ["*"] = function (a, b) return a * b end,
- ["/"] = function (a, b) return a / b end,
- ["and"] = function (a, b) return a and b end,
- ["or"] = function (a, b) return a or b end,
- ["not"] = function (a) return not a end,
- ["=="] = function (a, b) return a == b end,
- ["~="] = function (a, b) return a ~= b end,
-}
-
-local M = {
- bind = bind,
- collect = collect,
- compose = compose,
- curry = curry,
- eval = eval,
- filter = filter,
- fold = fold,
- id = id,
- map = map,
- memoize = memoize,
- metamethod = metamethod,
- op = op,
-}
-
-return M
diff --git a/lib/std/getopt.lua b/lib/std/getopt.lua
deleted file mode 100644
index cb4d898..0000000
--- a/lib/std/getopt.lua
+++ /dev/null
@@ -1,294 +0,0 @@
---- Simplified getopt, based on Svenne Panne's Haskell GetOpt.prog = {<
--- name = ,
--- [usage = ,]
--- [options = {
--- {{[, ...]}, , [ [, ]]},
--- ...
--- },]
--- [banner = ,]
--- [purpose = ,]
--- [notes = ]
--- } type of option argument is one of Req(uired),
--- Opt(ional)varis a descriptive name for the option argument.getopt.processargs (prog)-opt=arg or -opt arg.--version/-V and
--- --help/-h options automatically;
--- stops program if there was an error, or if --help or
--- --version was used.
--- @param prog table of named parameters
--- @param ... extra arguments for getopt
-local function processargs (prog, ...)
- local totArgs = #_G.arg
- local errors
- prog.options = makeOptions (prog.options)
- _G.arg, M.opt, errors = getopt (_G.arg, prog.options, ...)
- local opt = M.opt
- if (opt.version or opt.help) and prog.banner then
- io.writelines (prog.banner)
- end
- if #errors > 0 then
- local name = prog.name
- prog.name = nil
- if #errors > 0 then
- io.warn (name .. ": " .. table.concat (errors, "\n"))
- io.warn (name .. ": Try '" .. (arg[0] or name) .. " --help' for more help")
- end
- if #errors > 0 then
- error ()
- end
- elseif opt.version then
- version (prog)
- elseif opt.help then
- usage (prog)
- end
- if opt.version or opt.help then
- os.exit ()
- end
-end
-
-
--- Public interface
-return table.merge (M, {
- getopt = getopt,
- processargs = processargs,
- usage = usage,
- usageinfo = usageinfo,
-
- -- camelCase compatibility.
- getOpt = getopt,
- processArgs = processargs,
- usage = usage,
- usageInfo = usageinfo,
-})
diff --git a/lib/std/init.lua b/lib/std/init.lua
new file mode 100644
index 0000000..732d41f
--- /dev/null
+++ b/lib/std/init.lua
@@ -0,0 +1,389 @@
+--[[
+ General Lua Libraries for Lua 5.1, 5.2 & 5.3
+ Copyright (C) 2002-2018 stdlib authors
+]]
+--[[--
+ Enhanced Lua core functions, and others.
+
+ After requiring this module, simply referencing symbols in the submodule
+ hierarchy will load the necessary modules on demand. There are no
+ changes to any global symbols, or monkey patching of core module tables
+ and metatables.
+
+ @todo Write a style guide(indenting/wrapping, capitalisation,
+ function and variable names); library functions should call
+ error, not die; OO vs non-OO(a thorny problem).
+ @todo pre-compile.
+ @corefunction std
+]]
+
+
+local _ = require 'std._base'
+
+local argscheck = _.typecheck and _.typecheck.argscheck
+local compare = _.list.compare
+local maxn = _.table.maxn
+local split = _.string.split
+
+_ = nil
+
+
+local _ENV = require 'std.normalize' {
+ format = 'string.format',
+ match = 'string.match',
+}
+
+
+
+--[[ =============== ]]--
+--[[ Implementation. ]]--
+--[[ =============== ]]--
+
+
+local M
+
+
+local function _assert(expect, fmt, arg1, ...)
+ local msg =(arg1 ~= nil) and format(fmt, arg1, ...) or fmt or ''
+ return expect or error(msg, 2)
+end
+
+
+local function elems(t)
+ -- capture pairs iterator initial state
+ local fn, istate, ctrl = pairs(t)
+ return function(state, _)
+ local v
+ ctrl, v = fn(state, ctrl)
+ if ctrl then
+ return v
+ end
+ end, istate, true -- wrapped initial state
+end
+
+
+local function eval(s)
+ return load('return ' .. s)()
+end
+
+
+local function ielems(t)
+ -- capture pairs iterator initial state
+ local fn, istate, ctrl = ipairs(t)
+ return function(state, _)
+ local v
+ ctrl, v = fn(state, ctrl)
+ if ctrl then
+ return v
+ end
+ end, istate, true -- wrapped initial state
+end
+
+
+local function npairs(t)
+ local m = getmetamethod(t, '__len')
+ local i, n = 0, m and m(t) or maxn(t)
+ return function(t)
+ i = i + 1
+ if i <= n then
+ return i, t[i]
+ end
+ end,
+ t, i
+end
+
+
+local function ripairs(t)
+ local oob = 1
+ while t[oob] ~= nil do
+ oob = oob + 1
+ end
+
+ return function(t, n)
+ n = n - 1
+ if n > 0 then
+ return n, t[n]
+ end
+ end, t, oob
+end
+
+
+local function rnpairs(t)
+ local m = getmetamethod(t, '__len')
+ local oob =(m and m(t) or maxn(t)) + 1
+
+ return function(t, n)
+ n = n - 1
+ if n > 0 then
+ return n, t[n]
+ end
+ end, t, oob
+end
+
+
+local vconvert = setmetatable({
+ string = function(x)
+ return split(x, '%.')
+ end,
+ number = function(x)
+ return {x}
+ end,
+ table = function(x)
+ return x
+ end,
+}, {
+ __call = function(self, x)
+ local fn = self[type(x)] or function()
+ return 0
+ end
+ return fn(x)
+ end,
+})
+
+
+local function vcompare(a, b)
+ return compare(vconvert(a), vconvert(b))
+end
+
+
+local function _require(module, min, too_big, pattern)
+ pattern = pattern or '([%.%d]+)%D*$'
+
+ local s, m = '', require(module)
+ if type(m) == 'table' then
+ s = tostring(m.version or m._VERSION or '')
+ end
+ local v = match(s, pattern) or 0
+ if min then
+ _assert(vcompare(v, min) >= 0, "require '" .. module ..
+ "' with at least version " .. min .. ', but found version ' .. v)
+ end
+ if too_big then
+ _assert(vcompare(v, too_big) < 0, "require '" .. module ..
+ "' with version less than " .. too_big .. ', but found version ' .. v)
+ end
+ return m
+end
+
+
+
+--[[ ================= ]]--
+--[[ Public Interface. ]]--
+--[[ ================= ]]--
+
+
+local function X(decl, fn)
+ return argscheck and argscheck('std.' .. decl, fn) or fn
+end
+
+M = {
+ --- Release version string.
+ -- @field version
+
+
+ --- Core Functions
+ -- @section corefuncs
+
+ --- Enhance core `assert` to also allow formatted arguments.
+ -- @function assert
+ -- @param expect expression, expected to be *truthy*
+ -- @string[opt=''] f format string
+ -- @param[opt] ... arguments to format
+ -- @return value of *expect*, if *truthy*
+ -- @usage
+ -- std.assert(expect == nil, '100% unexpected!')
+ -- std.assert(expect == 'expect', '%s the unexpected!', expect)
+ assert = X('assert(?any, ?string, [any...])', _assert),
+
+ --- Evaluate a string as Lua code.
+ -- @function eval
+ -- @string s string of Lua code
+ -- @return result of evaluating `s`
+ -- @usage
+ -- --> 2
+ -- std.eval 'math.min(2, 10)'
+ eval = X('eval(string)', eval),
+
+ --- Return named metamethod, if any, otherwise `nil`.
+ -- The value found at the given key in the metatable of *x* must be a
+ -- function or have its own `__call` metamethod to qualify as a
+ -- callable. Any other value found at key *n* will cause this function
+ -- to return `nil`.
+ -- @function getmetamethod
+ -- @param x item to act on
+ -- @string n name of metamethod to lookup
+ -- @treturn callable|nil callable metamethod, or `nil` if no metamethod
+ -- @usage
+ -- clone = std.getmetamethod(std.object.prototype, '__call')
+ getmetamethod = X('getmetamethod(?any, string)', getmetamethod),
+
+
+ --- Module Functions
+ -- @section modulefuncs
+
+ --- Enhance core `require` to assert version number compatibility.
+ -- By default match against the last substring of(dot-delimited)
+ -- digits in the module version string.
+ -- @function require
+ -- @string module module to require
+ -- @string[opt] min lowest acceptable version
+ -- @string[opt] too_big lowest version that is too big
+ -- @string[opt] pattern to match version in `module.version` or
+ -- `module._VERSION`(default: `'([%.%d]+)%D*$'`)
+ -- @usage
+ -- -- posix.version == 'posix library for Lua 5.2 / 32'
+ -- posix = require('posix', '29')
+ require = X('require(string, ?string, ?string, ?string)', _require),
+
+ --- Iterator Functions
+ -- @section iteratorfuncs
+
+ --- An iterator over all values of a table.
+ -- If *t* has a `__pairs` metamethod, use that to iterate.
+ -- @function elems
+ -- @tparam table t a table
+ -- @treturn function iterator function
+ -- @treturn table *t*, the table being iterated over
+ -- @return *key*, the previous iteration key
+ -- @see ielems
+ -- @see pairs
+ -- @usage
+ -- --> foo
+ -- --> bar
+ -- --> baz
+ -- --> 5
+ -- std.functional.map(print, std.ielems, {'foo', 'bar', [4]='baz', d=5})
+ elems = X('elems(table)', elems),
+
+ --- An iterator over the integer keyed elements of a table.
+ --
+ -- If *t* has a `__len` metamethod, iterate up to the index it
+ -- returns, otherwise up to the first `nil`.
+ --
+ -- This function does **not** support the Lua 5.2 `__ipairs` metamethod.
+ -- @function ielems
+ -- @tparam table t a table
+ -- @treturn function iterator function
+ -- @treturn table *t*, the table being iterated over
+ -- @treturn int *index*, the previous iteration index
+ -- @see elems
+ -- @see ipairs
+ -- @usage
+ -- --> foo
+ -- --> bar
+ -- std.functional.map(print, std.ielems, {'foo', 'bar', [4]='baz', d=5})
+ ielems = X('ielems(table)', ielems),
+
+ --- An iterator over integer keyed pairs of a sequence.
+ --
+ -- Like Lua 5.1 and 5.3, this iterator returns successive key-value
+ -- pairs with integer keys starting at 1, up to the first `nil` valued
+ -- pair.
+ --
+ -- If there is a `_len` metamethod, keep iterating up to and including
+ -- that element, regardless of any intervening `nil` values.
+ --
+ -- This function does **not** support the Lua 5.2 `__ipairs` metamethod.
+ -- @function ipairs
+ -- @tparam table t a table
+ -- @treturn function iterator function
+ -- @treturn table *t*, the table being iterated over
+ -- @treturn int *index*, the previous iteration index
+ -- @see ielems
+ -- @see npairs
+ -- @see pairs
+ -- @usage
+ -- --> 1 foo
+ -- --> 2 bar
+ -- std.functional.map(print, std.ipairs, {'foo', 'bar', [4]='baz', d=5})
+ ipairs = X('ipairs(table)', ipairs),
+
+ --- Ordered iterator for integer keyed values.
+ -- Like ipairs, but does not stop until the __len or maxn of *t*.
+ -- @function npairs
+ -- @tparam table t a table
+ -- @treturn function iterator function
+ -- @treturn table t
+ -- @see ipairs
+ -- @see rnpairs
+ -- @usage
+ -- --> 1 foo
+ -- --> 2 bar
+ -- --> 3 nil
+ -- --> 4 baz
+ -- std.functional.map(print, std.npairs, {'foo', 'bar', [4]='baz', d=5})
+ npairs = X('npairs(table)', npairs),
+
+ --- Enhance core `pairs` to respect `__pairs` even in Lua 5.1.
+ -- @function pairs
+ -- @tparam table t a table
+ -- @treturn function iterator function
+ -- @treturn table *t*, the table being iterated over
+ -- @return *key*, the previous iteration key
+ -- @see elems
+ -- @see ipairs
+ -- @usage
+ -- --> 1 foo
+ -- --> 2 bar
+ -- --> 4 baz
+ -- --> d 5
+ -- std.functional.map(print, std.pairs, {'foo', 'bar', [4]='baz', d=5})
+ pairs = X('pairs(table)', pairs),
+
+ --- An iterator like ipairs, but in reverse.
+ -- Apart from the order of the elements returned, this function follows
+ -- the same rules as @{ipairs} for determining first and last elements.
+ -- @function ripairs
+ -- @tparam table t any table
+ -- @treturn function iterator function
+ -- @treturn table *t*
+ -- @treturn number `#t + 1`
+ -- @see ipairs
+ -- @see rnpairs
+ -- @usage
+ -- --> 2 bar
+ -- --> 1 foo
+ -- std.functional.map(print, std.ripairs, {'foo', 'bar', [4]='baz', d=5})
+ ripairs = X('ripairs(table)', ripairs),
+
+ --- An iterator like npairs, but in reverse.
+ -- Apart from the order of the elements returned, this function follows
+ -- the same rules as @{npairs} for determining first and last elements.
+ -- @function rnpairs
+ -- @tparam table t a table
+ -- @treturn function iterator function
+ -- @treturn table t
+ -- @see npairs
+ -- @see ripairs
+ -- @usage
+ -- --> 4 baz
+ -- --> 3 nil
+ -- --> 2 bar
+ -- --> 1 foo
+ -- std.functional.map(print, std.rnpairs, {'foo', 'bar', [4]='baz', d=5})
+ rnpairs = X('rnpairs(table)', rnpairs),
+}
+
+
+--- Metamethods
+-- @section Metamethods
+
+return setmetatable(M, {
+ --- Lazy loading of stdlib modules.
+ -- Don't load everything on initial startup, wait until first attempt
+ -- to access a submodule, and then load it on demand.
+ -- @function __index
+ -- @string name submodule name
+ -- @treturn table|nil the submodule that was loaded to satisfy the missing
+ -- `name`, otherwise `nil` if nothing was found
+ -- @usage
+ -- local std = require 'std'
+ -- local Object = std.object.prototype
+ __index = function(self, name)
+ local ok, t = pcall(require, 'std.' .. name)
+ if ok then
+ rawset(self, name, t)
+ return t
+ end
+ end,
+})
diff --git a/lib/std/io.lua b/lib/std/io.lua
index 3553a53..1a2b79f 100644
--- a/lib/std/io.lua
+++ b/lib/std/io.lua
@@ -1,155 +1,322 @@
---- Additions to the io module
-
-local package = require "std.package"
-local string = require "std.string"
-local tree = require "std.tree"
-
-
--- Get an input file handle.
--- @param h file handle or name (default: io.input ())
--- @return file handle, or nil on error
-local function input_handle (h)
- if h == nil then
- h = io.input ()
- elseif type (h) == "string" then
- h = io.open (h)
- end
- return h
-end
+--[[
+ General Lua Libraries for Lua 5.1, 5.2 & 5.3
+ Copyright (C) 2002-2018 stdlib authors
+]]
+--[[--
+ Additions to the core io module.
---- Slurp a file handle.
--- @param h file handle or name (default: io.input ())
--- @return contents of file or handle, or nil if error
-local function slurp (h)
- h = input_handle (h)
- if h then
- local s = h:read ("*a")
- h:close ()
- return s
- end
-end
+ The module table returned by `std.io` also contains all of the entries from
+ the core `io` module table. An hygienic way to import this module, then,
+ is simply to override core `io` locally:
+
+ local io = require 'std.io'
+
+ @corelibrary std.io
+]]
+
+
+local _ = require 'std._base'
+
+local argscheck = _.typecheck and _.typecheck.argscheck
+local catfile = _.io.catfile
+local leaves = _.tree.leaves
+local split = _.string.split
+
+_ = nil
+
+
+local _ENV = require 'std.normalize' {
+ 'io',
+ _G = _G, -- FIXME: don't use the host _G as an API!
+ concat = 'table.concat',
+ dirsep = 'package.dirsep',
+ format = 'string.format',
+ gsub = 'string.gsub',
+ input = 'io.input',
+ insert = 'table.insert',
+ io_type = 'io.type',
+ merge = 'table.merge',
+ open = 'io.open',
+ output = 'io.output',
+ popen = 'io.popen',
+ stderr = 'io.stderr',
+ stdin = 'io.stdin',
+ write = 'io.write',
+}
---- Read a file or file handle into a list of lines.
--- @param h file handle or name (default: io.input ());
--- if h is a handle, the file is closed after reading
--- @return list of lines
-local function readlines (h)
- h = input_handle (h)
- local l = {}
- for line in h:lines () do
- table.insert (l, line)
- end
- h:close ()
- return l
-end
---- Write values adding a newline after each.
--- @param h file handle (default: io.output ()
--- @param ... values to write (as for write)
-local function writelines (h, ...)
- if io.type (h) ~= "file" then
- io.write (h, "\n")
- h = io.output ()
- end
- for v in tree.ileaves ({...}) do
- h:write (v, "\n")
- end
+--[[ =============== ]]--
+--[[ Implementation. ]]--
+--[[ =============== ]]--
+
+
+local M
+
+
+local function input_handle(h)
+ if h == nil then
+ return input()
+ elseif type(h) == 'string' then
+ return open(h)
+ end
+ return h
end
---- Split a directory path into components.
--- Empty components are retained: the root directory becomes {"", ""}.
--- @param path path
--- @return list of path components
-local function splitdir (path)
- return string.split (path, package.dirsep)
+
+local function slurp(file)
+ local h, err = input_handle(file)
+ if h == nil then
+ argerror('std.io.slurp', 1, err, 2)
+ end
+
+ if h then
+ local s = h:read('*a')
+ h:close()
+ return s
+ end
end
---- Concatenate one or more directories and a filename into a path.
--- @param ... path components
--- @return path
-local function catfile (...)
- return table.concat ({...}, package.dirsep)
+
+local function readlines(file)
+ local h, err = input_handle(file)
+ if h == nil then
+ argerror('std.io.readlines', 1, err, 2)
+ end
+
+ local l = {}
+ for line in h:lines() do
+ l[#l + 1] = line
+ end
+ h:close()
+ return l
end
---- Concatenate two or more directories into a path, removing the trailing slash.
--- @param ... path components
--- @return path
-local function catdir (...)
- return (string.gsub (catfile (...), "^$", package.dirsep))
+
+local function writelines(h, ...)
+ if io_type(h) ~= 'file' then
+ write(h, '\n')
+ h = output()
+ end
+ for v in leaves(ipairs, {...}) do
+ h:write(v, '\n')
+ end
end
---- Perform a shell command and return its output.
--- @param c command
--- @return output, or nil if error
-local function shell (c)
- return slurp (io.popen (c))
+
+local function process_files(fn)
+ -- N.B. 'arg' below refers to the global array of command-line args
+ if len(arg) == 0 then
+ insert(arg, '-')
+ end
+ for i, v in ipairs(arg) do
+ if v == '-' then
+ input(stdin)
+ else
+ input(v)
+ end
+ fn(v, i)
+ end
end
---- Process files specified on the command-line.
--- If no files given, process io.stdin; in list of files,
--- - means io.stdin.
--- (name, arg_no)
-local function process_files (f)
- -- N.B. "arg" below refers to the global array of command-line args
- if #arg == 0 then
- table.insert (arg, "-")
- end
- for i, v in ipairs (arg) do
- if v == "-" then
- io.input (io.stdin)
- else
- io.input (v)
- end
- f (v, i)
- end
+
+local function warnfmt(msg, ...)
+ local prefix = ''
+ local prog = rawget(_G, 'prog') or {}
+ local opts = rawget(_G, 'opts') or {}
+ if prog.name then
+ prefix = prog.name .. ':'
+ if prog.line then
+ prefix = prefix .. str(prog.line) .. ':'
+ end
+ elseif prog.file then
+ prefix = prog.file .. ':'
+ if prog.line then
+ prefix = prefix .. str(prog.line) .. ':'
+ end
+ elseif opts.program then
+ prefix = opts.program .. ':'
+ if opts.line then
+ prefix = prefix .. str(opts.line) .. ':'
+ end
+ end
+ if #prefix > 0 then
+ prefix = prefix .. ' '
+ end
+ return prefix .. format(msg, ...)
end
---- Give warning with the name of program and file (if any).
--- @param ... arguments for format
-local function warn (...)
- if prog.name then
- io.stderr:write (prog.name .. ":")
- end
- if prog.file then
- io.stderr:write (prog.file .. ":")
- end
- if prog.line then
- io.stderr:write (tostring (prog.line) .. ":")
- end
- if prog.name or prog.file or prog.line then
- io.stderr:write (" ")
- end
- writelines (io.stderr, string.format (...))
+
+local function warn(msg, ...)
+ writelines(stderr, warnfmt(msg, ...))
end
---- Die with error.
--- @param ... arguments for format
-local function die (...)
- warn (...)
- error ()
+
+
+--[[ ================= ]]--
+--[[ Public Interface. ]]--
+--[[ ================= ]]--
+
+
+local function X(decl, fn)
+ return argscheck and argscheck('std.io.' .. decl, fn) or fn
end
-local M = {
- catdir = catdir,
- catfile = catfile,
- die = die,
- process_files = process_files,
- readlines = readlines,
- shell = shell,
- slurp = slurp,
- splitdir = splitdir,
- warn = warn,
- writelines = writelines,
-
- -- camelCase compatibility.
- processFiles = process_files,
+M = {
+ --- Diagnostic functions
+ -- @section diagnosticfuncs
+
+ --- Die with error.
+ -- This function uses the same rules to build a message prefix
+ -- as @{warn}.
+ -- @function die
+ -- @string msg format string
+ -- @param ... additional arguments to plug format string specifiers
+ -- @see warn
+ -- @usage
+ -- die('oh noes!(%s)', tostring(obj))
+ die = X('die(string, [any...])', function(...)
+ error(warnfmt(...), 0)
+ end),
+
+ --- Give warning with the name of program and file(if any).
+ -- If there is a global `prog` table, prefix the message with
+ -- `prog.name` or `prog.file`, and `prog.line` if any. Otherwise
+ -- if there is a global `opts` table, prefix the message with
+ -- `opts.program` and `opts.line` if any.
+ -- @function warn
+ -- @string msg format string
+ -- @param ... additional arguments to plug format string specifiers
+ -- @see die
+ -- @usage
+ -- local OptionParser = require 'std.optparse'
+ -- local parser = OptionParser 'eg 0\nUsage: eg\n'
+ -- _G.arg, _G.opts = parser:parse(_G.arg)
+ -- if not _G.opts.keep_going then
+ -- require 'std.io'.warn 'oh noes!'
+ -- end
+ warn = X('warn(string, [any...])', warn),
+
+
+ --- Path Functions
+ -- @section pathfuncs
+
+ --- Concatenate directory names into a path.
+ -- @function catdir
+ -- @string ... path components
+ -- @return path without trailing separator
+ -- @see catfile
+ -- @usage
+ -- dirpath = catdir('', 'absolute', 'directory')
+ catdir = X('catdir(string...)', function(...)
+ return(gsub(concat({...}, dirsep), '^$', dirsep))
+ end),
+
+ --- Concatenate one or more directories and a filename into a path.
+ -- @function catfile
+ -- @string ... path components
+ -- @treturn string path
+ -- @see catdir
+ -- @see splitdir
+ -- @usage
+ -- filepath = catfile('relative', 'path', 'filename')
+ catfile = X('catfile(string...)', catfile),
+
+ --- Remove the last dirsep delimited element from a path.
+ -- @function dirname
+ -- @string path file path
+ -- @treturn string a new path with the last dirsep and following
+ -- truncated
+ -- @usage
+ -- dir = dirname '/base/subdir/filename'
+ dirname = X('dirname(string)', function(path)
+ return(gsub(path, catfile('', '[^', ']*$'), ''))
+ end),
+
+ --- Split a directory path into components.
+ -- Empty components are retained: the root directory becomes `{'', ''}`.
+ -- @function splitdir
+ -- @param path path
+ -- @return list of path components
+ -- @see catdir
+ -- @usage
+ -- dir_components = splitdir(filepath)
+ splitdir = X('splitdir(string)', function(path)
+ return split(path, dirsep)
+ end),
+
+
+ --- IO Functions
+ -- @section iofuncs
+
+ --- Process files specified on the command-line.
+ -- Each filename is made the default input source with `io.input`, and
+ -- then the filename and argument number are passed to the callback
+ -- function. In list of filenames, `-` means `io.stdin`. If no
+ -- filenames were given, behave as if a single `-` was passed.
+ -- @todo Make the file list an argument to the function.
+ -- @function process_files
+ -- @tparam fileprocessor fn function called for each file argument
+ -- @usage
+ -- #! /usr/bin/env lua
+ -- -- minimal cat command
+ -- local io = require 'std.io'
+ -- io.process_files(function() io.write(io.slurp()) end)
+ process_files = X('process_files(function)', process_files),
+
+ --- Read a file or file handle into a list of lines.
+ -- The lines in the returned list are not `\n` terminated.
+ -- @function readlines
+ -- @tparam[opt=io.input()] file|string file file handle or name;
+ -- if file is a file handle, that file is closed after reading
+ -- @treturn list lines
+ -- @usage
+ -- list = readlines '/etc/passwd'
+ readlines = X('readlines(?file|string)', readlines),
+
+ --- Perform a shell command and return its output.
+ -- @function shell
+ -- @string c command
+ -- @treturn string output, or nil if error
+ -- @see os.execute
+ -- @usage
+ -- users = shell [[cat /etc/passwd | awk -F: '{print $1;}']]
+ shell = X('shell(string)', function(c) return slurp(popen(c)) end),
+
+ --- Slurp a file handle.
+ -- @function slurp
+ -- @tparam[opt=io.input()] file|string file file handle or name;
+ -- if file is a file handle, that file is closed after reading
+ -- @return contents of file or handle, or nil if error
+ -- @see process_files
+ -- @usage
+ -- contents = slurp(filename)
+ slurp = X('slurp(?file|string)', slurp),
+
+ --- Write values adding a newline after each.
+ -- @function writelines
+ -- @tparam[opt=io.output()] file h open writable file handle;
+ -- the file is **not** closed after writing
+ -- @tparam string|number ... values to write(as for write)
+ -- @usage
+ -- writelines(io.stdout, 'first line', 'next line')
+ writelines = X('writelines(?file|string|number, [string|number...])', writelines),
}
-for k, v in pairs (io) do
- M[k] = M[k] or v
-end
-return M
+return merge(io, M)
+
+
+
+--- Types
+-- @section Types
+
+--- Signature of @{process_files} callback function.
+-- @function fileprocessor
+-- @string filename filename
+-- @int i argument number of *filename*
+-- @usage
+-- local fileprocessor = function(filename, i)
+-- io.write(tostring(i) .. ':\n===\n' .. io.slurp(filename) .. '\n')
+-- end
+-- io.process_files(fileprocessor)
diff --git a/lib/std/list.lua b/lib/std/list.lua
deleted file mode 100644
index da18496..0000000
--- a/lib/std/list.lua
+++ /dev/null
@@ -1,369 +0,0 @@
---- Tables as lists.
-
-local base = require "std.base"
-local compare, elems, ileaves = base.compare, base.elems, base.ileaves
-
-local func = require "std.functional"
-local Object = require "std.object"
-
-local List -- forward declaration
-
---- Append an item to a list.
--- @param l list
--- @param x item
--- @return {l[1], ..., l[#l], x}
-local function append (l, x)
- local r = l {}
- table.insert (r, x)
- return r
-end
-
---- Concatenate lists.
--- @param ... lists
--- @return {l1[1], ...,
--- l1[#l1], ..., ln[1], ...,
--- ln[#ln]}
-local function concat (...)
- return List (base.concat (...))
-end
-
---- An iterator over the elements of a list, in reverse.
--- @param l list to iterate over
--- @return iterator function which returns precessive elements of the list
--- @return the list l as above
--- @return true
-local function relems (l)
- local n = #l + 1
- return function (l)
- n = n - 1
- if n > 0 then
- return l[n]
- end
- end,
- l, true
-end
-
---- Map a function over a list.
--- @param f function
--- @param l list
--- @return result list {f (l[1]), ..., f (l[#l])}
-local function map (l, f)
- return List (func.map (f, elems, l))
-end
-
---- Map a function over a list of lists.
--- @param f function
--- @param ls list of lists
--- @return result list {f (unpack (ls[1]))), ..., f (unpack (ls[#ls]))}
-local function map_with (l, f)
- return List (func.map (func.compose (f, unpack), elems, l))
-end
-
---- Filter a list according to a predicate.
--- @param p predicate (function of one argument returning a boolean)
--- @param l list of lists
--- @return result list containing elements e of
--- l for which p (e) is true
-local function filter (l, p)
- return List (func.filter (p, elems, l))
-end
-
---- Return a sub-range of a list. (The equivalent of string.sub
--- on strings; negative list indices count from the end of the list.)
--- @param l list
--- @param from start of range (default: 1)
--- @param to end of range (default: #l)
--- @return {l[from], ..., l[to]}
-local function sub (l, from, to)
- local r = List {}
- local len = #l
- from = from or 1
- to = to or len
- if from < 0 then
- from = from + len + 1
- end
- if to < 0 then
- to = to + len + 1
- end
- for i = from, to do
- table.insert (r, l[i])
- end
- return r
-end
-
---- Return a list with its first element removed.
--- @param l list
--- @return {l[2], ..., l[#l]}
-local function tail (l)
- return sub (l, 2)
-end
-
---- Fold a binary function through a list left associatively.
--- @param f function
--- @param e element to place in left-most position
--- @param l list
--- @return result
-local function foldl (l, f, e)
- return func.fold (f, e, elems, l)
-end
-
---- Fold a binary function through a list right associatively.
--- @param f function
--- @param e element to place in right-most position
--- @param l list
--- @return result
-local function foldr (l, f, e)
- return List (func.fold (function (x, y) return f (y, x) end,
- e, relems, l))
-end
-
---- Prepend an item to a list.
--- @param l list
--- @param x item
--- @return {x, unpack (l)}
-local function cons (l, x)
- return List {x, unpack (l)}
-end
-
---- Repeat a list.
--- @param l list
--- @param n number of times to repeat
--- @return n copies of l appended together
-local function rep (l, n)
- local r = List {}
- for i = 1, n do
- r = concat (r, l)
- end
- return r
-end
-
---- Reverse a list.
--- @param l list
--- @return list {l[#l], ..., l[1]}
-local function reverse (l)
- local r = List {}
- for i = #l, 1, -1 do
- table.insert (r, l[i])
- end
- return r
-end
-
---- Transpose a list of lists.
--- This function in Lua is equivalent to zip and unzip in more
--- strongly typed languages.
--- @param ls {{l1,1, ..., l1,c}, ...,
--- {lr,1, ..., lr,c}}
--- @return {{l1,1, ..., lr,1}, ...,
--- {l1,c, ..., lr,c}}
-local function transpose (ls)
- local rs, len = List {}, #ls
- for i = 1, math.max (unpack (map (ls, function (l) return #l end))) do
- rs[i] = List {}
- for j = 1, len do
- rs[i][j] = ls[j][i]
- end
- end
- return rs
-end
-
---- Zip lists together with a function.
--- @param f function
--- @param ls list of lists
--- @return {f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])
--- where N = max {map (function (l) return #l end, ls)}
-local function zip_with (ls, f)
- return map_with (transpose (ls), f)
-end
-
---- Project a list of fields from a list of tables.
--- @param f field to project
--- @param l list of tables
--- @return list of f fields
-local function project (l, f)
- return map (l, function (t) return t[f] end)
-end
-
---- Turn a table into a list of pairs.
--- {i1=v1, ...,
--- in=vn}
--- @return list {{i1, v1}, ...,
--- {in, vn}}
-local function enpair (t)
- local ls = List {}
- for i, v in pairs (t) do
- table.insert (ls, List {i, v})
- end
- return ls
-end
-
---- Turn a list of pairs into a table.
--- {{i1, v1}, ...,
--- {in, vn}}
--- @return table {i1=v1, ...,
--- in=vn}
-local function depair (ls)
- local t = {}
- for v in elems (ls) do
- t[v[1]] = v[2]
- end
- return t
-end
-
-
---- Flatten a list.
--- @param l list to flatten
--- @return flattened list
-local function flatten (l)
- local r = List {}
- for v in ileaves (l) do
- table.insert (r, v)
- end
- return r
-end
-
---- Shape a list according to a list of dimensions.
---
--- Dimensions are given outermost first and items from the original
--- list are distributed breadth first; there may be one 0 indicating
--- an indefinite number. Hence, {0} is a flat list,
--- {1} is a singleton, {2, 0} is a list of
--- two lists, and {0, 2} is a list of pairs.
--- {d1, ..., dn}
--- @param l list to reshape
--- @return reshaped list
--- FIXME: Use ileaves instead of flatten (needs a while instead of a
--- for in fill function)
-local function shape (l, s)
- l = flatten (l)
- -- Check the shape and calculate the size of the zero, if any
- local size = 1
- local zero
- for i, v in ipairs (s) do
- if v == 0 then
- if zero then -- bad shape: two zeros
- return nil
- else
- zero = i
- end
- else
- size = size * v
- end
- end
- if zero then
- s[zero] = math.ceil (#l / size)
- end
- local function fill (i, d)
- if d > #s then
- return l[i], i + 1
- else
- local r = List {}
- for j = 1, s[d] do
- local e
- e, i = fill (i, d + 1)
- table.insert (r, e)
- end
- return r, i
- end
- end
- return (fill (1, 1))
-end
-
---- Make an index of a list of tables on a given field
--- @param f field
--- @param l list of tables {t1, ...,
--- tn}
--- @return index {t1[f]=1, ...,
--- tn[f]=n}
-local function index_key (l, f)
- local r = List {}
- for i, v in ipairs (l) do
- local k = v[f]
- if k then
- r[k] = i
- end
- end
- return r
-end
-
---- Copy a list of tables, indexed on a given field
--- @param f field whose value should be used as index
--- @param l list of tables {i1=t1, ...,
--- in=tn}
--- @return index {t1[f]=t1, ...,
--- tn[f]=tn}
-local function index_value (l, f)
- local r = List {}
- for i, v in ipairs (l) do
- local k = v[f]
- if k then
- r[k] = v
- end
- end
- return r
-end
-
-
-List = Object {
- -- Derived object type.
- _type = "List",
-
- __concat = concat, -- list .. table
- __add = append, -- list + element
-
- -- list == list retains its referential meaning
- --
- -- list < list = list.compare returns < 0
- __lt = function (l, m) return compare (l, m) < 0 end,
-
- -- list <= list = list.compare returns <= 0
- __le = function (l, m) return compare (l, m) <= 0 end,
-
- -- list:method ()
- __index = {
- append = append,
- compare = compare,
- concat = concat,
- cons = cons,
- depair = depair,
- elems = elems,
- enpair = enpair,
- filter = filter,
- flatten = flatten,
- foldl = foldl,
- foldr = foldr,
- index_key = index_key,
- index_value = index_value,
- map = map,
- map_with = map_with,
- project = project,
- relems = relems,
- rep = rep,
- reverse = reverse,
- shape = shape,
- sub = sub,
- tail = tail,
- transpose = transpose,
- zip_with = zip_with,
-
- -- camelCase compatibility.
- indexKey = index_key,
- indexValue = index_value,
- mapWith = map_with,
- zipWith = zip_with,
- },
-}
-
-
--- Function forms of operators
-func.op[".."] = concat
-
-return List
diff --git a/lib/std/math.lua b/lib/std/math.lua
index 3a3f9ae..d955862 100644
--- a/lib/std/math.lua
+++ b/lib/std/math.lua
@@ -1,40 +1,92 @@
---- Additions to the math module.
+--[[
+ General Lua Libraries for Lua 5.1, 5.2 & 5.3
+ Copyright (C) 2002-2018 stdlib authors
+]]
+--[[--
+ Additions to the core math module.
+
+ The module table returned by `std.math` also contains all of the entries from
+ the core math table. An hygienic way to import this module, then, is simply
+ to override the core `math` locally:
+
+ local math = require 'std.math'
+
+ @corelibrary std.math
+]]
+
+
+local _ = require 'std._base'
+
+local argscheck = _.typecheck and _.typecheck.argscheck
+
+_ = nil
+
+
+local _ENV = require 'std.normalize' {
+ 'math',
+ merge = 'table.merge',
+}
+
+
+
+--[[ ================= ]]--
+--[[ Implementatation. ]]--
+--[[ ================= ]]--
+
+
+local M
+
local _floor = math.floor
---- Extend math.floor to take the number of decimal places.
--- @param n number
--- @param p number of decimal places to truncate to (default: 0)
--- @return n truncated to p decimal places
-local function floor (n, p)
- if p and p ~= 0 then
- local e = 10 ^ p
- return _floor (n * e) / e
- else
- return _floor (n)
- end
+local function floor(n, p)
+ if(p or 0) == 0 then
+ return _floor(n)
+ end
+ local e = 10 ^ p
+ return _floor(n * e) / e
end
---- Round a number to a given number of decimal places
--- @param n number
--- @param p number of decimal places to round to (default: 0)
--- @return n rounded to p decimal places
-local function round (n, p)
- local e = 10 ^ (p or 0)
- return _floor (n * e + 0.5) / e
+
+local function round(n, p)
+ local e = 10 ^(p or 0)
+ return _floor(n * e + 0.5) / e
end
-local M = {
- floor = floor,
- round = round,
- -- Core Lua function implementations.
- _floor = _floor,
-}
+--[[ ================= ]]--
+--[[ Public Interface. ]]--
+--[[ ================= ]]--
+
-for k, v in pairs (math) do
- M[k] = M[k] or v
+local function X(decl, fn)
+ return argscheck and argscheck('std.math.' .. decl, fn) or fn
end
-return M
+
+M = {
+ --- Core Functions
+ -- @section corefuncs
+
+ --- Extend `math.floor` to take the number of decimal places.
+ -- @function floor
+ -- @number n number
+ -- @int[opt=0] p number of decimal places to truncate to
+ -- @treturn number `n` truncated to `p` decimal places
+ -- @usage
+ -- tenths = floor(magnitude, 1)
+ floor = X('floor(number, ?int)', floor),
+
+ --- Round a number to a given number of decimal places.
+ -- @function round
+ -- @number n number
+ -- @int[opt=0] p number of decimal places to round to
+ -- @treturn number `n` rounded to `p` decimal places
+ -- @usage
+ -- roughly = round(exactly, 2)
+ round = X('round(number, ?int)', round),
+}
+
+
+return merge(math, M)
diff --git a/lib/std/modules.lua b/lib/std/modules.lua
deleted file mode 100644
index 00b04de..0000000
--- a/lib/std/modules.lua
+++ /dev/null
@@ -1,17 +0,0 @@
-return {
- -- true => module symbols injected into equivalent core namespace
- -- with `require 'std'`:
- debug = true,
- debug_init = false,
- functional = false,
- getopt = false,
- io = true,
- list = false,
- math = true,
- package = true,
- set = false,
- strbuf = false,
- string = true,
- table = true,
- tree = false,
-}
diff --git a/lib/std/object.lua b/lib/std/object.lua
deleted file mode 100644
index ea9a5ac..0000000
--- a/lib/std/object.lua
+++ /dev/null
@@ -1,185 +0,0 @@
---- Prototype-based objects
--- _init field is a list:
--- object/Class = prototype {value, ...; field = value, ...}_init._init field is a function:
--- object/Class = prototype (value, ...)_init function._".object.fieldobject:method (...)Class.method (object, ...)object.field = xfunction object:method (...) ... endpackage.config (undocumented
--- in 5.1; see luaconf.h for C equivalents).
--- @class table
--- @name package
--- @field dirsep directory separator
--- @field pathsep path separator
--- @field path_mark string that marks substitution points in a path template
--- @field execdir (Windows only) replaced by the executable's directory in a path
--- @field igmark Mark to ignore all before it when building luaopen_ function name.
-M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark =
- string.match (package.config, "^([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)\n([^\n]+)")
-
-for k, v in pairs (package) do
- M[k] = M[k] or v
+--[[
+ General Lua Libraries for Lua 5.1, 5.2 & 5.3
+ Copyright (C) 2002-2018 stdlib authors
+]]
+--[[--
+ Additions to the core package module.
+
+ The module table returned by `std.package` also contains all of the entries
+ from the core `package` table. An hygienic way to import this module, then, is
+ simply to override core `package` locally:
+
+ local package = require 'std.package'
+
+ Manage `package.path` with normalization, duplicate removal,
+ insertion & removal of elements and automatic folding of '/' and '?'
+ onto `package.dirsep` and `package.pathmark`, for easy addition of
+ new paths. For example, instead of all this:
+
+ lib = std.io.catfile('.', 'lib', package.pathmark .. '.lua')
+ paths = std.string.split(package.path, package.pathsep)
+ for i, path in ipairs(paths) do
+ -- ... lots of normalization code...
+ end
+ i = 1
+ while i <= #paths do
+ if paths[i] == lib then
+ table.remove(paths, i)
+ else
+ i = i + 1
+ end
+ end
+ table.insert(paths, 1, lib)
+ package.path = table.concat(paths, package.pathsep)
+
+ You can now write just:
+
+ package.path = package.normalize('./lib/?.lua', package.path)
+
+ @corelibrary std.package
+]]
+
+
+local _ = require 'std._base'
+
+local argscheck = _.typecheck and _.typecheck.argscheck
+local catfile = _.io.catfile
+local escape_pattern = _.string.escape_pattern
+local invert = _.table.invert
+local split = _.string.split
+
+_ = nil
+
+local _ENV = require 'std.normalize' {
+ 'package',
+ concat = 'table.concat',
+ dirsep = 'package.dirsep',
+ gsub = 'string.gsub',
+ merge = 'table.merge',
+ pathmark = 'package.pathmark',
+ pathsep = 'package.pathsep',
+ string_find = 'string.find',
+ table_insert = 'table.insert',
+ table_remove = 'table.remove',
+}
+
+
+
+--[[ =============== ]]--
+--[[ Implementation. ]]--
+--[[ =============== ]]--
+
+
+--- Make named constants for `package.config`
+-- (undocumented in 5.1; see luaconf.h for C equivalents).
+-- @table package
+-- @string dirsep directory separator
+-- @string pathsep path separator
+-- @string pathmark string that marks substitution points in a path template
+-- @string execdir(Windows only) replaced by the executable's directory in a path
+-- @string igmark Mark to ignore all before it when building `luaopen_` function name.
+
+
+local function pathsub(path)
+ return gsub(path, '%%?.', function(capture)
+ if capture == '?' then
+ return pathmark
+ elseif capture == '/' then
+ return dirsep
+ else
+ return gsub(capture, '^%%', '', 1)
+ end
+ end)
+end
+
+
+local function find(pathstrings, patt, init, plain)
+ local paths = split(pathstrings, pathsep)
+ if plain then
+ patt = escape_pattern(patt)
+ end
+ init = init or 1
+ if init < 0 then
+ init = #paths - init
+ end
+ for i = init, #paths do
+ if string_find(paths[i], patt) then
+ return i, paths[i]
+ end
+ end
+end
+
+
+local function normalize(...)
+ local i, paths, pathstrings = 1, {}, concat({...}, pathsep)
+ for _, path in ipairs(split(pathstrings, pathsep)) do
+ path = gsub(pathsub(path), catfile('^[^', ']'), catfile('.', '%0'))
+ path = gsub(path, catfile('', '%.', ''), dirsep)
+ path = gsub(path, catfile('', '%.$'), '')
+ path = gsub(path, catfile('^%.', '%..', ''), catfile('..', ''))
+ path = gsub(path, catfile('', '$'), '')
+
+ -- Carefully remove redundant /foo/../ matches.
+ repeat
+ local again = false
+ path = gsub(path, catfile('', '([^', ']+)', '%.%.', ''),
+ function(dir1)
+ if dir1 == '..' then -- don't remove /../../
+ return catfile('', '..', '..', '')
+ else
+ again = true
+ return dirsep
+ end
+ end)
+ path = gsub(path, catfile('', '([^', ']+)', '%.%.$'),
+ function(dir1)
+ if dir1 == '..' then -- don't remove /../..
+ return catfile('', '..', '..')
+ else
+ again = true
+ return ''
+ end
+ end)
+ until again == false
+
+ -- Build an inverted table of elements to eliminate duplicates after
+ -- normalization.
+ if not paths[path] then
+ paths[path], i = i, i + 1
+ end
+ end
+ return concat(invert(paths), pathsep)
+end
+
+
+local function insert(pathstrings, ...)
+ local paths = split(pathstrings, pathsep)
+ table_insert(paths, ...)
+ return normalize(unpack(paths, 1, len(paths)))
+end
+
+
+local function mappath(pathstrings, callback, ...)
+ for _, path in ipairs(split(pathstrings, pathsep)) do
+ local r = callback(path, ...)
+ if r ~= nil then
+ return r
+ end
+ end
+end
+
+
+local function remove(pathstrings, pos)
+ local paths = split(pathstrings, pathsep)
+ table_remove(paths, pos)
+ return concat(paths, pathsep)
+end
+
+
+
+--[[ ================= ]]--
+--[[ Public Interface. ]]--
+--[[ ================= ]]--
+
+
+local function X(decl, fn)
+ return argscheck and argscheck('std.package.' .. decl, fn) or fn
end
-return M
+
+local M = {
+ --- Look for a path segment match of *patt* in *pathstrings*.
+ -- @function find
+ -- @string pathstrings `pathsep` delimited path elements
+ -- @string patt a Lua pattern to search for in *pathstrings*
+ -- @int[opt=1] init element(not byte index!) to start search at.
+ -- Negative numbers begin counting backwards from the last element
+ -- @bool[opt=false] plain unless false, treat *patt* as a plain
+ -- string, not a pattern. Note that if *plain* is given, then *init*
+ -- must be given as well.
+ -- @return the matching element number(not byte index!) and full text
+ -- of the matching element, if any; otherwise nil
+ -- @usage
+ -- i, s = find(package.path, '^[^' .. package.dirsep .. '/]')
+ find = X('find(string, string, ?int, ?boolean|:plain)', find),
+
+ --- Insert a new element into a `package.path` like string of paths.
+ -- @function insert
+ -- @string pathstrings a `package.path` like string
+ -- @int[opt=n+1] pos element index at which to insert *value*, where `n` is
+ -- the number of elements prior to insertion
+ -- @string value new path element to insert
+ -- @treturn string a new string with the new element inserted
+ -- @usage
+ -- package.path = insert(package.path, 1, install_dir .. '/?.lua')
+ insert = X('insert(string, [int], string)', insert),
+
+ --- Call a function with each element of a path string.
+ -- @function mappath
+ -- @string pathstrings a `package.path` like string
+ -- @tparam mappathcb callback function to call for each element
+ -- @param ... additional arguments passed to *callback*
+ -- @return nil, or first non-nil returned by *callback*
+ -- @usage
+ -- mappath(package.path, searcherfn, transformfn)
+ mappath = X('mappath(string, function, [any...])', mappath),
+
+ --- Normalize a path list.
+ -- Removing redundant `.` and `..` directories, and keep only the first
+ -- instance of duplicate elements. Each argument can contain any number
+ -- of `pathsep` delimited elements; wherein characters are subject to
+ -- `/` and `?` normalization, converting `/` to `dirsep` and `?` to
+ -- `pathmark`(unless immediately preceded by a `%` character).
+ -- @function normalize
+ -- @param ... path elements
+ -- @treturn string a single normalized `pathsep` delimited paths string
+ -- @usage
+ -- package.path = normalize(user_paths, sys_paths, package.path)
+ normalize = X('normalize(string...)', normalize),
+
+ --- Remove any element from a `package.path` like string of paths.
+ -- @function remove
+ -- @string pathstrings a `package.path` like string
+ -- @int[opt=n] pos element index from which to remove an item, where `n`
+ -- is the number of elements prior to removal
+ -- @treturn string a new string with given element removed
+ -- @usage
+ -- package.path = remove(package.path)
+ remove = X('remove(string, ?int)', remove),
+}
+
+
+return merge(package, M)
+
+
+--- Types
+-- @section Types
+
+--- Function signature of a callback for @{mappath}.
+-- @function mappathcb
+-- @string element an element from a `pathsep` delimited string of
+-- paths
+-- @param ... additional arguments propagated from @{mappath}
+-- @return non-nil to break, otherwise continue with the next element
diff --git a/lib/std/set.lua b/lib/std/set.lua
deleted file mode 100644
index 91c28d7..0000000
--- a/lib/std/set.lua
+++ /dev/null
@@ -1,195 +0,0 @@
--- Sets.
-
-local list = require "std.base"
-local Object = require "std.object"
-
-local Set -- forward declaration
-
--- Primitive methods (know about representation)
--- The representation is a table whose tags are the elements, and
--- whose values are true.
-
---- Say whether an element is in a set
--- @param s set
--- @param e element
--- @return true if e is in set, false
--- otherwise
-local function member (s, e)
- return rawget (s, e) == true
-end
-
---- Insert an element into a set
--- @param s set
--- @param e element
--- @return the modified set
-local function insert (s, e)
- rawset (s, e, true)
- return s
-end
-
---- Delete an element from a set
--- @param s set
--- @param e element
--- @return the modified set
-local function delete (s, e)
- rawset (s, e, nil)
- return s
-end
-
---- Iterator for sets
--- TODO: Make the iterator return only the key
-local function elems (s)
- return pairs (s)
-end
-
-
--- High level methods (representation-independent)
-
-local difference, symmetric_difference, intersection, union, subset, equal
-
---- Find the difference of two sets
--- @param s set
--- @param t set
--- @return s with elements of t removed
-function difference (s, t)
- if Object.type (t) == "table" then
- t = Set (t)
- end
- local r = Set {}
- for e in elems (s) do
- if not member (t, e) then
- insert (r, e)
- end
- end
- return r
-end
-
---- Find the symmetric difference of two sets
--- @param s set
--- @param t set
--- @return elements of s and t that are in s or t but not both
-function symmetric_difference (s, t)
- if Object.type (t) == "table" then
- t = Set (t)
- end
- return difference (union (s, t), intersection (t, s))
-end
-
---- Find the intersection of two sets
--- @param s set
--- @param t set
--- @return set intersection of s and t
-function intersection (s, t)
- if Object.type (t) == "table" then
- t = Set (t)
- end
- local r = Set {}
- for e in elems (s) do
- if member (t, e) then
- insert (r, e)
- end
- end
- return r
-end
-
---- Find the union of two sets
--- @param s set
--- @param t set or set-like table
--- @return set union of s and t
-function union (s, t)
- if Object.type (t) == "table" then
- t = Set (t)
- end
- local r = Set {}
- for e in elems (s) do
- insert (r, e)
- end
- for e in elems (t) do
- insert (r, e)
- end
- return r
-end
-
---- Find whether one set is a subset of another
--- @param s set
--- @param t set
--- @return true if s is a subset of t, false
--- otherwise
-function subset (s, t)
- if Object.type (t) == "table" then
- t = Set (t)
- end
- for e in elems (s) do
- if not member (t, e) then
- return false
- end
- end
- return true
-end
-
---- Find whether one set is a proper subset of another
--- @param s set
--- @param t set
--- @return true if s is a proper subset of t, false otherwise
-function propersubset (s, t)
- if Object.type (t) == "table" then
- t = Set (t)
- end
- return subset (s, t) and not subset (t, s)
-end
-
---- Find whether two sets are equal
--- @param s set
--- @param t set
--- @return true if sets are equal, false
--- otherwise
-function equal (s, t)
- return subset (s, t) and subset (t, s)
-end
-
-
-Set = Object {
- -- Derived object type.
- _type = "Set",
-
- -- Initialise.
- _init = function (self, t)
- for e in list.elems (t) do
- insert (self, e)
- end
- return self
- end,
-
- __add = union, -- set + table = union
- __sub = difference, -- set - table = set difference
- __mul = intersection, -- set * table = intersection
- __div = symmetric_difference, -- set / table = symmetric difference
- __le = subset, -- set <= table = subset
- __lt = propersubset, -- set < table = proper subset
-
- __totable = function (self)
- local t = {}
- for e in elems (self) do
- table.insert (t, e)
- end
- table.sort (t)
- return t
- end,
-
- -- set:method ()
- __index = {
- delete = delete,
- difference = difference,
- elems = elems,
- equal = equal,
- insert = insert,
- intersection = intersection,
- member = member,
- propersubset = propersubset,
- subset = subset,
- symmetric_difference = symmetric_difference,
- union = union,
- },
-}
-
-return Set
diff --git a/lib/std/strbuf.lua b/lib/std/strbuf.lua
deleted file mode 100644
index f7cb61e..0000000
--- a/lib/std/strbuf.lua
+++ /dev/null
@@ -1,37 +0,0 @@
---- String buffers.
-
-local Object = require "std.object"
-
-
---- Add a string to a buffer
--- @param b buffer
--- @param s string to add
--- @return buffer
-local function concat (b, s)
- table.insert (b, s)
- return b
-end
-
-
---- Convert a buffer to a string
--- @param b buffer
--- @return string
-local function tostring (b)
- return table.concat (b)
-end
-
-
-return Object {
- -- Derived object type.
- _type = "StrBuf",
-
- -- Metamethods.
- __concat = concat, -- buffer .. string
- __tostring = tostring, -- tostring (buffer)
-
- -- strbuf:method ()
- __index = {
- concat = concat,
- tostring = tostring,
- },
-}
diff --git a/lib/std/strict.lua b/lib/std/strict.lua
deleted file mode 100644
index 1eb22c7..0000000
--- a/lib/std/strict.lua
+++ /dev/null
@@ -1,40 +0,0 @@
---- Checks uses of undeclared global variables.
--- All global variables must be 'declared' through a regular
--- assignment (even assigning nil will do) in a top-level
--- chunk before being used anywhere or assigned to inside a function.
--- From Lua distribution (etc/strict.lua).
--- @class module
--- @name strict
-
-local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget
-
-local mt = getmetatable (_G)
-if mt == nil then
- mt = {}
- setmetatable (_G, mt)
-end
-
-mt.__declared = {}
-
-local function what ()
- local d = getinfo (3, "S")
- return d and d.what or "C"
-end
-
-mt.__newindex = function (t, n, v)
- if not mt.__declared[n] then
- local w = what ()
- if w ~= "main" and w ~= "C" then
- error ("assignment to undeclared variable '" .. n .. "'", 2)
- end
- mt.__declared[n] = true
- end
- rawset (t, n, v)
-end
-
-mt.__index = function (t, n)
- if not mt.__declared[n] and what () ~= "C" then
- error ("variable '" .. n .. "' is not declared", 2)
- end
- return rawget (t, n)
-end
diff --git a/lib/std/string.lua b/lib/std/string.lua
index 20ac896..6ad9014 100644
--- a/lib/std/string.lua
+++ b/lib/std/string.lua
@@ -1,560 +1,505 @@
---- Additions to the string module
--- TODO: Pretty printing (use in getopt); see source for details.
+--[[
+ General Lua Libraries for Lua 5.1, 5.2 & 5.3
+ Copyright (C) 2002-2018 stdlib authors
+]]
+--[[--
+ Additions to the core string module.
-local func = require "std.functional"
-local list = require "std.list"
-local StrBuf = require "std.strbuf"
-local table = require "std.table"
+ The module table returned by `std.string` also contains all of the entries
+ from the core string table. An hygienic way to import this module, then, is
+ simply to override the core `string` locally:
-local M = {}
+ local string = require 'std.string'
---- Extend to work better with one argument.
--- If only one argument is passed, no formatting is attempted.
--- @param f format
--- @param ... arguments to format
--- @return formatted string
-local _format = string.format
-local function format (f, arg1, ...)
- if arg1 == nil then
- return f
- else
- return _format (f, arg1, ...)
- end
-end
+ @corelibrary std.string
+]]
---- Extend to allow formatted arguments.
--- @param v value to assert
--- @param f format
--- @param ... arguments to format
--- @return value
-local _assert = assert
-local function assert (v, f, ...)
- if not v then
- if f == nil then
- f = ""
- end
- error (format (f, ...))
- end
- return v
-end
---- Do find, returning captures as a list.
--- @param s target string
--- @param p pattern
--- @param init start position (default: 1)
--- @param plain inhibit magic characters (default: nil)
--- @return start of match, end of match, table of captures
-local function tfind (s, p, init, plain)
- assert (type (s) == "string",
- "bad argument #1 to 'tfind' (string expected, got " .. type (s) .. ")")
- assert (type (p) == "string",
- "bad argument #2 to 'tfind' (string expected, got " .. type (p) .. ")")
- local function pack (from, to, ...)
- return from, to, {...}
- end
- return pack (p.find (s, p, init, plain))
-end
+local _ = require 'std._base'
---- Do multiple finds on a string.
--- @param s target string
--- @param p pattern
--- @param init start position (default: 1)
--- @param plain inhibit magic characters (default: nil)
--- @return list of {from, to; capt = {captures}}
-local function finds (s, p, init, plain)
- init = init or 1
- local l = {}
- local from, to, r
- repeat
- from, to, r = tfind (s, p, init, plain)
- if from ~= nil then
- table.insert (l, {from, to, capt = r})
- init = to + 1
- end
- until not from
- return l
-end
+local argscheck = _.typecheck and _.std.typecheck.argscheck
+local escape_pattern = _.string.escape_pattern
+local split = _.string.split
---- Split a string at a given separator.
--- FIXME: Consider Perl and Python versions.
--- @param s string to split
--- @param sep separator pattern
--- @return list of strings
-local function split (s, sep)
- -- finds gets a list of {from, to, capt = {}} lists; we then
- -- flatten the result, discarding the captures, and prepend 0 (1
- -- before the first character) and append 0 (1 after the last
- -- character), and then read off the result in pairs.
- local pairs = list.concat ({0}, list.flatten (finds (s, sep)), {0})
- local l = {}
- for i = 1, #pairs, 2 do
- table.insert (l, string.sub (s, pairs[i] + 1, pairs[i + 1] - 1))
- end
- return l
-end
+_ = nil
---- Require a module with a particular version
--- @param module module to require
--- @param min lowest acceptable version (default: any)
--- @param too_big lowest version that is too big (default: none)
--- @pattern pattern to match version in module.version or
--- module.VERSION (default: ".*[%.%d]+"
-local function require_version (module, min, too_big, pattern)
- local function version_to_list (v)
- return list.new (split (v, "%."))
- end
- local function module_version (module, pattern)
- return version_to_list (string.match (module.version or module._VERSION,
- pattern or ".*[%.%d]+"))
- end
- local m = require (module)
- if min then
- assert (module_version (m, pattern) >= version_to_list (min))
- end
- if too_big then
- assert (module_version (m, pattern) < version_to_list (too_big))
- end
- return m
-end
+local _ENV = require 'std.normalize' {
+ 'string',
+ abs = 'math.abs',
+ concat = 'table.concat',
+ find = 'string.find',
+ floor = 'math.floor',
+ format = 'string.format',
+ gsub = 'string.gsub',
+ insert = 'table.insert',
+ match = 'string.match',
+ merge = 'table.merge',
+ render = 'string.render',
+ sort = 'table.sort',
+ sub = 'string.sub',
+ upper = 'string.upper',
+}
--- Write pretty-printing based on:
---
--- John Hughes's and Simon Peyton Jones's Pretty Printer Combinators
---
--- Based on "The Design of a Pretty-printing Library in Advanced
--- Functional Programming", Johan Jeuring and Erik Meijer (eds), LNCS 925
--- http://www.cs.chalmers.se/~rjmh/Papers/pretty.ps
--- Heavily modified by Simon Peyton Jones, Dec 96
---
--- Haskell types:
--- data Doc list of lines
--- quote :: Char -> Char -> Doc -> Doc Wrap document in ...
--- (<>) :: Doc -> Doc -> Doc Beside
--- (<+>) :: Doc -> Doc -> Doc Beside, separated by space
--- ($$) :: Doc -> Doc -> Doc Above; if there is no overlap it "dovetails" the two
--- nest :: Int -> Doc -> Doc Nested
--- punctuate :: Doc -> [Doc] -> [Doc] punctuate p [d1, ... dn] = [d1 <> p, d2 <> p, ... dn-1 <> p, dn]
--- render :: Int Line length
--- -> Float Ribbons per line
--- -> (TextDetails -> a -> a) What to do with text
--- -> a What to do at the end
--- -> Doc The document
--- -> a Result
-
-
---- Turn tables into strings with recursion detection.
--- N.B. Functions calling render should not recurse, or recursion
--- detection will not work.
--- @see render_OpenRenderer, render_CloseRenderer
--- @see render_ElementRenderer, render_PairRenderer
--- @see render_SeparatorRenderer
--- @param x object to convert to string
--- @param open open table renderer
--- @param close close table renderer
--- @param elem element renderer
--- @param pair pair renderer
--- @param sep separator renderer
--- @return string representation
-local function render (x, open, close, elem, pair, sep, roots)
- local function stop_roots (x)
- return roots[x] or render (x, open, close, elem, pair, sep, table.clone (roots))
- end
- roots = roots or {}
- if type (x) ~= "table" or func.metamethod (x, "__tostring") then
- return elem (x)
- else
- local s = StrBuf {}
- s = s .. open (x)
- roots[x] = elem (x)
-
- -- create a sorted list of keys
- local ord = {}
- for k, _ in pairs (x) do table.insert (ord, k) end
- table.sort (ord, function (a, b) return tostring (a) < tostring (b) end)
-
- -- render x elements in order
- local i, v = nil, nil
- for _, j in ipairs (ord) do
- local w = x[j]
- s = s .. sep (x, i, v, j, w) .. pair (x, j, w, stop_roots (j), stop_roots (w))
- i, v = j, w
- end
- s = s .. sep (x, i, v, nil, nil) .. close (x)
- return s:tostring ()
- end
-end
----
--- @class function
--- @name render_OpenRenderer
--- @param t table
--- @return open table string
-
----
--- @class function
--- @name render_CloseRenderer
--- @param t table
--- @return close table string
-
----
--- @class function
--- @name render_ElementRenderer
--- @param e element
--- @return element string
-
----
--- @class function
--- @name render_PairRenderer
--- N.B. the function should not try to render i and v, or treat
--- them recursively.
--- @param t table
--- @param i index
--- @param v value
--- @param is index string
--- @param vs value string
--- @return element string
-
----
--- @class function
--- @name render_SeparatorRenderer
--- @param t table
--- @param i preceding index (nil on first call)
--- @param v preceding value (nil on first call)
--- @param j following index (nil on last call)
--- @param w following value (nil on last call)
--- @return separator string
-
---- Extend tostring to work better on tables.
--- @class function
--- @name tostring
--- @param x object to convert to string
--- @return string representation
-local _tostring = _G.tostring
-local function tostring (x)
- return render (x,
- function () return "{" end,
- function () return "}" end,
- _tostring,
- function (t, _, _, i, v)
- return i .. "=" .. v
- end,
- function (_, i, _, j)
- if i and j then
- return ","
- end
- return ""
- end)
-end
+--[[ =============== ]]--
+--[[ Implementation. ]]--
+--[[ =============== ]]--
---- Pretty-print a table.
--- @param t table to print
--- @param indent indent between levels ["\t"]
--- @param spacing space before every line
--- @return pretty-printed string
-local function prettytostring (t, indent, spacing)
- indent = indent or "\t"
- spacing = spacing or ""
- return render (t,
- function ()
- local s = spacing .. "{"
- spacing = spacing .. indent
- return s
- end,
- function ()
- spacing = string.gsub (spacing, indent .. "$", "")
- return spacing .. "}"
- end,
- function (x)
- if type (x) == "string" then
- return format ("%q", x)
- else
- return tostring (x)
- end
- end,
- function (x, i, v, is, vs)
- local s = spacing
- if type (i) ~= "string" or i:match "[^%w_]" then
- s = s .. "["
- if type (i) == "table" then
- s = s .. "\n"
- end
- s = s .. is
- if type (i) == "table" then
- s = s .. "\n"
- end
- s = s .. "]"
- else
- s = s .. i
- end
- s = s .. " ="
- if type (v) == "table" then
- s = s .. "\n"
- else
- s = s .. " "
- end
- s = s .. vs
- return s
- end,
- function (_, i)
- local s = "\n"
- if i then
- s = "," .. s
- end
- return s
- end)
-end
+local M
---- Convert a value to a string.
--- The string can be passed to dostring to retrieve the value.
--- string.sub (s, i, i) if i is a number, or
--- falls back to any previous metamethod (by default, string methods)
-local old__index = getmetatable ("").__index
-getmetatable ("").__index = function (s, i)
- if type (i) == "number" then
- return s:sub (i, i)
- -- Fall back to module metamethods
- else
- return M[i]
- end
+local concatvfns = {
+ elem = tostring,
+ term = function(x)
+ return type(x) ~= 'table' or getmetamethod(x, '__tostring')
+ end,
+ sort = function(keys)
+ return keys
+ end,
+ open = function(x) return '{' end,
+ close = function(x) return '}' end,
+ pair = function(x, kp, vp, k, v, kstr, vstr, seqp)
+ return toqstring(k, kstr) .. '=' .. toqstring(v, vstr)
+ end,
+ sep = function(x, kp, vp, kn, vn, seqp)
+ return kp ~= nil and kn ~= nil and ',' or ''
+ end,
+}
+
+
+local function __concat(s, o)
+ -- Don't use normalize.str here, because we don't want ASCII escape rendering.
+ return render(s, concatvfns) .. render(o, concatvfns)
end
---- Give strings an append metamethod.
--- @param s string
--- @param c character (1-character string)
--- @return s .. c
-getmetatable ("").__append = function (s, c)
- return s .. c
+
+local function __index(s, i)
+ if type(i) == 'number' then
+ return sub(s, i, i)
+ else
+ -- Fall back to module metamethods
+ return M[i]
+ end
end
---- Give strings a concat metamethod.
--- @param s string
--- @param o object
--- @return s .. tostring (o)
-getmetatable ("").__concat = function (s, o)
- return tostring (s) .. tostring (o)
+
+local _format = string.format
+
+local function format(f, arg1, ...)
+ return(arg1 ~= nil) and _format(f, arg1, ...) or f
end
---- Capitalise each word in a string.
--- @param s string
--- @return capitalised string
-local function caps (s)
- return (string.gsub (s, "(%w)([%w]*)",
- function (l, ls)
- return string.upper (l) .. ls
- end))
+
+local function tpack(from, to, ...)
+ return from, to, {...}
end
---- Remove any final newline from a string.
--- @param s string to process
--- @return processed string
-local function chomp (s)
- return (string.gsub (s, "\n$", ""))
+local function tfind(s, ...)
+ return tpack(find(s, ...))
end
---- Escape a string to be used as a pattern
--- @param s string to process
--- @return
--- @param s_: processed string
-local function escape_pattern (s)
- return (string.gsub (s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0"))
+
+local function finds(s, p, i, ...)
+ i = i or 1
+ local l = {}
+ local from, to, r
+ repeat
+ from, to, r = tfind(s, p, i, ...)
+ if from ~= nil then
+ insert(l, {from, to, capt=r})
+ i = to + 1
+ end
+ until not from
+ return l
end
--- Escape a string to be used as a shell token.
--- Quotes spaces, parentheses, brackets, quotes, apostrophes and
--- whitespace.
--- @param s string to process
--- @return processed string
-local function escape_shell (s)
- return (string.gsub (s, "([ %(%)%\\%[%]\"'])", "\\%1"))
+
+local function caps(s)
+ return(gsub(s, '(%w)([%w]*)', function(l, ls)
+ return upper(l) .. ls
+ end))
end
---- Return the English suffix for an ordinal.
--- @param n number of the day
--- @return suffix
-local function ordinal_suffix (n)
- n = math.abs (n) % 100
- local d = n % 10
- if d == 1 and n ~= 11 then
- return "st"
- elseif d == 2 and n ~= 12 then
- return "nd"
- elseif d == 3 and n ~= 13 then
- return "rd"
- else
- return "th"
- end
+
+local function escape_shell(s)
+ return(gsub(s, '([ %(%)%\\%[%]\'"])', '\\%1'))
end
---- Justify a string.
--- When the string is longer than w, it is truncated (left or right
--- according to the sign of w).
--- @param s string to justify
--- @param w width to justify to (-ve means right-justify; +ve means
--- left-justify)
--- @param p string to pad with (default: " ")
--- @return justified string
-local function pad (s, w, p)
- p = string.rep (p or " ", math.abs (w))
- if w < 0 then
- return string.sub (p .. s, w)
- end
- return string.sub (s .. p, 1, w)
+
+local function ordinal_suffix(n)
+ n = abs(n) % 100
+ local d = n % 10
+ if d == 1 and n ~= 11 then
+ return 'st'
+ elseif d == 2 and n ~= 12 then
+ return 'nd'
+ elseif d == 3 and n ~= 13 then
+ return 'rd'
+ else
+ return 'th'
+ end
end
---- Wrap a string into a paragraph.
--- @param s string to wrap
--- @param w width to wrap to (default: 78)
--- @param ind indent (default: 0)
--- @param ind1 indent of first line (default: ind)
--- @return wrapped paragraph
-local function wrap (s, w, ind, ind1)
- w = w or 78
- ind = ind or 0
- ind1 = ind1 or ind
- assert (ind1 < w and ind < w,
- "the indents must be less than the line width")
- assert (type (s) == "string",
- "bad argument #1 to 'wrap' (string expected, got " .. type (s) .. ")")
- local r = StrBuf { string.rep (" ", ind1) }
- local i, lstart, len = 1, ind1, #s
- while i <= #s do
- local j = i + w - lstart
- while #s[j] > 0 and s[j] ~= " " and j > i do
- j = j - 1
- end
- local ni = j + 1
- while s[j] == " " do
- j = j - 1
- end
- r:concat (s:sub (i, j))
- i = ni
- if i < #s then
- r:concat ("\n" .. string.rep (" ", ind))
- lstart = ind
- end
- end
- return r:tostring ()
+
+local function pad(s, w, p)
+ p = string.rep(p or ' ', abs(w))
+ if w < 0 then
+ return string.sub(p .. s, w)
+ end
+ return string.sub(s .. p, 1, w)
end
---- Write a number using SI suffixes.
--- The number is always written to 3 s.f.
--- @param n number
--- @return string
-local function numbertosi (n)
- local SIprefix = {
- [-8] = "y", [-7] = "z", [-6] = "a", [-5] = "f",
- [-4] = "p", [-3] = "n", [-2] = "mu", [-1] = "m",
- [0] = "", [1] = "k", [2] = "M", [3] = "G",
- [4] = "T", [5] = "P", [6] = "E", [7] = "Z",
- [8] = "Y"
- }
- local t = format("% #.2e", n)
- local _, _, m, e = t:find(".(.%...)e(.+)")
- local man, exp = tonumber (m), tonumber (e)
- local siexp = math.floor (exp / 3)
- local shift = exp - siexp * 3
- local s = SIprefix[siexp] or "e" .. tostring (siexp)
- man = man * (10 ^ shift)
- return tostring (man) .. s
+
+local function wrap(s, w, ind, ind1)
+ w = w or 78
+ ind = ind or 0
+ ind1 = ind1 or ind
+ assert(ind1 < w and ind < w,
+ 'the indents must be less than the line width')
+ local r = {string.rep(' ', ind1)}
+ local i, lstart, lens = 1, ind1, len(s)
+ while i <= lens do
+ local j = i + w - lstart
+ while len(s[j]) > 0 and s[j] ~= ' ' and j > i do
+ j = j - 1
+ end
+ local ni = j + 1
+ while s[j] == ' ' do
+ j = j - 1
+ end
+ insert(r, sub(s, i, j))
+ i = ni
+ if i < lens then
+ insert(r, '\n' .. string.rep(' ', ind))
+ lstart = ind
+ end
+ end
+ return concat(r)
end
---- Remove leading matter from a string.
--- @param s string
--- @param r leading pattern (default: "%s+")
--- @return string without leading r
-local function ltrim (s, r)
- r = r or "%s+"
- return (string.gsub (s, "^" .. r, ""))
+
+local function numbertosi(n)
+ local SIprefix = {
+ [-8]='y', [-7]='z', [-6]='a', [-5]='f',
+ [-4]='p', [-3]='n', [-2]='mu', [-1]='m',
+ [0]='', [1]='k', [2]='M', [3]='G',
+ [4]='T', [5]='P', [6]='E', [7]='Z',
+ [8]='Y'
+ }
+ local t = _format('% #.2e', n)
+ local _, _, m, e = find(t, '.(.%...)e(.+)')
+ local man, exp = tonumber(m), tonumber(e)
+ local siexp = floor(exp / 3)
+ local shift = exp - siexp * 3
+ local s = SIprefix[siexp] or 'e' .. tostring(siexp)
+ man = man *(10 ^ shift)
+ return _format('%0.f', man) .. s
end
---- Remove trailing matter from a string.
--- @param s string
--- @param r trailing pattern (default: "%s+")
--- @return string without trailing r
-local function rtrim (s, r)
- r = r or "%s+"
- return (string.gsub (s, r .. "$", ""))
+
+-- Ordor numbers first then asciibetically.
+local function keycmp(a, b)
+ if type(a) == 'number' then
+ return type(b) ~= 'number' or a < b
+ end
+ return type(b) ~= 'number' and tostring(a) < tostring(b)
end
---- Remove leading and trailing matter from a string.
--- @param s string
--- @param r leading/trailing pattern (default: "%s+")
--- @return string without leading/trailing r
-local function trim (s, r)
- return rtrim (ltrim (s, r), r)
+
+local render_fallbacks = {
+ __index = concatvfns,
+}
+
+
+local function prettytostring(x, indent, spacing)
+ indent = indent or '\t'
+ spacing = spacing or ''
+ return render(x, setmetatable({
+ elem = function(x)
+ if type(x) ~= 'string' then
+ return tostring(x)
+ end
+ return format('%q', x)
+ end,
+
+ sort = function(keylist)
+ sort(keylist, keycmp)
+ return keylist
+ end,
+
+ open = function()
+ local s = spacing .. '{'
+ spacing = spacing .. indent
+ return s
+ end,
+
+ close = function()
+ spacing = string.gsub(spacing, indent .. '$', '')
+ return spacing .. '}'
+ end,
+
+ pair = function(x, _, _, k, v, kstr, vstr)
+ local type_k = type(k)
+ local s = spacing
+ if type_k ~= 'string' or match(k, '[^%w_]') then
+ s = s .. '['
+ if type_k == 'table' then
+ s = s .. '\n'
+ end
+ s = s .. kstr
+ if type_k == 'table' then
+ s = s .. '\n'
+ end
+ s = s .. ']'
+ else
+ s = s .. k
+ end
+ s = s .. ' ='
+ if type(v) == 'table' then
+ s = s .. '\n'
+ else
+ s = s .. ' '
+ end
+ s = s .. vstr
+ return s
+ end,
+
+ sep = function(_, k)
+ local s = '\n'
+ if k then
+ s = ',' .. s
+ end
+ return s
+ end,
+ }, render_fallbacks))
end
-for k, v in pairs {
- __index = old__index,
- assert = assert,
- caps = caps,
- chomp = chomp,
- escape_pattern = escape_pattern,
- escape_shell = escape_shell,
- finds = finds,
- format = format,
- ltrim = ltrim,
- numbertosi = numbertosi,
- ordinal_suffix = ordinal_suffix,
- pad = pad,
- pickle = pickle,
- prettytostring = prettytostring,
- render = render,
- require_version = require_version,
- rtrim = rtrim,
- split = split,
- tfind = tfind,
- tostring = tostring,
- trim = trim,
- wrap = wrap,
-
- -- camelCase compatibility:
- escapePattern = escape_pattern,
- escapeShell = escape_shell,
- ordinalSuffix = ordinal_suffix,
-
- -- Core Lua function implementations.
- _format = _format,
- _tostring = _tostring,
-} do
- M[k] = v
+local function trim(s, r)
+ r = r or '%s+'
+ return (gsub(gsub(s, '^' .. r, ''), r .. '$', ''))
end
-for k, v in pairs (string) do
- M[k] = M[k] or v
+
+
+--[[ ================= ]]--
+--[[ Public Interface. ]]--
+--[[ ================= ]]--
+
+
+local function X(decl, fn)
+ return argscheck and argscheck('std.string.' .. decl, fn) or fn
end
-return M
+M = {
+ --- Metamethods
+ -- @section metamethods
+
+ --- String concatenation operation.
+ -- @function __concat
+ -- @string s initial string
+ -- @param o object to stringify and concatenate
+ -- @return s .. tostring(o)
+ -- @usage
+ -- local string = setmetatable('', require 'std.string')
+ -- concatenated = 'foo' .. {'bar'}
+ __concat = __concat,
+
+ --- String subscript operation.
+ -- @function __index
+ -- @string s string
+ -- @tparam int|string i index or method name
+ -- @return `sub(s, i, i)` if i is a number, otherwise
+ -- fall back to a `std.string` metamethod(if any).
+ -- @usage
+ -- getmetatable('').__index = require 'std.string'.__index
+ -- third =('12345')[3]
+ __index = __index,
+
+
+ --- Core Functions
+ -- @section corefuncs
+
+ --- Capitalise each word in a string.
+ -- @function caps
+ -- @string s any string
+ -- @treturn string *s* with each word capitalized
+ -- @usage
+ -- userfullname = caps(input_string)
+ caps = X('caps(string)', caps),
+
+ --- Remove any final newline from a string.
+ -- @function chomp
+ -- @string s any string
+ -- @treturn string *s* with any single trailing newline removed
+ -- @usage
+ -- line = chomp(line)
+ chomp = X('chomp(string)', function(s)
+ return(gsub(s, '\n$', ''))
+ end),
+
+ --- Escape a string to be used as a pattern.
+ -- @function escape_pattern
+ -- @string s any string
+ -- @treturn string *s* with active pattern characters escaped
+ -- @usage
+ -- substr = match(inputstr, escape_pattern(literal))
+ escape_pattern = X('escape_pattern(string)', escape_pattern),
+
+ --- Escape a string to be used as a shell token.
+ -- Quotes spaces, parentheses, brackets, quotes, apostrophes and
+ -- whitespace.
+ -- @function escape_shell
+ -- @string s any string
+ -- @treturn string *s* with active shell characters escaped
+ -- @usage
+ -- os.execute('echo ' .. escape_shell(outputstr))
+ escape_shell = X('escape_shell(string)', escape_shell),
+
+ --- Repeatedly `string.find` until target string is exhausted.
+ -- @function finds
+ -- @string s target string
+ -- @string pattern pattern to match in *s*
+ -- @int[opt=1] init start position
+ -- @bool[opt] plain inhibit magic characters
+ -- @return list of `{from, to; capt={captures}}`
+ -- @see std.string.tfind
+ -- @usage
+ -- for t in std.elems(finds('the target string', '%S+')) do
+ -- print(tostring(t.capt))
+ -- end
+ finds = X('finds(string, string, ?int, ?boolean|:plain)', finds),
+
+ --- Extend to work better with one argument.
+ -- If only one argument is passed, no formatting is attempted.
+ -- @function format
+ -- @string f format string
+ -- @param[opt] ... arguments to format
+ -- @return formatted string
+ -- @usage
+ -- print(format '100% stdlib!')
+ format = X('format(string, [any...])', format),
+
+ --- Remove leading matter from a string.
+ -- @function ltrim
+ -- @string s any string
+ -- @string[opt='%s+'] r leading pattern
+ -- @treturn string *s* with leading *r* stripped
+ -- @usage
+ -- print('got: ' .. ltrim(userinput))
+ ltrim = X('ltrim(string, ?string)', function(s, r)
+ return (gsub(s, '^' ..(r or '%s+'), ''))
+ end),
+
+ --- Write a number using SI suffixes.
+ -- The number is always written to 3 s.f.
+ -- @function numbertosi
+ -- @tparam number|string n any numeric value
+ -- @treturn string *n* simplifed using largest available SI suffix.
+ -- @usage
+ -- print(numbertosi(bitspersecond) .. 'bps')
+ numbertosi = X('numbertosi(number|string)', numbertosi),
+
+ --- Return the English suffix for an ordinal.
+ -- @function ordinal_suffix
+ -- @tparam int|string n any integer value
+ -- @treturn string English suffix for *n*
+ -- @usage
+ -- local now = os.date '*t'
+ -- print('%d%s day of the week', now.day, ordinal_suffix(now.day))
+ ordinal_suffix = X('ordinal_suffix(int|string)', ordinal_suffix),
+
+ --- Justify a string.
+ -- When the string is longer than w, it is truncated(left or right
+ -- according to the sign of w).
+ -- @function pad
+ -- @string s a string to justify
+ -- @int w width to justify to(-ve means right-justify; +ve means
+ -- left-justify)
+ -- @string[opt=' '] p string to pad with
+ -- @treturn string *s* justified to *w* characters wide
+ -- @usage
+ -- print(pad(trim(outputstr, 78)) .. '\n')
+ pad = X('pad(string, int, ?string)', pad),
+
+ --- Pretty-print a table, or other object.
+ -- @function prettytostring
+ -- @param x object to convert to string
+ -- @string[opt='\t'] indent indent between levels
+ -- @string[opt=''] spacing space before every line
+ -- @treturn string pretty string rendering of *x*
+ -- @usage
+ -- print(prettytostring(std, ' '))
+ prettytostring = X('prettytostring(?any, ?string, ?string)', prettytostring),
+
+ --- Remove trailing matter from a string.
+ -- @function rtrim
+ -- @string s any string
+ -- @string[opt='%s+'] r trailing pattern
+ -- @treturn string *s* with trailing *r* stripped
+ -- @usage
+ -- print('got: ' .. rtrim(userinput))
+ rtrim = X('rtrim(string, ?string)', function(s, r)
+ return (gsub(s, (r or '%s+') .. '$', ''))
+ end),
+
+ --- Split a string at a given separator.
+ -- Separator is a Lua pattern, so you have to escape active characters,
+ -- `^$()%.[]*+-?` with a `%` prefix to match a literal character in *s*.
+ -- @function split
+ -- @string s to split
+ -- @string[opt='%s+'] sep separator pattern
+ -- @return list of strings
+ -- @usage
+ -- words = split 'a very short sentence'
+ split = X('split(string, ?string)', split),
+
+ --- Do `string.find`, returning a table of captures.
+ -- @function tfind
+ -- @string s target string
+ -- @string pattern pattern to match in *s*
+ -- @int[opt=1] init start position
+ -- @bool[opt] plain inhibit magic characters
+ -- @treturn int start of match
+ -- @treturn int end of match
+ -- @treturn table list of captured strings
+ -- @see std.string.finds
+ -- @usage
+ -- b, e, captures = tfind('the target string', '%s', 10)
+ tfind = X('tfind(string, string, ?int, ?boolean|:plain)', tfind),
+
+ --- Remove leading and trailing matter from a string.
+ -- @function trim
+ -- @string s any string
+ -- @string[opt='%s+'] r trailing pattern
+ -- @treturn string *s* with leading and trailing *r* stripped
+ -- @usage
+ -- print('got: ' .. trim(userinput))
+ trim = X('trim(string, ?string)', trim),
+
+ --- Wrap a string into a paragraph.
+ -- @function wrap
+ -- @string s a paragraph of text
+ -- @int[opt=78] w width to wrap to
+ -- @int[opt=0] ind indent
+ -- @int[opt=ind] ind1 indent of first line
+ -- @treturn string *s* wrapped to *w* columns
+ -- @usage
+ -- print(wrap(copyright, 72, 4))
+ wrap = X('wrap(string, ?int, ?int, ?int)', wrap),
+}
+
+
+return merge(string, M)
+
diff --git a/lib/std/table.lua b/lib/std/table.lua
index 6ed1f6c..7bda608 100644
--- a/lib/std/table.lua
+++ b/lib/std/table.lua
@@ -1,137 +1,439 @@
--- Extensions to the table module
+--[[
+ General Lua Libraries for Lua 5.1, 5.2 & 5.3
+ Copyright (C) 2002-2018 stdlib authors
+]]
+--[[--
+ Extensions to the core table module.
-local base = require "std.base"
-local func = require "std.functional"
+ The module table returned by `std.table` also contains all of the entries from
+ the core table module. An hygienic way to import this module, then, is simply
+ to override the core `table` locally:
-local _sort = table.sort
---- Make table.sort return its result.
--- @param t table
--- @param c comparator function
--- @return sorted table
-local function sort (t, c)
- _sort (t, c)
- return t
+ local table = require 'std.table'
+
+ @corelibrary std.table
+]]
+
+
+local _ = require 'std._base'
+
+local argscheck = _.typecheck and _.typecheck.argscheck
+local invert = _.table.invert
+local maxn = _.table.maxn
+
+_ = nil
+
+local _ENV = require 'std.normalize' {
+ 'table',
+ merge = 'table.merge',
+ min = 'math.min',
+}
+
+
+
+--[[ =============== ]]--
+--[[ Implementation. ]]--
+--[[ =============== ]]--
+
+
+local M
+
+
+local function merge_allfields(t, u, map, nometa)
+ if type(map) ~= 'table' then
+ map, nometa = nil, map
+ end
+
+ if not nometa then
+ setmetatable(t, getmetatable(u))
+ end
+ if map then
+ for k, v in pairs(u) do
+ t[map[k] or k] = v
+ end
+ else
+ for k, v in pairs(u) do
+ t[k] = v
+ end
+ end
+ return t
end
---- Return whether table is empty.
--- @param t table
--- @return true if empty or false otherwise
-local function empty (t)
- return not next (t)
+
+local function merge_namedfields(t, u, keys, nometa)
+ if type(keys) ~= 'table' then
+ keys, nometa = nil, keys
+ end
+
+ if not nometa then
+ setmetatable(t, getmetatable(u))
+ end
+ for _, k in pairs(keys or {}) do
+ t[k] = u[k]
+ end
+ return t
end
---- Turn a tuple into a list.
--- @param ... tuple
--- @return list
-local function pack (...)
- return {...}
+
+local function depair(ls)
+ local t = {}
+ for _, v in ipairs(ls) do
+ t[v[1]] = v[2]
+ end
+ return t
end
---- Find the number of elements in a table.
--- @param t table
--- @return number of elements in t
-local function size (t)
- local n = 0
- for _ in pairs (t) do
- n = n + 1
- end
- return n
+
+local function enpair(t)
+ local tt = {}
+ for i, v in pairs(t) do
+ tt[#tt + 1] = {i, v}
+ end
+ return tt
end
---- Make the list of keys of a table.
--- @param t table
--- @return list of keys
-local function keys (t)
- local u = {}
- for i, v in pairs (t) do
- table.insert (u, i)
- end
- return u
+
+local _insert = table.insert
+
+local function insert(t, pos, v)
+ if v == nil then
+ pos, v = len(t) + 1, pos
+ end
+ if pos < 1 or pos > len(t) + 1 then
+ argerror('std.table.insert', 2, 'position ' .. pos .. ' out of bounds', 2)
+ end
+ _insert(t, pos, v)
+ return t
+end
+
+
+local function keys(t)
+ local l = {}
+ for k in pairs(t) do
+ l[#l + 1] = k
+ end
+ return l
end
---- Make the list of values of a table.
--- @param t table
--- @return list of values
-local function values (t)
- local u = {}
- for i, v in pairs (t) do
- table.insert (u, v)
- end
- return u
+
+local function new(x, t)
+ return setmetatable(t or {}, {__index = function(t, i)
+ return x
+ end})
+end
+
+
+local function project(fkey, tt)
+ local r = {}
+ for _, t in ipairs(tt) do
+ r[#r + 1] = t[fkey]
+ end
+ return r
end
---- Invert a table.
--- @param t table {i=v, ...}
--- @return inverted table {v=i, ...}
-local function invert (t)
- local u = {}
- for i, v in pairs (t) do
- u[v] = i
- end
- return u
+
+local function size(t)
+ local n = 0
+ for _ in pairs(t) do
+ n = n + 1
+ end
+ return n
end
---- An iterator like ipairs, but in reverse.
--- @param t table to iterate over
--- @return iterator function
--- @return the table, as above
--- @return #t + 1
-local function ripairs (t)
- return function (t, n)
- n = n - 1
- if n > 0 then
- return n, t[n]
- end
- end,
- t, #t + 1
+
+-- Preserve core table sort function.
+local _sort = table.sort
+
+local function sort(t, c)
+ _sort(t, c)
+ return t
end
---- Turn an object into a table according to __totable metamethod.
--- @param x object to turn into a table
--- @return table or nil
-local function totable (x)
- local m = func.metamethod (x, "__totable")
- if m then
- return m (x)
- elseif type (x) == "table" then
- return x
- else
- return nil
- end
+
+local _remove = table.remove
+
+local function remove(t, pos)
+ local lent = len(t)
+ pos = pos or lent
+ if pos < min(1, lent) or pos > lent + 1 then -- +1? whu? that's what 5.2.3 does!?!
+ argerror('std.table.remove', 2, 'position ' .. pos .. ' out of bounds', 2)
+ end
+ return _remove(t, pos)
end
---- Make a table with a default value for unset keys.
--- @param x default entry value (default: nil)
--- @param t initial table (default: {})
--- @return table whose unset elements are x
-local function new (x, t)
- return setmetatable (t or {},
- {__index = function (t, i)
- return x
- end})
+
+local _unpack = unpack
+
+local function unpack(t, i, j)
+ if j == nil then
+ -- if j was not given, respect __len, otherwise use maxn
+ local m = getmetamethod(t, '__len')
+ j = m and m(t) or maxn(t)
+ end
+ return _unpack(t, tonumber(i) or 1, tonumber(j))
end
-local M = {
- clone = base.clone,
- clone_rename = base.clone_rename,
- empty = empty,
- invert = invert,
- keys = keys,
- merge = base.merge,
- new = new,
- pack = pack,
- ripairs = ripairs,
- size = size,
- sort = sort,
- totable = totable,
- values = values,
-
- -- Core Lua table.sort function.
- _sort = _sort,
-}
-for k, v in pairs (table) do
- M[k] = M[k] or v
+local function values(t)
+ local l = {}
+ for _, v in pairs(t) do
+ l[#l + 1] = v
+ end
+ return l
end
-return M
+
+
+--[[ ================= ]]--
+--[[ Public Interface. ]]--
+--[[ ================= ]]--
+
+
+local function X(decl, fn)
+ return argscheck and argscheck('std.table.' .. decl, fn) or fn
+end
+
+M = {
+ --- Core Functions
+ -- @section corefuncs
+
+ --- Enhance core *table.insert* to return its result.
+ -- If *pos* is not given, respect `__len` metamethod when calculating
+ -- default append. Also, diagnose out of bounds *pos* arguments
+ -- consistently on any supported version of Lua.
+ -- @function insert
+ -- @tparam table t a table
+ -- @int[opt=len(t)] pos index at which to insert new element
+ -- @param v value to insert into *t*
+ -- @treturn table *t*
+ -- @usage
+ -- --> {1, 'x', 2, 3, 'y'}
+ -- insert(insert({1, 2, 3}, 2, 'x'), 'y')
+ insert = X('insert(table, [int], any)', insert),
+
+ --- Largest integer key in a table.
+ -- @function maxn
+ -- @tparam table t a table
+ -- @treturn int largest integer key in *t*
+ -- @usage
+ -- --> 42
+ -- maxn {'a', b='c', 99, [42]='x', 'x', [5]=67}
+ maxn = X('maxn(table)', maxn),
+
+ --- Turn a tuple into a list, with tuple-size in field `n`
+ -- @function pack
+ -- @param ... tuple
+ -- @return list-like table, with tuple-size in field `n`
+ -- @usage
+ -- --> {1, 2, 'ax', n=3}
+ -- pack(find('ax1', '(%D+)'))
+ pack = pack,
+
+ --- Enhance core *table.remove* to respect `__len` when *pos* is omitted.
+ -- Also, diagnose out of bounds *pos* arguments consistently on any supported
+ -- version of Lua.
+ -- @function remove
+ -- @tparam table t a table
+ -- @int[opt=len(t)] pos index from which to remove an element
+ -- @return removed value, or else `nil`
+ -- @usage
+ -- --> {1, 2, 5}
+ -- t = {1, 2, 'x', 5}
+ -- remove(t, 3) == 'x' and t
+ remove = X('remove(table, ?int)', remove),
+
+ --- Enhance core *table.sort* to return its result.
+ -- @function sort
+ -- @tparam table t unsorted table
+ -- @tparam[opt=std.operator.lt] comparator c ordering function callback
+ -- @return *t* with keys sorted according to *c*
+ -- @usage
+ -- table.concat(sort(object))
+ sort = X('sort(table, ?function)', sort),
+
+ --- Enhance core *table.unpack* to always unpack up to __len or maxn.
+ -- @function unpack
+ -- @tparam table t table to act on
+ -- @int[opt=1] i first index to unpack
+ -- @int[opt=table.maxn(t)] j last index to unpack
+ -- @return ... values of numeric indices of *t*
+ -- @usage
+ -- return unpack(results_table)
+ unpack = X('unpack(table, ?int, ?int)', unpack),
+
+
+ --- Accessor Functions
+ -- @section accessorfuncs
+
+ --- Make a shallow copy of a table, including any metatable.
+ -- @function clone
+ -- @tparam table t source table
+ -- @tparam[opt={}] table map table of `{old_key=new_key, ...}`
+ -- @bool[opt] nometa if non-nil don't copy metatable
+ -- @return copy of *t*, also sharing *t*'s metatable unless *nometa*
+ -- is true, and with keys renamed according to *map*
+ -- @see merge
+ -- @see clone_select
+ -- @usage
+ -- shallowcopy = clone(original, {rename_this='to_this'}, ':nometa')
+ clone = X('clone(table, [table], ?boolean|:nometa)', function(...)
+ return merge_allfields({}, ...)
+ end),
+
+ --- Make a partial clone of a table.
+ --
+ -- Like `clone`, but does not copy any fields by default.
+ -- @function clone_select
+ -- @tparam table t source table
+ -- @tparam[opt={}] table keys list of keys to copy
+ -- @bool[opt] nometa if non-nil don't copy metatable
+ -- @treturn table copy of fields in *selection* from *t*, also sharing *t*'s
+ -- metatable unless *nometa*
+ -- @see clone
+ -- @see merge_select
+ -- @usage
+ -- partialcopy = clone_select(original, {'this', 'and_this'}, true)
+ clone_select = X('clone_select(table, [table], ?boolean|:nometa)', function(...)
+ return merge_namedfields({}, ...)
+ end),
+
+ --- Turn a list of pairs into a table.
+ -- @todo Find a better name.
+ -- @function depair
+ -- @tparam table ls list of lists
+ -- @treturn table a flat table with keys and values from *ls*
+ -- @see enpair
+ -- @usage
+ -- --> {a=1, b=2, c=3}
+ -- depair {{'a', 1}, {'b', 2}, {'c', 3}}
+ depair = X('depair(list of lists)', depair),
+
+ --- Turn a table into a list of pairs.
+ -- @todo Find a better name.
+ -- @function enpair
+ -- @tparam table t a table `{i1=v1, ..., in=vn}`
+ -- @treturn table a new list of pairs containing `{{i1, v1}, ..., {in, vn}}`
+ -- @see depair
+ -- @usage
+ -- --> {{1, 'a'}, {2, 'b'}, {3, 'c'}}
+ -- enpair {'a', 'b', 'c'}
+ enpair = X('enpair(table)', enpair),
+
+ --- Return whether table is empty.
+ -- @function empty
+ -- @tparam table t any table
+ -- @treturn boolean `true` if *t* is empty, otherwise `false`
+ -- @usage
+ -- if empty(t) then error 'ohnoes' end
+ empty = X('empty(table)', function(t)
+ return not next(t)
+ end),
+
+ --- Make a table with a default value for unset keys.
+ -- @function new
+ -- @param[opt=nil] x default entry value
+ -- @tparam[opt={}] table t initial table
+ -- @treturn table table whose unset elements are *x*
+ -- @usage
+ -- t = new(0)
+ new = X('new(?any, ?table)', new),
+
+ --- Project a list of fields from a list of tables.
+ -- @function project
+ -- @param fkey field to project
+ -- @tparam table tt a list of tables
+ -- @treturn table list of *fkey* fields from *tt*
+ -- @usage
+ -- --> {1, 3, 'yy'}
+ -- project('xx', {{'a', xx=1, yy='z'}, {'b', yy=2}, {'c', xx=3}, {xx='yy'})
+ project = X('project(any, list of tables)', project),
+
+ --- Find the number of elements in a table.
+ -- @function size
+ -- @tparam table t any table
+ -- @treturn int number of non-nil values in *t*
+ -- @usage
+ -- --> 3
+ -- size {foo=true, bar=true, baz=false}
+ size = X('size(table)', size),
+
+ --- Make the list of values of a table.
+ -- @function values
+ -- @tparam table t any table
+ -- @treturn table list of values in *t*
+ -- @see keys
+ -- @usage
+ -- --> {'a', 'c', 42}
+ -- values {'a', b='c', [-1]=42}
+ values = X('values(table)', values),
+
+
+ --- Mutator Functions
+ -- @section mutatorfuncs
+
+ --- Invert a table.
+ -- @function invert
+ -- @tparam table t a table with `{k=v, ...}`
+ -- @treturn table inverted table `{v=k, ...}`
+ -- @usage
+ -- --> {a=1, b=2, c=3}
+ -- invert {'a', 'b', 'c'}
+ invert = X('invert(table)', invert),
+
+ --- Make the list of keys in table.
+ -- @function keys
+ -- @tparam table t a table
+ -- @treturn table list of keys from *t*
+ -- @see values
+ -- @usage
+ -- globals = keys(_G)
+ keys = X('keys(table)', keys),
+
+ --- Destructively merge one table's fields into another.
+ -- @function merge
+ -- @tparam table t destination table
+ -- @tparam table u table with fields to merge
+ -- @tparam[opt={}] table map table of `{old_key=new_key, ...}`
+ -- @bool[opt] nometa if `true` or ':nometa' don't copy metatable
+ -- @treturn table *t* with fields from *u* merged in
+ -- @see clone
+ -- @see merge_select
+ -- @usage
+ -- merge(_G, require 'std.debug', {say='log'}, ':nometa')
+ merge = X('merge(table, table, [table], ?boolean|:nometa)', merge_allfields),
+
+ --- Destructively merge another table's named fields into *table*.
+ --
+ -- Like `merge`, but does not merge any fields by default.
+ -- @function merge_select
+ -- @tparam table t destination table
+ -- @tparam table u table with fields to merge
+ -- @tparam[opt={}] table keys list of keys to copy
+ -- @bool[opt] nometa if `true` or ':nometa' don't copy metatable
+ -- @treturn table copy of fields in *selection* from *t*, also sharing *t*'s
+ -- metatable unless *nometa*
+ -- @see merge
+ -- @see clone_select
+ -- @usage
+ -- merge_select(_G, require 'std.debug', {'say'}, false)
+ merge_select = X('merge_select(table, table, [table], ?boolean|:nometa)',
+ merge_namedfields),
+}
+
+
+return merge(table, M)
+
+
+
+--- Types
+-- @section Types
+
+--- Signature of a @{sort} comparator function.
+-- @function comparator
+-- @param a any object
+-- @param b any object
+-- @treturn boolean `true` if *a* sorts before *b*, otherwise `false`
+-- @see sort
+-- @usage
+-- local reversor = function(a, b) return a > b end
+-- sort(t, reversor)
diff --git a/lib/std/tree.lua b/lib/std/tree.lua
deleted file mode 100644
index f9f93b1..0000000
--- a/lib/std/tree.lua
+++ /dev/null
@@ -1,153 +0,0 @@
---- Tables as trees.
-local base = require "std.base"
-local ileaves, leaves = base.ileaves, base.leaves
-
-local list = require "std.list"
-local func = require "std.functional"
-
-
-local metatable = {}
---- Make a table into a tree
--- @param t table
--- @return tree
-local function new (t)
- return setmetatable (t or {}, metatable)
-end
-
---- Tree __index metamethod.
--- @param tr tree
--- @param i non-table, or list of keys {i1 ...
--- in}
--- @return tr[i]...[in] if i is a table, or
--- tr[i] otherwise
-function metatable.__index (tr, i)
- -- FIXME: the following doesn't treat list keys correctly
- -- e.g. tr[{{1, 2}, {3, 4}}], maybe flatten first?
- if type (i) == "table" and #i > 0 then
- return list.foldl (func.op["[]"], tr, i)
- else
- return rawget (tr, i)
- end
-end
-
---- Tree __newindex metamethod.
--- Sets tr[i1]...[in] = v if i is a
--- table, or tr[i] = v otherwise
--- @param tr tree
--- @param i non-table, or list of keys {i1 ...
--- in}
--- @param v value
-function metatable.__newindex (tr, i, v)
- if type (i) == "table" then
- for n = 1, #i - 1 do
- if getmetatable (tr[i[n]]) ~= metatable then
- rawset (tr, i[n], new ())
- end
- tr = tr[i[n]]
- end
- rawset (tr, i[#i], v)
- else
- rawset (tr, i, v)
- end
-end
-
---- Make a deep copy of a tree, including any metatables
--- @param t table
--- @param nometa if non-nil don't copy metatables
--- @return copy of table
-local function clone (t, nometa)
- local r = {}
- if not nometa then
- setmetatable (r, getmetatable (t))
- end
- local d = {[t] = r}
- local function copy (o, x)
- for i, v in pairs (x) do
- if type (v) == "table" then
- if not d[v] then
- d[v] = {}
- if not nometa then
- setmetatable (d[v], getmetatable (v))
- end
- o[i] = copy (d[v], v)
- else
- o[i] = d[v]
- end
- else
- o[i] = v
- end
- end
- return o
- end
- return copy (r, t)
-end
-
----
--- @class function
--- @name tree_Iterator
--- @param n current node
--- @return type ("leaf", "branch" (pre-order) or "join" (post-order))
--- @return path to node ({i1...ik})
--- @return node
-local function _nodes (it, tr)
- local p = {}
- local function visit (n)
- if type (n) == "table" then
- coroutine.yield ("branch", p, n)
- for i, v in it (n) do
- table.insert (p, i)
- visit (v)
- table.remove (p)
- end
- coroutine.yield ("join", p, n)
- else
- coroutine.yield ("leaf", p, n)
- end
- end
- return coroutine.wrap (visit), tr
-end
-
---- Tree iterator.
--- @see tree_Iterator
--- @param tr tree to iterate over
--- @return iterator function
--- @return the tree, as above
-local function nodes (tr)
- return _nodes (pairs, tr)
-end
-
---- Tree iterator over numbered nodes, in order.
--- @see tree_Iterator
--- @param tr tree to iterate over
--- @return iterator function
--- @return the tree, as above
-local function inodes (tr)
- return _nodes (ipairs, tr)
-end
-
---- Deep-merge one tree into another. u is merged into
---- t.
--- @param t first tree
--- @param u second tree
--- @return first tree
-local function merge (t, u)
- for ty, p, n in nodes (u) do
- if ty == "leaf" then
- t[p] = n
- end
- end
- return t
-end
-
--- Public interface
-local M = {
- clone = clone,
- ileaves = ileaves,
- inodes = inodes,
- leaves = leaves,
- merge = merge,
- new = new,
- nodes = nodes,
-}
-
-return M
diff --git a/local.mk b/local.mk
deleted file mode 100644
index 3dd9a4b..0000000
--- a/local.mk
+++ /dev/null
@@ -1,95 +0,0 @@
-# Local Make rules.
-
-## ------------ ##
-## Environment. ##
-## ------------ ##
-
-std_path = $(abs_srcdir)/lib/?.lua
-LUA_ENV = LUA_PATH="$(std_path);$(LUA_PATH)"
-
-
-## ---------- ##
-## Bootstrap. ##
-## ---------- ##
-
-old_NEWS_hash = 7ef01dfb840329db3d8db218bfe9d075
-
-
-## ------------- ##
-## Declarations. ##
-## ------------- ##
-
-filesdir = $(docdir)/files
-modulesdir = $(docdir)/modules
-
-dist_doc_DATA =
-dist_files_DATA =
-dist_modules_DATA =
-
-include specs/specs.mk
-
-
-## ------ ##
-## Build. ##
-## ------ ##
-
-dist_lua_DATA += \
- lib/std.lua \
- $(NOTHING_ELSE)
-
-luastddir = $(luadir)/std
-
-dist_luastd_DATA = \
- lib/std/base.lua \
- lib/std/debug.lua \
- lib/std/debug_init.lua \
- lib/std/functional.lua \
- lib/std/getopt.lua \
- lib/std/io.lua \
- lib/std/list.lua \
- lib/std/math.lua \
- lib/std/modules.lua \
- lib/std/object.lua \
- lib/std/package.lua \
- lib/std/set.lua \
- lib/std/strbuf.lua \
- lib/std/strict.lua \
- lib/std/string.lua \
- lib/std/table.lua \
- lib/std/tree.lua \
- $(NOTHING_ELSE)
-
-# In order to avoid regenerating std.lua at configure time, which
-# causes the documentation to be rebuilt and hence requires users to
-# have luadoc installed, put std/std.lua in as a Makefile dependency.
-# (Strictly speaking, distributing an AC_CONFIG_FILE would be wrong.)
-lib/std.lua: lib/std.lua.in
- ./config.status --file=$@
-
-
-## Use a builtin rockspec build with root at $(srcdir)/lib
-mkrockspecs_args = --module-dir $(srcdir)/lib
-
-
-## ------------- ##
-## Distribution. ##
-## ------------- ##
-
-EXTRA_DIST += \
- lib/std.lua.in \
- $(NOTHING_ELSE)
-
-
-## -------------- ##
-## Documentation. ##
-## -------------- ##
-
-dist_doc_DATA += \
- $(srcdir)/lib/index.html \
- $(srcdir)/lib/luadoc.css
-
-dist_files_DATA += $(wildcard $(srcdir)/lib/files/*.html)
-dist_modules_DATA += $(wildcard $(srcdir)/lib/modules/*.html)
-
-$(dist_doc_DATA): $(dist_lua_DATA) $(dist_luastd_DATA)
- cd $(srcdir)/lib && $(LUADOC) *.lua std/*.lua
diff --git a/rockspec.conf b/rockspec.conf
deleted file mode 100644
index c7813c7..0000000
--- a/rockspec.conf
+++ /dev/null
@@ -1,16 +0,0 @@
-# stdlib rockspec configuration.
-
-description:
- homepage: http://rrthomas.github.io/lua-stdlib
- license: MIT/X11
- summary: General Lua Libraries
- detailed:
- stdlib is a library of modules for common programming tasks,
- including list, table and functional operations, regexps, objects,
- pickling, pretty-printing and getopt.
-
-dependencies:
-- lua >= 5.1
-
-source:
- url: git://github.com/rrthomas/lua-stdlib.git
diff --git a/rockspecs.lua b/rockspecs.lua
deleted file mode 100644
index aec97fd..0000000
--- a/rockspecs.lua
+++ /dev/null
@@ -1,46 +0,0 @@
--- Rockspec data
-
--- Variables to be interpolated:
---
--- package_name
--- version
-
-local version_dashed = version:gsub ("%.", "-")
-
-local default = {
- package = package_name,
- version = version.."-1",
- source = {
- url = "git://github.com/rrthomas/lua-stdlib.git",
- },
- description = {
- summary = "General Lua libraries",
- detailed = [[
- stdlib is a library of modules for common programming tasks,
- including list, table and functional operations, regexps, objects,
- pickling, pretty-printing and getopt.
- ]],
- homepage = "http://github.com/rrthomas/lua-stdlib/",
- license = "MIT/X11",
- },
- dependencies = {
- "lua >= 5.1",
- },
- build = {
- type = "command",
- build_command = "./configure " ..
- "LUA_INCLUDE=$(LUA_INCDIR) LUA=$(LUA) CPPFLAGS=-I$(LUA_INCDIR) " ..
- "--prefix=$(PREFIX) --libdir=$(LIBDIR) --datadir=$(LUADIR) " ..
- "&& make clean all",
- install_command = "make install luadir=$(LUADIR)",
- copy_directories = {},
- },
-}
-
-if version ~= "git" then
- default.source.branch = "release-v"..version_dashed
-else
- default.build.build_command = "./bootstrap && " .. default.build.build_command
-end
-
-return {default=default, [""]={}}
diff --git a/slingshot b/slingshot
deleted file mode 160000
index 78bc2a8..0000000
--- a/slingshot
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 78bc2a8173910b0d8bd4ecdff1acff612629b335
diff --git a/spec/debug_spec.yaml b/spec/debug_spec.yaml
new file mode 100644
index 0000000..5102723
--- /dev/null
+++ b/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/spec/io_spec.yaml b/spec/io_spec.yaml
new file mode 100644
index 0000000..67b850f
--- /dev/null
+++ b/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/spec/math_spec.yaml b/spec/math_spec.yaml
new file mode 100644
index 0000000..ed08753
--- /dev/null
+++ b/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/spec/package_spec.yaml b/spec/package_spec.yaml
new file mode 100644
index 0000000..23ce961
--- /dev/null
+++ b/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/spec/spec_helper.lua b/spec/spec_helper.lua
new file mode 100644
index 0000000..642712f
--- /dev/null
+++ b/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/spec/std_spec.yaml b/spec/std_spec.yaml
new file mode 100644
index 0000000..b747abf
--- /dev/null
+++ b/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/spec/string_spec.yaml b/spec/string_spec.yaml
new file mode 100644
index 0000000..2fa47f2
--- /dev/null
+++ b/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/spec/table_spec.yaml b/spec/table_spec.yaml
new file mode 100644
index 0000000..d9fed0c
--- /dev/null
+++ b/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)
diff --git a/specs/debug_spec.yaml b/specs/debug_spec.yaml
deleted file mode 100644
index 6ba7acc..0000000
--- a/specs/debug_spec.yaml
+++ /dev/null
@@ -1,69 +0,0 @@
-specify debug:
-- before: |
- M = require "std.debug"
-
- extends = debug
- enhancements = {}
- extensions = { "say", "trace" }
-
-- context when required:
- - before:
- enhanced = {}
- for _, api in ipairs (enhancements) do enhanced[api] = true end
-
- - context by name:
- - before: |
- function restore (g, m)
- for _, api in ipairs (enhancements) do
- g[api], g["_" .. api] = m[api], m["_" .. api]
- end
- for _, api in ipairs (extensions) do g[api] = m[api] end
- end
-
- for _, api in ipairs (enhancements) do
- extends[api] = M["_" .. api]
- end
- for _, api in ipairs (extensions) do extends[api] = nil end
- - after:
- restore (extends, M)
- - it does not perturb the global table:
- for _, api in ipairs (extensions) do
- expect (extends[api]).should_be (nil)
- end
- for _, api in ipairs (enhancements) do
- expect (extends[api]).should_be (M["_" .. api])
- end
- - it contains all global access points:
- for api in pairs (extends) do
- if enhanced[api] then
- expect (M[api]).should_not_be (extends[api])
- else
- expect (M[api]).should_be (extends[api])
- end
- end
-
- - context via the std module:
- - before:
- require "std"
- - it adds extension apis to the global table:
- for api in pairs (M) do
- expect (extends[api]).should_be (M[api])
- end
- - it does not add any other global access points:
- for api in pairs (extends) do
- if not enhanced[api] then
- expect (M[api]).should_be (extends[api])
- end
- end
-
-
-- describe _DEBUG:
-
-
-- describe debug:
-
-
-- describe say:
-
-
-- describe trace:
diff --git a/specs/getopt_spec.yaml b/specs/getopt_spec.yaml
deleted file mode 100644
index c36c7d0..0000000
--- a/specs/getopt_spec.yaml
+++ /dev/null
@@ -1,67 +0,0 @@
-specify getopt:
-- before:
- getopt = require "std.getopt"
-
-- "describe getopt.getOpt":
- - before: |
- prog = {
- name = "getopt_spec.lua",
- options = { {{"verbose", "v"}, "verbosely list files"},
- {{"output", "o"}, "dump to FILE", "Opt", "FILE"},
- {{"name", "n"}, "only dump USER's files", "Req", "USER"},
- }
- }
-
- function test (cmdLine, stop_at_nonopt)
- local nonOpts, opts, errors = getopt.getOpt (cmdLine, prog.options, stop_at_nonopt)
- if #errors == 0 then
- return { options = opts, args = nonOpts }
- else
- return errors
- end
- end
-
- getopt.processArgs (prog)
-
- - it recognizes a user defined option:
- expect (test {"foo", "-v"}).should_equal (
- { options = { verbose = {1}}, args = { "foo" } })
- - "it treats -- as the end of the option list":
- expect (test {"foo", "--", "-v"}).should_equal (
- { options = {}, args = { "foo", "-v" } })
- - it captures a list of repeated option arguments:
- expect (test {"-o", "-V", "-name", "bar", "--name=baz"}).should_equal (
- { options = { name = {"bar", "baz"}, version = {1}, output = {1}},
- args = {} })
- - it diagnoses unrecognized options:
- expect (test {"-foo"}).should_contain "unrecognized option `-foo'"
- - it stops option parsing at the first non-option if told to:
- expect (test ({"foo", "-bar"}, true)).should_equal (
- { options = {}, args = {"foo", "-bar"} })
-
-- "describe getopt.usageInfo":
- - context when specifying options:
- - before:
- helppatt = "%-h, %-%-help%s+print this help, then exit"
- versionpatt = "%-V, %-%-version%s+print version information, then exit"
- prog = { name = "getopt_spec.lua", options = {} }
- options = { {{"help", "?"}, "display this help"},
- {{"version"}, "display version number"} }
- f = getopt.usageInfo
-
- - it provides a default version option:
- getopt.processArgs (prog)
- expect (f ("", prog.options)).should_match (versionpatt)
- - it allows the user to override the version option:
- prog.options = options
- getopt.processArgs (prog)
- expect (f ("", prog.options)).should_not_match (versionpatt)
- expect (f ("", prog.options)).should_match (" %-%-version%s+display")
- - it provides a default help option:
- getopt.processArgs (prog)
- expect (f ("", prog.options)).should_match (helppatt)
- - it allows the user to override the help option:
- prog.options = options
- getopt.processArgs (prog)
- expect (f ("", prog.options)).should_not_match (helppatt)
- expect (f ("", prog.options)).should_match ("%-%?, %-%-help%s+display")
diff --git a/specs/io_spec.yaml b/specs/io_spec.yaml
deleted file mode 100644
index 758060a..0000000
--- a/specs/io_spec.yaml
+++ /dev/null
@@ -1,89 +0,0 @@
-specify io:
-- before: |
- M = require "std.io"
-
- extends = io
- enhancements = {}
- extensions = { "catdir", "catfile", "die", "process_files",
- "readlines", "shell", "slurp", "splitdir", "warn",
- "writelines",
- -- camelCase compatibility:
- "processFiles" }
-
-- context when required:
- - before:
- enhanced = {}
- for _, api in ipairs (enhancements) do enhanced[api] = true end
-
- - context by name:
- - before: |
- function restore (g, m)
- for _, api in ipairs (enhancements) do
- g[api], g["_" .. api] = m[api], m["_" .. api]
- end
- for _, api in ipairs (extensions) do g[api] = m[api] end
- end
-
- for _, api in ipairs (enhancements) do
- extends[api] = M["_" .. api]
- end
- for _, api in ipairs (extensions) do extends[api] = nil end
- - after:
- restore (extends, M)
- - it does not perturb the global table:
- for _, api in ipairs (extensions) do
- expect (extends[api]).should_be (nil)
- end
- for _, api in ipairs (enhancements) do
- expect (extends[api]).should_be (M["_" .. api])
- end
- - it contains all global access points:
- for api in pairs (extends) do
- if enhanced[api] then
- expect (M[api]).should_not_be (extends[api])
- else
- expect (M[api]).should_be (extends[api])
- end
- end
-
- - context via the std module:
- - before:
- require "std"
- - it adds extension apis to the global table:
- for api in pairs (M) do
- expect (extends[api]).should_be (M[api])
- end
- - it does not add any other global access points:
- for api in pairs (extends) do
- if not enhanced[api] then
- expect (M[api]).should_be (extends[api])
- end
- end
-
-
-- describe catdir:
-
-
-- describe catfile:
-
-
-- describe process_files:
- - before:
- subject = M.process_files
- - it is the same function as legacy processFiles call:
- expect (io.processFiles).should_be (subject)
-
-
-- describe readlines:
-
-
-- describe shell:
-
-
-- describe slurp:
-
-
-- describe splitdir:
-
-
-- describe writelines:
diff --git a/specs/list_spec.yaml b/specs/list_spec.yaml
deleted file mode 100644
index a80749a..0000000
--- a/specs/list_spec.yaml
+++ /dev/null
@@ -1,441 +0,0 @@
-before:
- require "spec_helper"
- Object = require "std.object"
- List = require "std.list"
- l = List {"foo", "bar", "baz"}
-
-
-specify List:
-- describe construction:
- - context from List clone method:
- - it constructs a new list:
- l = List:clone {}
- expect (l).should_not_be (List)
- expect (Object.type (l)).should_be "List"
- - it reuses the List metatable:
- l, m = List:clone {"l"}, List:clone {"m"}
- expect (getmetatable (l)).should_be (getmetatable (m))
- - it initialises list with constructor parameters:
- m = List:clone {"foo", "bar", "baz"}
- expect (m).should_equal (l)
- - it serves as a prototype for new instances:
- obj = l:clone {}
- expect (Object.type (obj)).should_be "List"
- expect (obj).should_equal (l)
- expect (getmetatable (obj)).should_be (getmetatable (l))
-
- # List {args} is just syntactic sugar for List:clone {args}
- - context from List object prototype:
- - it constructs a new list:
- l = List {}
- expect (l).should_not_be (List)
- expect (Object.type (l)).should_be "List"
- - it reuses the List metatable:
- l, m = List {"l"}, List {"m"}
- expect (getmetatable (l)).should_be (getmetatable (m))
- - it initialises list with constructor parameters:
- m = List {"foo", "bar", "baz"}
- expect (m).should_equal (l)
- - it serves as a prototype for new instances:
- obj = l {}
- expect (Object.type (obj)).should_be "List"
- expect (obj).should_equal (l)
- expect (getmetatable (obj)).should_be (getmetatable (l))
-
-
-- describe metatable propagation:
- - it reuses the metatable for List constructed objects:
- obj = List {"foo", "bar"}
- expect (getmetatable (obj)).should_be (getmetatable (l))
-
-
-- describe append:
- - context when called as a list object method:
- - it returns a list object:
- l = l:append ("quux")
- expect (Object.type (l)).should_be "List"
- - it works for an empty list:
- l = List {}
- expect (l:append ("quux")).should_equal (List {"quux"})
- - it appends an item to a list:
- expect (l:append ("quux")).
- should_equal (List {"foo", "bar", "baz", "quux"})
- - context when called as a list metamethod:
- - it returns a list object:
- l = l + "quux"
- expect (Object.type (l)).should_be "List"
- - it works for an empty list:
- l = List {}
- expect (l + "quux").should_equal (List {"quux"})
- - it appends an item to a list:
- expect (l + "quux").
- should_equal (List {"foo", "bar", "baz", "quux"})
-
-
-- describe compare:
- - before:
- a, b = List {"foo", "bar"}, List {"foo", "baz"}
- - context when called as a list object method:
- - it returns -1 when the first list is less than the second: |
- expect (a:compare {"foo", "baz"}).should_be (-1)
- expect (a:compare (List {"foo", "baz"})).should_be (-1)
- - it returns -1 when the second list has additional elements: |
- b = List {"foo"}
- expect (b:compare {"foo", "bar"}).should_be (-1)
- expect (List {"foo"}:compare (List {"foo", "bar"})).should_be (-1)
- - it returns 0 when two lists are the same: |
- expect (a:compare {"foo", "bar"}).should_be (0)
- expect (a:compare (List {"foo", "bar"})).should_be (0)
- - it returns +1 when the first list is greater than the second: |
- expect (a:compare {"baz", "quux"}).should_be (1)
- expect (a:compare (List {"baz", "quux"})).should_be (1)
- - it returns +1 when the first list has additional elements: |
- expect (a:compare {"foo"}).should_be (1)
- expect (a:compare (List {"foo"})).should_be (1)
- - context when called as a '<' list metamethod:
- - it succeeds when the first list is less than the second:
- expect (a < b).should_be (true)
- - it fails when the first list is not less than the second:
- expect (a < a).should_be (false)
- expect (b < a).should_be (false)
- - context when called as a '>' list metamethod:
- - it succeeds when the first list is greater than the second:
- expect (b > a).should_be (true)
- - it fails when the first list is not greater than the second:
- expect (b > b).should_be (false)
- expect (a > b).should_be (false)
- - context when called as a '<=' list metamethod:
- - it succeeds when the first list is less than or equal to the second:
- expect (a <= b).should_be (true)
- expect (a <= a).should_be (true)
- - it fails when the first list is not less than or equal to the second:
- expect (b <= a).should_be (false)
- - context when called as a '>=' list metamethod:
- - it succeeds when the first list is greater than or equal to the second:
- expect (b >= a).should_be (true)
- expect (b >= b).should_be (true)
- - it fails when the first list is not greater than or equal to the second:
- expect (a >= b).should_be (false)
-
-
-- describe concat:
- - before: l = List {"foo", "bar"}
-
- - context when called as a list object method:
- - it returns a list object:
- l = l:concat (List {"baz"})
- expect (Object.type (l)).should_be "List"
- - it works for an empty list:
- l = List {}
- expect (l:concat (List {"baz"})).should_equal (List {"baz"})
- - it concatenates lists:
- expect (l:concat (List {"baz", "quux"})).
- should_equal (List {"foo", "bar", "baz", "quux"})
- expect (l:concat (List {"baz"}, List {"quux"})).
- should_equal (List {"foo", "bar", "baz", "quux"})
- - context whne called as a list metamethod:
- - it returns a list object:
- l = l .. List {"baz"}
- expect (Object.type (l)).should_be "List"
- - it works for an empty list:
- l = List {}
- expect (l .. List {"baz"}).should_equal (List {"baz"})
- - it concatenates lists:
- expect (l .. List {"baz", "quux"}).
- should_equal (List {"foo", "bar", "baz", "quux"})
- expect (l .. List {"baz"} .. List {"quux"}).
- should_equal (List {"foo", "bar", "baz", "quux"})
-
-
-- describe cons:
- - context when called as a list object method:
- - it returns a list object:
- l = l:cons "quux"
- expect (Object.type (l)).should_be "List"
- - it works for empty lists:
- l = List {}
- expect (l:cons "quux").should_equal (List {"quux"})
- - it prepends an item to a list:
- expect (l:cons "quux").
- should_equal (List {"quux", "foo", "bar", "baz"})
-
-
-- describe depair:
- - before:
- t = {"first", "second", third = 4}
- l = List.enpair (t)
-
- - it diagnoses an argument that is not a list of lists:
- - context when called as a list object method:
- - it returns a primitive table:
- expect (Object.type (l:depair ())).should_be "table"
- - it works with an empty list:
- l = List {}
- expect (l:depair ()).should_equal {}
- - it is the inverse of enpair:
- expect (l:depair ()).should_equal (t)
-
-
-- describe elems:
- - it is an iterator over list members:
- t = {}
- for e in List.elems (l) do table.insert (t, e) end
- expect (t).should_equal {"foo", "bar", "baz"}
- - it works for an empty list:
- t = {}
- for e in List.elems (List {}) do table.insert (t, e) end
- expect (t).should_equal {}
- - it can be called from the list module:
- t = {}
- for e in List.elems (l) do table.insert (t, e) end
- expect (t).should_equal {"foo", "bar", "baz"}
- - it can be called as a list object method:
- t = {}
- for e in l:elems () do table.insert (t, e) end
- expect (t).should_equal {"foo", "bar", "baz"}
-
-
-- describe enpair:
- - before:
- t = {"first", "second", third = 4}
-
- - it diagnoses a missing argument:
- - it diagnoses a non-table argument:
- - it returns a list object:
- expect (Object.type (List.enpair (t))).should_be "List"
- - it works for an empty table:
- expect (List.enpair {}).should_equal (List {})
- - it turns a table into a list of pairs:
- expect (List.enpair (t)).
- should_equal (List {List {1, "first"}, List {2, "second"}, List {"third", 4}})
-
-
-- describe filter:
- - before:
- l = List {"foo", "bar", "baz", "quux"}
- p = function (e) return (e:match "a" ~= nil) end
-
- - context when called as a list object method:
- - it returns a list object:
- m = l:filter (p)
- expect (Object.type (m)).should_be "List"
- - it works for an empty list:
- l = List {}
- expect (l:filter (p)).should_equal (List {})
- - it filters a list according to a predicate:
- expect (l:filter (p)).should_equal (List {"bar", "baz"})
-
-
-- describe flatten:
- - before:
- l = List {List {List {"one"}}, "two", List {List {"three"}, "four"}}
-
- - context when called as a list object method:
- - it returns a list object:
- m = List.flatten (l)
- expect (Object.type (m)).should_be "List"
- - it works for an empty list:
- l = List {}
- expect (l:flatten ()).should_equal (List {})
- - it flattens a list:
- expect (l:flatten ()).
- should_equal (List {"one", "two", "three", "four"})
-
-
-- describe foldl:
- - before:
- op = (require "std.functional").op
- l = List {1, 10, 100}
-
- - context when called as a list object method:
- - it works with an empty list:
- l = List {}
- expect (l:foldl (op["+"], 10000)).should_be (10000)
- - it folds a binary function through a list:
- expect (l:foldl (op["+"], 10000)).should_be (10111)
-
-
-- describe foldr:
- - before:
- op = (require "std.functional").op
- l = List {1, 10, 100}
-
- - context when called as a list object method:
- - it works with an empty list:
- l = List {}
- expect (l:foldl (op["/"], 1)).should_be (1)
- - it folds a binary function through a list:
- expect (l:foldl (op["/"], 10000)).should_be (10)
-
-
-- describe index_key:
-
-
-- describe index_value:
-
-
-- describe map:
- - before:
- l = List {1, 2, 3, 4, 5}
- f = function (n) return n * n end
-
- - context when called as a list object method:
- - it returns a list object:
- m = l:map (f)
- expect (Object.type (m)).should_be "List"
- - it works for an empty list:
- l = List {}
- expect (l:map (f)).should_equal (List {})
- - it creates a new list:
- o = l
- m = l:map (f)
- expect (l).should_equal (o)
- expect (m).should_not_equal (o)
- expect (l).should_equal (List {1, 2, 3, 4, 5})
- - it maps a function over a list:
- expect (l:map (f)).should_equal (List {1, 4, 9, 16, 25})
-
-
-- describe map_with:
- - before:
- l = List {List {1, 2, 3}, List {4, 5}}
- f = function (...) return select ("#", ...) end
-
- - it diagnoses an argument that is not a list of lists:
- - context when called as a list object method:
- - it returns a list object:
- m = l:map_with (f)
- expect (Object.type (m)).should_be "List"
- - it works for an empty list:
- l = List {}
- expect (l:map_with (f)).should_equal (List {})
- - it creates a new list:
- o = l
- m = l:map_with (f)
- expect (l).should_equal (o)
- expect (m).should_not_equal (o)
- expect (l).should_equal (List {List {1, 2, 3}, List {4, 5}})
- - it maps a function over a list:
- expect (l:map_with (f)).should_equal (List {3, 2})
-
-
-- describe project:
- - before:
- l = List {
- {first = false, second = true, third = true},
- {first = 1, second = 2, third = 3},
- {first = "1st", second = "2nd", third = "3rd"},
- }
-
- - it diagnoses an argument that is not a list of tables:
- - context when called as a list object method:
- - it returns a list object:
- p = l:project ("third")
- expect (Object.type (p)).should_be "List"
- - it works with an empty list:
- l = List {}
- expect (l:project ("third")).should_equal (List {})
- - it projects a list of fields from a list of tables:
- expect (l:project ("third")).
- should_equal (List {true, 3, "3rd"})
- - it projects fields with a falsey value correctly: |
- pending "see issue #34"
- expect (l:project ("first")).
- should_equal (List {false, 1, "1st"})
-
-
-- describe relems:
- - it is a reverse iterator over list members:
- t = {}
- for e in List.relems (l) do table.insert (t, e) end
- expect (t).should_equal {"baz", "bar", "foo"}
- - it works for an empty list:
- t = {}
- for e in List.relems (List {}) do table.insert (t, e) end
- expect (t).should_equal {}
- - it can be called from the list module:
- t = {}
- for e in List.relems (l) do table.insert (t, e) end
- expect (t).should_equal {"baz", "bar", "foo"}
- - it can be called as a list object method:
- t = {}
- for e in l:relems () do table.insert (t, e) end
- expect (t).should_equal {"baz", "bar", "foo"}
-
-
-- describe rep:
- - before: l = List {"foo", "bar"}
-
- - context when called as a list object method:
- - it returns a list object:
- expect (Object.type (l:rep (3))).should_be "List"
- - it works for an empty list:
- l = List {}
- expect (l:rep (99)).should_equal (List {})
- - it repeats the contents of a list:
- expect (l:rep (3)).
- should_equal (List {"foo", "bar", "foo", "bar", "foo", "bar"})
-
-
-- describe reverse:
- - before: l = List {"foo", "bar", "baz", "quux"}
-
- - context when called as a list object method:
- - it returns a list object:
- expect (Object.type (l:reverse ())).should_be "List"
- - it works for an empty list:
- l = List {}
- expect (l:reverse ()).should_equal (List {})
- - it makes a new reversed list:
- m = l
- expect (l:reverse ()).
- should_equal (List {"quux", "baz", "bar", "foo"})
- expect (l).should_equal (List {"foo", "bar", "baz", "quux"})
- expect (l).should_be (m)
-
-
-- describe shape:
-
-
-- describe sub:
- - before: l = List {1, 2, 3, 4, 5, 6, 7}
-
- - context when called as a list object method:
- - it returns a list object: |
- expect (Object.type (l:sub (1, 1))).should_be "List"
- - it makes a list from a subrange of another list: |
- expect (l:sub (2, 5)).should_equal (List {2, 3, 4, 5})
- - it truncates the result if 'to' argument is too large: |
- expect (l:sub (5, 10)).should_equal (List {5, 6, 7})
- - it defaults 'to' to the end of the list: |
- expect (l:sub (5)).should_equal (List {5, 6, 7})
- - it defaults 'from' to the beginning of the list: |
- expect (l:sub ()).should_equal (l)
- - it returns an empty list when 'from' is greater than 'to': |
- expect (l:sub (2, 1)).should_equal (List {})
- - it counts from the end of the list for a negative 'from' argument: |
- expect (l:sub (-3)).should_equal (List {5, 6, 7})
- - it counts from the end of the list for a negative 'to' argument: |
- expect (l:sub (-5, -2)).should_equal (List {3, 4, 5, 6})
-
-
-- describe tail:
- - before: l = List {1, 2, 3, 4, 5, 6, 7}
-
- - context when called as a list object method:
- - it returns a list object: |
- expect (Object.type (l:tail ())).should_be "List"
- - it makes a new list with the first element removed: |
- expect (l:tail ()).should_equal (List {2, 3, 4, 5, 6, 7})
- - it works for an empty list: |
- l = List {}
- expect (l:tail ()).should_equal (List {})
- - it returns an empty list when passed a list with one element: |
- l = List {1}
- expect (l:tail ()).should_equal (List {})
-
-
-- describe transpose:
-
-
-- describe zip_with:
diff --git a/specs/math_spec.yaml b/specs/math_spec.yaml
deleted file mode 100644
index d812d4a..0000000
--- a/specs/math_spec.yaml
+++ /dev/null
@@ -1,64 +0,0 @@
-specify math:
-- before: |
- M = require "std.math"
-
- extends = math
- enhancements = { "floor" }
- extensions = { "round" }
-
-
-- context when required:
- - before:
- enhanced = {}
- for _, api in ipairs (enhancements) do enhanced[api] = true end
-
- - context by name:
- - before: |
- function restore (g, m)
- for _, api in ipairs (enhancements) do
- g[api], g["_" .. api] = m[api], m["_" .. api]
- end
- for _, api in ipairs (extensions) do g[api] = m[api] end
- end
-
- for _, api in ipairs (enhancements) do
- extends[api] = M["_" .. api]
- end
- for _, api in ipairs (extensions) do extends[api] = nil end
- - after:
- restore (extends, M)
- - it does not perturb the global table:
- for _, api in ipairs (extensions) do
- expect (extends[api]).should_be (nil)
- end
- for _, api in ipairs (enhancements) do
- expect (extends[api]).should_be (M["_" .. api])
- end
- - it contains all global access points:
- for api in pairs (extends) do
- if enhanced[api] then
- expect (M[api]).should_not_be (extends[api])
- else
- expect (M[api]).should_be (extends[api])
- end
- end
-
- - context via the std module:
- - before:
- require "std"
- - it adds extension apis to the global table:
- for api in pairs (M) do
- expect (extends[api]).should_be (M[api])
- end
- - it does not add any other global access points:
- for api in pairs (extends) do
- if not enhanced[api] then
- expect (M[api]).should_be (extends[api])
- end
- end
-
-
-- describe floor:
-
-
-- describe round:
diff --git a/specs/object_spec.yaml b/specs/object_spec.yaml
deleted file mode 100644
index 0b36dd8..0000000
--- a/specs/object_spec.yaml
+++ /dev/null
@@ -1,322 +0,0 @@
-before:
- Object = require "std.object"
- obj = Object {"foo", "bar", baz="quux"}
-
-specify Object:
-- describe construction:
- - context from Object clone method:
- - it constructs a new object:
- obj = Object:clone {}
- expect (obj).should_not_be (Object)
- expect (type (obj)).should_be "table"
- expect (Object.type (obj)).should_be "Object"
- - it reuses the Object metatable:
- o = obj:clone {"o"}
- p = o:clone {"p"}
- expect (p).should_not_be (o)
- expect (getmetatable (o)).should_be (getmetatable (p))
- - it sets object fields from arguments:
- o = obj:clone {}
- expect (o).should_not_be (obj)
- expect (o).should_equal (obj)
- - it serves as a prototype for new instances:
- o = obj:clone {}
- expect (Object.type (o)).should_be "Object"
- expect (o).should_not_be (obj)
- expect (o).should_equal (obj)
- expect (getmetatable (o)).should_be (getmetatable (obj))
- - it separates '_' prefixed fields:
- expect (Object:clone {foo="bar", _baz="quux"}).
- should_equal (Object:clone {foo="bar"})
- - it puts '_' prefixed fields in a new metatable:
- obj = Object:clone {foo="bar", _baz="quux"}
- expect (getmetatable (obj)).should_not_be (getmetatable (Object))
- expect (getmetatable (obj)._baz).should_be "quux"
-
- # Object {args} is just syntactic sugar for Object:clone {args}
- - context from Object prototype:
- - it constructs a new object:
- obj = Object {}
- expect (obj).should_not_be (Object)
- expect (type (obj)).should_be "table"
- expect (Object.type (obj)).should_be "Object"
- - it reuses the object metatable:
- o, p = Object {"o"}, Object {"p"}
- expect (getmetatable (o)).should_be (getmetatable (p))
- - it sets object fields from arguments:
- o = Object {"foo", "bar", baz="quux"}
- expect (o).should_equal (obj)
- - it serves as a prototype for new instances:
- o = obj {}
- expect (Object.type (o)).should_be "Object"
- expect (o).should_not_be (obj)
- expect (o).should_equal (obj)
- expect (getmetatable (o)).should_be (getmetatable (obj))
- - it separates '_' prefixed fields:
- expect (Object {foo="bar", _baz="quux"}).
- should_equal (Object {foo="bar"})
- - it puts '_' prefixed fields in a new metatable:
- obj = Object {foo="bar", _baz="quux"}
- expect (getmetatable (obj)).should_not_be (getmetatable (Object))
- expect (getmetatable (obj)._baz).should_be "quux"
-
-- describe type:
- - before: o = Object {}
-
- - context when called from the object module:
- - it reports the type stored in the object's metatable:
- expect (Object.type (o)).should_be "Object"
- - it reports the type of a cloned object:
- expect (Object.type (o {})).should_be "Object"
- - it reports the type of a subclassed object:
- Example = Object {_type = "Example"}
- expect (Object.type (Example)).should_be "Example"
- - it reports the type of a cloned subclassed object:
- Portal = Object {_type = "Demon"}
- p = Portal {}
- expect (Object.type (p)).should_be "Demon"
- expect (Object.type (p {})).should_be "Demon"
- - context when called as an object method:
- - it reports the type stored in the object's metatable:
- expect (o:type ()).should_be "Object"
- - it reports the type of a cloned object:
- expect ((o {}):type ()).should_be "Object"
- - it reports the type of a subclassed object:
- Example = Object {_type = "Example"}
- expect (Example:type ()).should_be "Example"
- - it reports the type of a cloned subclassed object:
- Portal = Object {_type = "Demon"}
- p = Portal {}
- expect (p:type ()).should_be "Demon"
- expect ((p {}):type ()).should_be "Demon"
-
-
-- describe instantiation from a prototype:
- - before:
- totable = (require "std.table").totable
-
- - context when _init is nil:
- - before:
- Array = Object {
- _type = "Array",
- "foo", "bar", "baz",
- }
- Array._init = nil
- - it contains user-defined fields:
- expect (totable (Array)).
- should_equal {"foo", "bar", "baz"}
- - it sets array part of instance object from positional parameters:
- array = Array {"first", "second", "third"}
- expect (totable (array)).
- should_equal {"first", "second", "third"}
- - it uses prototype values for missing positional parameters:
- array = Array {"first", "second"}
- expect (totable (array)).
- should_equal {"first", "second", "baz"}
- - it merges surplas positional parameters:
- array = Array {"first", "second", "third", "fourth"}
- expect (totable (array)).
- should_equal {"first", "second", "third", "fourth"}
-
- - context when _init is an empty table:
- - before:
- Prototype = Object {
- _type = "Prototype";
- _init = {},
- "first", "second", "third",
- }
- - it contains user-defined fields:
- expect (totable (Prototype)).
- should_equal {"first", "second", "third"}
- - it ignores positional parameters: |
- instance = Prototype {"foo", "bar"}
- expect (instance).should_not_be (Prototype)
- pending "see issue #35"
- expect (instance).should_equal (Prototype)
-
- - context when _init is a table of field names:
- - before:
- Process = Object {
- _type = "Process",
- _init = {"status", "output", "errout"},
- status = -1,
- output = "empty",
- errout = "no errors",
- }
- - it contains user-defined fields:
- expect (totable (Process)).
- should_equal {status = -1, output = "empty", errout = "no errors"}
- - it sets user-defined fields from positional parameters:
- proc = Process {0, "output", "diagnostics"}
- expect (totable (proc)).
- should_equal {status = 0, output = "output", errout = "diagnostics"}
- - it uses prototype values for missing positional parameters:
- proc = Process {0, "output"}
- expect (totable (proc)).
- should_equal {status = 0, output = "output", errout = "no errors"}
- - it merges surplus positional parameters into array part of new object: |
- proc = Process {0, "output", "diagnostics", "garbage"}
- pending "see issue #35"
- expect (totable (proc)).
- should_equal { status = 0, output = "output", errout = "diagnostics" }
-
- - context when _init is a function:
- - before:
- Prototype = Object {
- _type = "Prototype",
- f1 = "proto1", f2 = "proto2",
- _init = function (self, ...)
- self.args = unpack {...}
- return self
- end,
- }
- - it passes user defined fields to custom _init function:
- instance = Prototype {"param1", "param2"}
- expect ({instance.f1, instance.f2, instance.args}).
- should_equal {"proto1", "proto2", {"param1", "param2"}}
-
-- describe field access:
- - before:
- Prototype = Object {
- _type = "Prototype",
- _init = { "field", "method"},
- field = "in prototype",
- method = function (self, ...)
- return Object.type (self) .. " class, " ..
- table.concat ({...}, ", ")
- end,
- }
- instance = Prototype {"in object", function (self, ...)
- return Object.type (self) .. " instance, " ..
- table.concat ({...}, ", ")
- end,
- }
-
- - it provides object field access with dot notation:
- expect (instance.field).should_be "in object"
- - it provides class field acces with dot notation:
- expect (Prototype.field).should_be "in prototype"
- - it provides object method acces with colon notation:
- expect (instance:method "object method call").
- should_be "Prototype instance, object method call"
- - it provides class method access with class dot notation:
- expect (Prototype.method (instance, "class method call")).
- should_be "Prototype class, class method call"
- - it allows new instance fields to be added:
- instance.newfield = "new"
- expect (instance.newfield).should_be "new"
- - it allows new instance methods to be added:
- instance.newmethod = function (self)
- return Object.type (self) .. ", new instance method"
- end
- expect (instance:newmethod ()).should_be "Prototype, new instance method"
- - it allows new class methods to be added:
- Prototype.newmethod = function (self)
- return Object.type (self) .. ", new class method"
- end
- expect (Prototype.newmethod (instance)).
- should_be "Prototype, new class method"
-
-
-- describe object method propagation:
- - context with no custom instance methods:
- # :type is a method defined by the root object
- - it inherits prototype object methods:
- instance = Object {}
- expect (instance:type ()).should_be "Object"
- - it propagates prototype methods to derived instances:
- Derived = Object {_type = "Derived"}
- instance = Derived {}
- expect (instance:type ()).should_be "Derived"
- - context with custom object methods:
- - before:
- bag = Object {
- _type = "bag",
- __index = {
- add = function (self, item)
- self[item] = (self[item] or 0) + 1
- return self
- end,
- },
- }
- # :type is a method defined by the root object
- - it inherits prototype object methods:
- expect (bag:type ()).should_be "bag"
- - it propagates prototype methods to derived instances:
- instance = bag {}
- expect (instance:type ()).should_be "bag"
- - it supports method calls:
- expect (bag:add "foo").should_be (bag)
- expect (bag.foo).should_be (1)
-
-
-# Metatable propagation is an important property of Object cloning,
-# because Lua will only call __lt and __le metamethods when both
-# arguments share the same metatable - i.e. the previous behaviour
-# of making each object its own metatable precluded ever being able
-# to use __lt and __le!
-- describe object metatable propagation:
- - before: root_mt = getmetatable (Object)
-
- - context with no custom metamethods:
- - it inherits prototype object metatable:
- instance = Object {}
- expect (getmetatable (instance)).should_be (root_mt)
- - it propagates prototype metatable to derived instances:
- Derived = Object {_type = "Derived"}
- instance = Derived {}
- expect (getmetatable (Derived)).should_not_be (root_mt)
- expect (getmetatable (instance)).should_be (getmetatable (Derived))
- - context with custom metamethods:
- - before:
- base = require "std.base"
- bag = Object {
- _type = "bag",
- __lt = function (a, b) return base.compare (a, b) < 0 end,
- }
- - it has it's own metatable:
- expect (getmetatable (bag)).should_not_be (root_mt)
- - it propagates prototype metatable to derived instances:
- instance = bag {}
- expect (getmetatable (instance)).should_be (getmetatable (bag))
- - it supports __lt calls:
- a, b = bag {"a"}, bag {"b"}
- expect (a < b).should_be (true)
- expect (a < a).should_be (false)
- expect (a > b).should_be (false)
-
-
-- describe __totable:
- - before:
- totable = (require "std.table").totable
- Derived = Object {_type = "Derived", "one", "two", three = true}
-
- - it returns a table:
- expect (Object.type (totable (Derived))).should_be "table"
- - it contains all non-hidden fields of object:
- expect (totable (Derived)).should_contain.all_of {"one", "two", "three"}
- - it does not contain any hidden fields of object:
- expect (totable (Derived)).should_equal {"one", "two", three = true}
-
-
-- describe __tostring:
- - before:
- obj = Object {_type = "Derived", "one", "two", "three"}
- - it returns a string:
- expect (type (tostring (obj))).should_be "string"
- - it contains the type:
- expect (tostring (Object {})).should_contain "Object"
- expect (tostring (obj)).should_contain (Object.type (obj))
- - it contains the ordered array part elements:
- expect (tostring (obj)).should_contain "one, two, three"
- - it contains the ordered dictionary part elements:
- expect (tostring (Object {one = true, two = true, three = true})).
- should_contain "one=true, three=true, two=true"
- expect (tostring (obj {one = true, two = true, three = true})).
- should_contain "one=true, three=true, two=true"
- - it contains a ';' separator only when object has array and dictionary parts:
- expect (tostring (obj)).should_not_contain ";"
- expect (tostring (Object {one = true, two = true, three = true})).
- should_not_contain ";"
- expect (tostring (obj {one = true, two = true, three = true})).
- should_contain ";"
diff --git a/specs/package_spec.yaml b/specs/package_spec.yaml
deleted file mode 100644
index c7b4bbd..0000000
--- a/specs/package_spec.yaml
+++ /dev/null
@@ -1,63 +0,0 @@
-specify package:
-- before: |
- M = require "std.package"
-
- extends = package
- extensions = { "dirsep", "pathsep", "path_mark", "execdir", "igmark" }
- enhancements = {}
-
- -- Do not try to check all the entries in unextended package,
- -- because they naturally change as modules are loaded.
- coreapis = { "config", "cpath", "loaders", "loadlib", "preload",
- "searchers", "searchpath", "seeall" }
-
-- context when required:
- - before:
- enhanced = {}
- for _, api in ipairs (enhancements) do enhanced[api] = true end
-
- - context by name:
- - before:
- for _, api in ipairs (enhancements) do
- extends[api] = M["_" .. api]
- end
- for _, api in ipairs (extensions) do extends[api] = nil end
- - after:
- for _, api in ipairs (enhancements) do
- extends[api], extends["_" .. api] = M[api], M["_" .. api]
- end
- for _, api in ipairs (extensions) do extends[api] = M[api] end
- - it does not perturb the global table:
- for _, api in ipairs (extensions) do
- expect (extends[api]).should_be (nil)
- end
- for _, api in ipairs (enhancements) do
- expect (extends[api]).should_be (M["_" .. api])
- end
- - it contains all global access points:
- for _, api in ipairs (coreapis) do
- if enhanced[api] then
- expect (M[api]).should_not_be (extends[api])
- else
- expect (M[api]).should_be (extends[api])
- end
- end
-
- - context via the std module:
- - before:
- require "std"
- - it adds extension apis to the global table:
- for _, api in ipairs (extensions) do
- expect (extends[api]).should_be (M[api])
- end
- - it does not add any other global access points:
- for _, api in ipairs (coreapis) do
- if not enhanced[api] then
- expect (M[api]).should_be (extends[api])
- end
- end
-
-- "it splits package.config up":
- expect (string.format ("%s\n%s\n%s\n%s\n%s\n",
- M.dirsep, M.pathsep, M.path_mark, M.execdir, M.igmark)
- ).should_contain (package.config)
diff --git a/specs/set_spec.yaml b/specs/set_spec.yaml
deleted file mode 100644
index 3f674cf..0000000
--- a/specs/set_spec.yaml
+++ /dev/null
@@ -1,320 +0,0 @@
-before:
- require "spec_helper"
- Object = require "std.object"
- Set = require "std.set"
- totable = (require "std.table").totable
- s = Set {"foo", "bar", "bar"}
-
-specify Set:
-- describe construction:
- - context from Set clone method:
- - it constructs a new set:
- s = Set:clone {}
- expect (s).should_not_be (Set)
- expect (Object.type (s)).should_be "Set"
- - it reuses the Set metatable:
- s, t = Set:clone {"s"}, Set:clone {"t"}
- expect (getmetatable (s)).should_be (getmetatable (t))
- - it initialises set with constructor parameters:
- t = Set:clone {"foo", "bar", "bar"}
- expect (t).should_equal (s)
- - it serves as a prototype for new instances:
- obj = s:clone {}
- expect (Object.type (obj)).should_be "Set"
- expect (obj).should_equal (s)
- expect (getmetatable (obj)).should_be (getmetatable (s))
-
- # Set {args} is just syntactic sugar for Set:clone {args}
- - context from Set object prototype:
- - it constructs a new set:
- s = Set {}
- expect (s).should_not_be (Set)
- expect (Object.type (s)).should_be "Set"
- - it reuses the Set metatable:
- s, t = Set {"s"}, Set {"t"}
- expect (getmetatable (s)).should_be (getmetatable (t))
- - it initialises set with constructor parameters:
- t = Set:clone {"foo", "bar", "bar"}
- expect (t).should_equal (s)
- - it serves as a prototype for new instances:
- obj = s {}
- expect (Object.type (obj)).should_be "Set"
- expect (obj).should_equal (s)
- expect (getmetatable (obj)).should_be (getmetatable (s))
-
-
-- describe delete:
- - context when called as a set object method:
- - before:
- s = Set {"foo", "bar", "baz"}
- - it returns a set object:
- expect (Object.type (s:delete ("foo"))).should_be "Set"
- - it is destructive:
- s:delete ("bar")
- expect (s).should_not_have_member "bar"
- - it returns the modified set:
- expect (s:delete ("baz")).should_not_have_member "baz"
- - it ignores removal of non-members: |
- clone = s {}
- expect (s:delete ("quux")).should_equal (clone)
- - it deletes a member from the set:
- expect (s).should_have_member "bar"
- s:delete ("bar")
- expect (s).should_not_have_member "bar"
- - it works with an empty set:
- expect (Set.delete (Set {}, "quux")).should_equal (Set {})
-
-
-- describe difference:
- - before:
- r = Set {"foo", "bar", "baz"}
- s = Set {"bar", "baz", "quux"}
-
- - context when called as a set object method:
- - it returns a set object:
- expect (Object.type (r:difference (s))).should_be "Set"
- - it is non-destructive:
- r:difference (s)
- expect (r).should_equal (Set {"foo", "bar", "baz"})
- expect (s).should_equal (Set {"bar", "baz", "quux"})
- - it returns a set containing members of the first that are not in the second:
- expect (r:difference (s)).should_equal (Set {"foo"})
- - it coerces a table argument to a set:
- expect (r:difference {"bar"}).should_equal (Set {"baz", "foo"})
- - context when called as a set metamethod:
- - it returns a set object:
- expect (Object.type (r - s)).should_be "Set"
- - it is non-destructive:
- q = r - s
- expect (r).should_equal (Set {"foo", "bar", "baz"})
- expect (s).should_equal (Set {"bar", "baz", "quux"})
- - it returns a set containing members of the first that are not in the second:
- expect (r - s).should_equal (Set {"foo"})
- - it coerces a table argument to a set:
- expect (r - {"bar"}).should_equal (Set {"baz", "foo"})
-
-
-- describe elems:
- - before:
- s = Set {"foo", "bar", "baz"}
-
- - context when called as a set object method:
- - it is an iterator over set members:
- t = {}
- for e in s:elems () do table.insert (t, e) end
- table.sort (t)
- expect (t).should_equal {"bar", "baz", "foo"}
- - it works for an empty set:
- t = {}
- for e in (Set {}):elems () do table.insert (t, e) end
- expect (t).should_equal {}
-
-
-- describe insert:
- - context when called as a set object method:
- - before:
- s = Set {"foo"}
- - it returns a set object:
- expect (Object.type (s:insert ("bar"))).should_be "Set"
- - it is destructive:
- s:insert ("bar")
- expect (s).should_have_member "bar"
- - it returns the modified set:
- expect (s:insert ("baz")).should_have_member "baz"
- - it ignores insertion of existing members:
- expect (s:insert ("foo")).should_equal (Set {"foo"})
- - it inserts a new member into the set:
- expect (s).should_not_have_member "bar"
- s:insert ("bar")
- expect (s).should_have_member "bar"
- - it works with an empty set:
- expect ((Set {}):insert ("foo")).should_equal (s)
-
-
-- describe intersection:
- - before:
- r = Set {"foo", "bar", "baz"}
- s = Set {"bar", "baz", "quux"}
-
- - context when called as a set object method:
- - it returns a set object:
- expect (Object.type (r:intersection (s))).should_be "Set"
- - it is non-destructive:
- r:intersection (s)
- expect (r).should_equal (Set {"foo", "bar", "baz"})
- expect (s).should_equal (Set {"bar", "baz", "quux"})
- - it returns a set containing members common to both arguments:
- expect (r:intersection (s)).
- should_equal (Set {"bar", "baz"})
- - it coerces a table argument to a set:
- expect (r:intersection ({"bar", "quux"})).
- should_equal (Set {"bar"})
- - context when called as a set metamethod:
- - it returns a set object:
- expect (Object.type (r:intersection (r))).should_be "Set"
- - it is non-destructive:
- q = r * s
- expect (r).should_equal (Set {"foo", "bar", "baz"})
- expect (s).should_equal (Set {"bar", "baz", "quux"})
- - it returns a set containing members common to both arguments:
- expect (r * s).should_equal (Set {"bar", "baz"})
- - it coerces a table argument to a set:
- expect (r * {"bar", "quux"}).should_equal (Set {"bar"})
-
-
-- describe member:
- - before: s = Set {"foo", "bar"}
-
- - context when called as a set object method:
- - it succeeds when set contains the given member:
- expect (s:member ("foo")).should_be (true)
- - it fails when set does not contain the given member:
- expect (s:member ("baz")).should_be (false)
- - it works with the empty set:
- expect ((Set {}):member ("foo")).should_be (false)
-
-
-- describe propersubset:
- - before:
- r = Set {"foo", "bar", "baz"}
- s = Set {"bar", "baz"}
-
- - context when called as a set object method:
- - it succeeds when set contains all elements of another:
- expect (s:propersubset (r)).should_be (true)
- - it fails when two sets are equal:
- r = s {}
- expect (s:propersubset (r)).should_be (false)
- - it fails when set does not contain all elements of another:
- s = s + Set {"quux"}
- expect (r:propersubset (s)).should_be (false)
- - it coerces a table argument to a set:
- expect (s:propersubset {"foo", "bar", "baz"}).should_be (true)
- expect (s:propersubset {"foo"}).should_be (false)
- - context when called as a set metamethod:
- - it succeeds when set contains all elements of another:
- expect (s < r).should_be (true)
- - it fails when two sets are equal:
- r = s {}
- expect (s < r).should_be (false)
- - it fails when set does not contain all elements of another:
- s = s + Set {"quux"}
- expect (r < s).should_be (false)
-
-
-- describe subset:
- - before:
- r = Set {"foo", "bar", "baz"}
- s = Set {"bar", "baz"}
-
- - context when called as a set object method:
- - it succeeds when set contains all elements of another:
- expect (s:subset (r)).should_be (true)
- - it succeeds when two sets are equal:
- r = s {}
- expect (s:subset (r)).should_be (true)
- - it fails when set does not contain all elements of another:
- s = s + Set {"quux"}
- expect (r:subset (s)).should_be (false)
- - it coerces a table argument to a set:
- expect (s:subset {"foo", "bar", "baz"}).should_be (true)
- expect (s:subset {"foo"}).should_be (false)
- - context when called as a set metamethod:
- - it succeeds when set contains all elements of another:
- expect (s <= r).should_be (true)
- - it succeeds when two sets are equal:
- r = s {}
- expect (s <= r).should_be (true)
- - it fails when set does not contain all elements of another:
- s = s + Set {"quux"}
- expect (r <= s).should_be (false)
-
-
-- describe symmetric_difference:
- - before:
- r = Set {"foo", "bar", "baz"}
- s = Set {"bar", "baz", "quux"}
-
- - context when called as a set object method:
- - it returns a set object:
- expect (Object.type (r:symmetric_difference (s))).
- should_be "Set"
- - it is non-destructive:
- r:symmetric_difference (s)
- expect (r).should_equal (Set {"foo", "bar", "baz"})
- expect (s).should_equal (Set {"bar", "baz", "quux"})
- - it returns a set containing members in only one argument set:
- expect (r:symmetric_difference (s)).
- should_equal (Set {"foo", "quux"})
- - it coerces a table argument to a set:
- expect (r:symmetric_difference {"bar"}).
- should_equal (Set {"baz", "foo"})
- - context when called as a set metamethod:
- - it returns a set object:
- expect (Object.type (r / s)).should_be "Set"
- - it is non-destructive:
- q = r / s
- expect (r).should_equal (Set {"foo", "bar", "baz"})
- expect (s).should_equal (Set {"bar", "baz", "quux"})
- - it returns a set containing members in only one argument set:
- expect (r / s).should_equal (Set {"foo", "quux"})
- - it coerces a table argument to a set:
- expect (r / {"bar"}).should_equal (Set {"baz", "foo"})
-
-
-- describe union:
- - before:
- r = Set {"foo", "bar", "baz"}
- s = Set {"bar", "baz", "quux"}
-
- - context when called as a set object method:
- - it returns a set object:
- expect (Object.type (r:union (s))).should_be "Set"
- - it is non-destructive:
- r:union (s)
- expect (r).should_equal (Set {"foo", "bar", "baz"})
- expect (s).should_equal (Set {"bar", "baz", "quux"})
- - it returns a set containing members in only one argument set:
- expect (r:union (s)).
- should_equal (Set {"foo", "bar", "baz", "quux"})
- - it coerces a table argument to a set:
- expect (r:union {"quux"}).
- should_equal (Set {"foo", "bar", "baz", "quux"})
- - context when called as a set metamethod:
- - it returns a set object:
- expect (Object.type (r + s)).should_be "Set"
- - it is non-destructive:
- q = r + s
- expect (r).should_equal (Set {"foo", "bar", "baz"})
- expect (s).should_equal (Set {"bar", "baz", "quux"})
- - it returns a set containing members in only one argument set:
- expect (r + s).should_equal (Set {"foo", "bar", "baz", "quux"})
- - it coerces a table argument to a set:
- expect (r + {"quux"}).
- should_equal (Set {"foo", "bar", "baz", "quux"})
-
-
-- describe __totable:
- - before:
- s = Set {"foo", "bar", "baz"}
-
- - it returns a table:
- expect (Object.type (totable (s))).should_be "table"
- - it contains all non-hidden fields of object:
- expect (totable (s)).should_contain.all_of {"foo", "bar", "baz"}
- - it contains fields of set in order:
- expect (totable (s)).should_equal {"bar", "baz", "foo"}
- - it does not contain any hidden fields of object:
- expect (totable (s)).should_equal {"bar", "baz", "foo"}
-
-
-- describe __tostring:
- - before:
- s = Set {"foo", "bar", "baz"}
-
- - it returns a string:
- expect (type (tostring (s))).should_be "string"
- - it contains the type:
- expect (tostring (s)).should_contain "Set"
- - it contains the ordered set elements:
- expect (tostring (s)).should_contain "bar, baz, foo"
diff --git a/specs/spec_helper.lua b/specs/spec_helper.lua
deleted file mode 100644
index 2480cc1..0000000
--- a/specs/spec_helper.lua
+++ /dev/null
@@ -1,29 +0,0 @@
--- Not local, so that it is available in spec examples.
-totable = (require "std.table").totable
-
-
--- Custom matcher for set membership.
-
-local set = require "std.set"
-local util = require "specl.util"
-local matchers = require "specl.matchers"
-
-local Matcher, matchers, q =
- matchers.Matcher, matchers.matchers, matchers.stringify
-
-matchers.have_member = Matcher {
- function (actual, expect)
- return set.member (actual, expect)
- end,
-
- actual = "set",
-
- format_expect = function (expect)
- return " a set containing " .. q (expect) .. ", "
- end,
-
- format_any_of = function (alternatives)
- return " a set containing any of " ..
- util.concat (alternatives, util.QUOTED) .. ", "
- end,
-}
diff --git a/specs/specs.mk b/specs/specs.mk
deleted file mode 100644
index dbec97e..0000000
--- a/specs/specs.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Specl specs make rules.
-
-
-## ------------ ##
-## Environment. ##
-## ------------ ##
-
-SPECL_ENV = $(LUA_ENV)
-
-
-## ------ ##
-## Specs. ##
-## ------ ##
-
-specl_SPECS = \
- $(srcdir)/specs/debug_spec.yaml \
- $(srcdir)/specs/getopt_spec.yaml \
- $(srcdir)/specs/io_spec.yaml \
- $(srcdir)/specs/list_spec.yaml \
- $(srcdir)/specs/math_spec.yaml \
- $(srcdir)/specs/object_spec.yaml \
- $(srcdir)/specs/package_spec.yaml \
- $(srcdir)/specs/set_spec.yaml \
- $(srcdir)/specs/strbuf_spec.yaml \
- $(srcdir)/specs/string_spec.yaml \
- $(srcdir)/specs/table_spec.yaml \
- $(NOTHING_ELSE)
-
-EXTRA_DIST += \
- $(srcdir)/specs/spec_helper.lua \
- $(NOTHING_ELSE)
-
-include build-aux/specl.mk
diff --git a/specs/strbuf_spec.yaml b/specs/strbuf_spec.yaml
deleted file mode 100644
index 11bdc9a..0000000
--- a/specs/strbuf_spec.yaml
+++ /dev/null
@@ -1,79 +0,0 @@
-before:
- object = require "std.object"
- StrBuf = require "std.strbuf"
- b = StrBuf {"foo", "bar"}
-
-specify StrBuf:
-- describe construction:
- - context from StrBuf clone method:
- - it constructs a new strbuf:
- b = StrBuf:clone {}
- expect (b).should_not_be (StrBuf)
- expect (object.type (b)).should_be "StrBuf"
- - it reuses the StrBuf metatable:
- a, b = StrBuf:clone {"a"}, StrBuf:clone {"b"}
- expect (getmetatable (a)).should_be (getmetatable (b))
- - it initialises strbuf with constructor parameters:
- a = StrBuf:clone {"foo", "bar"}
- expect (a).should_equal (b)
- - it serves as a prototype for new instances:
- obj = b:clone {}
- expect (object.type (obj)).should_be "StrBuf"
- expect (obj).should_equal (b)
- expect (getmetatable (obj)).should_be (getmetatable (b))
-
- # StrBuf {args} is just syntactic sugar for StrBuf:clone {args}
- - context from StrBuf object prototype:
- - it constructs a new strbuf:
- b = StrBuf {}
- expect (b).should_not_be (StrBuf)
- expect (object.type (b)).should_be "StrBuf"
- - it reuses the StrBuf metatable:
- a, b = StrBuf {"a"}, StrBuf {"b"}
- expect (getmetatable (a)).should_be (getmetatable (b))
- - it initialises strbuf with constructor parameters:
- a = StrBuf:clone {"foo", "bar"}
- expect (a).should_equal (b)
- - it serves as a prototype for new instances:
- obj = b {}
- expect (object.type (obj)).should_be "StrBuf"
- expect (obj).should_equal (b)
- expect (getmetatable (obj)).should_be (getmetatable (b))
-
-
-- describe tostring:
- - it can be called from strbuf module:
- expect (StrBuf.tostring (b)).should_be "foobar"
- - it can be called as a strbuf object method:
- expect (b:tostring ()).should_be "foobar"
- - it can be called as a strbuf metabethod:
- expect (tostring (b)).should_be "foobar"
-
-
-- describe concat:
- - before:
- b = StrBuf {"foo", "bar"}
- - it can be called from strbuf module:
- b = StrBuf.concat (b, "baz")
- expect (object.type (b)).should_be "StrBuf"
- expect (StrBuf.tostring (b)).should_be "foobarbaz"
- - it can be called as a strbuf object method:
- b:concat "baz"
- expect (object.type (b)).should_be "StrBuf"
- expect (b:tostring()).should_be "foobarbaz"
- - it can be called as a strbuf metamethod:
- b = b .. "baz"
- expect (object.type (b)).should_be "StrBuf"
- expect (tostring (b)).should_be "foobarbaz"
-
-
-- describe __totable:
- - before:
- totable = (require "std.table").totable
-
- - it returns a table:
- expect (object.type (totable (b))).should_be "table"
- - it contains all non-hidden fields of object:
- expect (totable (b)).should_contain.all_of {"foo", "bar"}
- - it does not contain any hidden fields of object:
- expect (totable (b)).should_equal {"foo", "bar"}
diff --git a/specs/string_spec.yaml b/specs/string_spec.yaml
deleted file mode 100644
index d4de247..0000000
--- a/specs/string_spec.yaml
+++ /dev/null
@@ -1,532 +0,0 @@
-specify string:
-- before: |
- M = require "std.string"
-
- extends = string
- enhancements = { "assert", "format", "tostring" }
- extensions = { "caps", "chomp", "escape_pattern", "escape_shell",
- "finds", "ltrim", "numbertosi", "ordinal_suffix",
- "pad", "pickle", "prettytostring", "render",
- "require_version", "rtrim", "split", "tfind",
- "trim", "wrap",
- -- make these available after require "std"
- "__index", "_format", "_tostring",
- -- camelCase compatibility:
- "escapePattern", "escapeShell", "ordinalSuffix" }
-
- subject = "a string \n\n"
-
-
-- context when required:
- - before:
- enhanced = {}
- for _, api in ipairs (enhancements) do enhanced[api] = true end
-
- - context by name:
- - before: |
- for _, api in ipairs (enhancements) do
- extends[api] = M["_" .. api]
- end
- for _, api in ipairs (extensions) do extends[api] = nil end
- - after:
- for _, api in ipairs (enhancements) do
- extends[api], extends["_" .. api] = M[api], M["_" .. api]
- end
- for _, api in ipairs (extensions) do extends[api] = M[api] end
- - it does not perturb the global table:
- for _, api in ipairs (extensions) do
- expect (extends[api]).should_be (nil)
- end
- for _, api in ipairs (enhancements) do
- expect (extends[api]).should_be (M["_" .. api])
- end
- - it contains all global access points:
- for api in pairs (extends) do
- if enhanced[api] then
- expect (M[api]).should_not_be (extends[api])
- else
- expect (M[api]).should_be (extends[api])
- end
- end
-
- - context via the std module:
- - before:
- require "std"
- - it adds extension apis to the global table:
- for api in pairs (M) do
- expect (extends[api]).should_be (M[api])
- end
- - it does not add any other global access points:
- for api in pairs (extends) do
- if not enhanced[api] then
- expect (M[api]).should_be (extends[api])
- end
- end
-
-
-- "describe ..":
- - it concatenates string arguments:
- target = "a string \n\n another string"
- expect (subject .. " another string").should_be (target)
- - "it stringifies non-string arguments":
- argument = { "a table" }
- expect (subject .. argument).should_match (string.format ("%s{1=a table}", subject))
- - it stringifies nil arguments:
- argument = nil
- expect (subject .. argument).should_be (string.format ("%s%s", subject, tostring (argument)))
- - the original subject is not perturbed:
- original = subject
- newstring = subject .. " concatenate something"
- expect (subject).should_be (original)
-
-
-- describe assert:
-
-
-- describe caps:
- - before:
- f = M.caps
- - it capitalises words of a string:
- target = "A String \n\n"
- expect (f (subject)).should_be (target)
- - it changes only the first letter of each word:
- expect (f "a stRiNg").should_be "A StRiNg"
- - it is available as a string metamethod:
- expect (("a stRiNg"):caps ()).should_be "A StRiNg"
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject)
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f ()).should_error ("string expected")
- expect (f {"a table"}).should_error ("string expected")
-
-
-- describe chomp:
- - before:
- f = M.chomp
- target = "a string \n"
- - it removes a single trailing newline from a string:
- expect (f (subject)).should_be (target)
- - it does not change a string with no trailing newline:
- subject = "a string "
- expect (f (subject)).should_be (subject)
- - it is available as a string metamethod:
- expect (subject:chomp ()).should_be (target)
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject)
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f ()).should_error ("string expected")
- expect (f {"a table"}).should_error ("string expected")
-
-
-- describe escape_pattern:
- - before: |
- f = M.escape_pattern
-
- magic = {}
- meta = "^$()%.[]*+-?"
- for i = 1, string.len (meta) do
- magic[meta:sub (i, i)] = true
- end
-
- - 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)).should_be (target)
- - it is available as a string metamethod:
- expect (subject:escape_pattern ()).should_be (target)
- - legacy escapePattern call is the same function:
- expect (M.escapePattern).should_be (f)
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject)
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f ()).should_error ("string expected")
- expect (f {"a table"}).should_error ("string expected")
-
-
-- describe escape_shell:
- - before:
- f = M.escape_shell
- - 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)).should_be (target)
- - it is available as a string metamethod:
- expect (subject:escape_shell ()).should_be (target)
- - legacy escapeShell call is the same function:
- expect (M.escapeShell).should_be (f)
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject)
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f ()).should_error ("string expected")
- expect (f {"a table"}).should_error ("string expected")
-
-
-- describe finds:
- - before:
- subject = "abcd"
- f = M.finds
- - 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, "(.)(.)")}).should_equal ({ target })
- - it is available as a string metamethod:
- expect ({subject:finds ("(.)(.)")}).should_equal ({ target })
- - it creates an empty list where no captures are matched:
- target = {}
- expect ({f (subject, "(x)")}).should_equal ({ target })
- - it creates an empty list for a pattern without captures:
- target = { { 1, 1; capt = {} } }
- expect ({f (subject, "a")}).should_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)}).should_equal ({ target })
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject, "...")
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f ()).should_error ("string expected")
- expect (f {"a table"}).should_error ("string expected")
-
-
-# FIXME: This looks like a misfeature to me, let's remove it!
-- describe format:
- - before: |
- subject = "string: %s, number: %d"
- f = M.format
- - it returns a single argument without attempting formatting:
- expect (f (subject)).should_be (subject)
- - it is available as a string metamethod:
- expect (subject:format ()).should_be (subject)
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject)
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f (nil, "arg")).should_error ("string expected")
- expect (f ({"a table"}, "arg")).should_error ("string expected")
-
-
-- describe ltrim:
- - before:
- subject = " \t\r\n a short string \t\r\n "
- f = M.ltrim
- - it removes whitespace from the start of a string:
- target = "a short string \t\r\n "
- expect (f (subject)).should_equal (target)
- - it supports custom removal patterns:
- target = "\r\n a short string \t\r\n "
- expect (f (subject, "[ \t\n]+")).should_equal (target)
- - it is available as a string metamethod:
- target = "\r\n a short string \t\r\n "
- expect (subject:ltrim ("[ \t\n]+")).should_equal (target)
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject, "%W")
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f ()).should_error ("string expected")
- expect (f {"a table"}).should_error ("string expected")
-
-
-- describe numbertosi:
- - before:
- f = M.numbertosi
- - 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).should_equal (target)
- - it coerces string arguments to a number:
- expect (f "1000").should_be "1k"
- - "it diagnoses non-numeric arguments":
- expect (f ()).should_error ("attempt to perform arithmetic")
- expect (f {"a table"}).should_error ("number expected")
-
-
-- describe ordinal_suffix:
- - before:
- f = M.ordinal_suffix
- - 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).should_equal (target)
- - legacy ordinalSuffix call is the same function:
- expect (M.ordinalSuffix).should_be (f)
- - it coerces string arguments to a number:
- expect (f "-91").should_be "st"
- - "it diagnoses non-numeric arguments":
- expect (f ()).should_error ("number expected")
- expect (f {"a table"}).should_error ("number expected")
-
-
-- describe pad:
- - before:
- width = 20
- f = M.pad
-
- - 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)).should_be (target)
- - it left pads a string to the given negative width with spaces:
- width = -width
- target = " short string"
- expect (f (subject, width)).should_be (target)
- - it is available as a string metamethod:
- target = "short string "
- expect (subject:pad (width)).should_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)).should_be (target)
- - it left pads a string to given width with spaces:
- width = -width
- target = "an twenty characters"
- expect (f (subject, width)).should_be (target)
- - it is available as a string metamethod:
- target = "a string that's long"
- expect (subject:pad (width)).should_be (target)
-
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject, width)
- expect (subject).should_be (original)
- - "it coerces non-string arguments to a string":
- expect (f ({ "a table" }, width)).should_contain "a table"
- - "it diagnoses non-numeric width arguments":
- expect (f (subject, nil)).should_error ("number expected")
- expect (f (subject, {"a table"})).should_error ("number expected")
-
-
-- describe pickle:
-
-
-- describe prettytostring:
- - before:
- f = M.prettytostring
- - it renders nil exactly like system tostring:
- expect (f (nil)).should_be (tostring (nil))
- - it renders booleans exactly like system tostring:
- expect (f (true)).should_be (tostring (true))
- expect (f (false)).should_be (tostring (false))
- - it renders numbers exactly like system tostring:
- n = 8723643
- expect (f (n)).should_be (tostring (n))
- - it renders functions exactly like system tostring:
- expect (f (f)).should_be (tostring (f))
- - it renders strings with format "%q" styling:
- s = "a string"
- expect (f (s)).should_be (string.format ("%q", s))
- - it renders empty tables as a pair of braces:
- expect (f {}).should_be ("{\n}")
- - it renders an array prettily:
- a = {"one", "two", "three"}
- expect (f (a, "")).
- should_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, "")).
- should_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, "")).
- should_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, "")).
- should_be '{\n["?"] = 1,\n["[]"] = 1,\n_ = 0,\n["a-key"] = 1,\nword = 0,\n}'
-
-
-- describe render:
-
-
-- describe require_version:
-
-
-- describe rtrim:
- - before:
- subject = " \t\r\n a short string \t\r\n "
- f = M.rtrim
- - it removes whitespace from the end of a string:
- target = " \t\r\n a short string"
- expect (f (subject)).should_equal (target)
- - it supports custom removal patterns:
- target = " \t\r\n a short string \t\r"
- expect (f (subject, "[ \t\n]+")).should_equal (target)
- - it is available as a string metamethod:
- target = " \t\r\n a short string \t\r"
- expect (subject:rtrim ("[ \t\n]+")).should_equal (target)
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject, "%W")
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f ()).should_error ("string expected")
- expect (f {"a table"}).should_error ("string expected")
-
-
-- describe split:
- - before:
- target = { "first", "the second one", "final entry" }
- subject = table.concat (target, ", ")
- f = M.split
- - it makes a table of substrings delimited by a separator:
- expect (f (subject, ", ")).should_equal (target)
- - it is available as a string metamethod:
- expect (subject:split ", ").should_equal (target)
- expect (("/foo/bar/baz.quux"):split "/").should_equal {"", "foo", "bar", "baz.quux"}
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject, "e")
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f ("a string")).should_error ("string expected")
- expect (f (nil, ",")).should_error ("string expected")
- expect (f ({"a table"}, ",")).should_error ("string expected")
-
-
-- describe tfind:
- - before:
- subject = "abc"
- f = M.tfind
- - it creates a list of pattern captures:
- target = { 1, 3, { "a", "b", "c" } }
- expect ({f (subject, "(.)(.)(.)")}).should_equal (target)
- - it creates an empty list where no captures are matched:
- target = { nil, nil, {} }
- expect ({f (subject, "(x)(y)(z)")}).should_equal (target)
- - it creates an empty list for a pattern without captures:
- target = { 1, 1, {} }
- expect ({f (subject, "a")}).should_equal (target)
- - it starts the search at a specified index into the subject:
- target = { 8, 10, { "a", "b", "c" } }
- expect ({f ("garbage" .. subject, "(.)(.)(.)", 8)}).should_equal (target)
- - it is available as a string metamethod:
- target = { 8, 10, { "a", "b", "c" } }
- expect ({("garbage" .. subject):tfind ("(.)(.)(.)", 8)}).should_equal (target)
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject, "...")
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f ()).should_error ("string expected")
- expect (f {"a table"}).should_error ("string expected")
-
-
-- describe tostring:
-
-
-- describe trim:
- - before:
- subject = " \t\r\n a short string \t\r\n "
- f = M.trim
- - it removes whitespace from each end of a string:
- target = "a short string"
- expect (f (subject)).should_equal (target)
- - it supports custom removal patterns:
- target = "\r\n a short string \t\r"
- expect (f (subject, "[ \t\n]+")).should_equal (target)
- - it is available as a string metamethod:
- target = "\r\n a short string \t\r"
- expect (subject:trim ("[ \t\n]+")).should_equal (target)
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject, "%W")
- expect (subject).should_be (original)
- - "it diagnoses non-string arguments":
- expect (f ()).should_error ("string expected")
- expect (f {"a table"}).should_error ("string expected")
-
-
-- 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" ..
- "-2013 (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
- - 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" ..
- "-2013 (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)).should_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" ..
- "-2013 (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)).should_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-2013 (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)).should_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-2013 (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)).should_be (target)
- - it is available as a string metamethod:
- expect (subject:wrap (64, 2, 4)).should_be (target)
- - the original subject is not perturbed:
- original = subject
- newstring = f (subject, 55, 5)
- expect (subject).should_be (original)
- - it diagnoses indent greater than line width:
- expect (f (subject, 10, 12)).should_error ("less than the line width")
- expect (f (subject, 99, 99)).should_error ("less than the line width")
- - it diagnoses non-string arguments:
- expect (f ()).should_error ("string expected")
- expect (f {"a table"}).should_error ("string expected")
diff --git a/specs/table_spec.yaml b/specs/table_spec.yaml
deleted file mode 100644
index 2620d5c..0000000
--- a/specs/table_spec.yaml
+++ /dev/null
@@ -1,281 +0,0 @@
-specify table:
-- before: |
- M = require "std.table"
-
- extends = table
- enhancements = { "sort" }
- extensions = { "clone", "clone_rename", "empty", "invert", "keys",
- "merge", "new", "pack", "ripairs", "size", "totable",
- "values" }
-
-- context when required:
- - before:
- enhanced = {}
- for _, api in ipairs (enhancements) do enhanced[api] = true end
-
- - context by name:
- - before: |
- function restore (g, m)
- for _, api in ipairs (enhancements) do
- g[api], g["_" .. api] = m[api], m["_" .. api]
- end
- for _, api in ipairs (extensions) do g[api] = m[api] end
- end
-
- for _, api in ipairs (enhancements) do
- extends[api] = M["_" .. api]
- end
- for _, api in ipairs (extensions) do extends[api] = nil end
- - after:
- restore (extends, M)
- - it does not perturb the global table:
- for _, api in ipairs (extensions) do
- expect (extends[api]).should_be (nil)
- end
- for _, api in ipairs (enhancements) do
- expect (extends[api]).should_be (M["_" .. api])
- end
- - it contains all global access points:
- for api in pairs (extends) do
- if enhanced[api] then
- expect (M[api]).should_not_be (extends[api])
- else
- expect (M[api]).should_be (extends[api])
- end
- end
-
- - context via the std module:
- - before:
- require "std"
- - it adds extension apis to the global table:
- for api in pairs (M) do
- expect (extends[api]).should_be (M[api])
- end
- - it does not add any other global access points:
- for api in pairs (extends) do
- if not enhanced[api] then
- expect (M[api]).should_be (extends[api])
- end
- end
-
-
-- describe clone:
- - before:
- subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} }
- f = M.clone
- - it does not just return the subject:
- expect (f (subject)).should_not_be (subject)
- - it does copy the subject:
- expect (f (subject)).should_equal (subject)
- - it only makes a shallow copy:
- expect (f (subject).k1).should_be (subject.k1)
- - the original subject is not perturbed:
- target = { k1 = subject.k1, k2 = subject.k2, k3 = subject.k3 }
- copy = f (subject)
- expect (subject).should_equal (target)
- expect (subject).should_be (subject)
- - "it diagnoses non-table arguments":
- expect (f ()).should_error ("table expected")
- expect (f "foo").should_error ("table expected")
-
-
-- describe clone_rename:
- - before:
- subject = { k1 = {"v1"}, k2 = {"v2"}, k3 = {"v3"} }
- f = M.clone_rename
- - it does not just return the subject:
- expect (f ({}, subject)).should_not_be (subject)
- - it copies the subject:
- expect (f ({}, subject)).should_equal (subject)
- - it only makes a shallow copy:
- expect (f ({}, subject).k2).should_be (subject.k2)
-
- - context when renaming some keys:
- - before:
- target = { newkey = subject.k1, k2 = subject.k2, k3 = subject.k3 }
- - it renames during cloning:
- expect (f ({k1 = "newkey"}, subject)).should_equal (target)
- - it does not perturb the value in the renamed key field:
- expect (f ({k1 = "newkey"}, subject).newkey).should_be (subject.k1)
-
- - "it diagnoses non-table arguments":
- expect (f {}).should_error ("table expected")
- expect (f ({}, "foo")).should_error ("table expected")
-
-
-- describe empty:
- - before:
- f = M.empty
- - it returns true for an empty table:
- expect (f {}).should_be (true)
- expect (f {nil}).should_be (true)
- - "it returns false for a non-empty table":
- expect (f {"stuff"}).should_be (false)
- expect (f {{}}).should_be (false)
- expect (f {false}).should_be (false)
- - "it diagnoses non-table arguments":
- expect (f ()).should_error ("table expected")
- expect (f "foo").should_error ("table expected")
-
-
-- describe invert:
- - before:
- subject = { k1 = 1, k2 = 2, k3 = 3 }
- f = M.invert
- - it returns a new table:
- expect (f (subject)).should_not_be (subject)
- - it inverts keys and values in the returned table:
- expect (f (subject)).should_equal { "k1", "k2", "k3" }
- - it is reversible:
- expect (f (f (subject))).should_equal (subject)
- - "it seems to copy a list of 1..n numbers":
- subject = { 1, 2, 3 }
- expect (f (subject)).should_equal (subject)
- expect (f (subject)).should_not_be (subject)
- - "it diagnoses non-table arguments":
- expect (f ()).should_error ("table expected")
- expect (f "foo").should_error ("table expected")
-
-
-- describe keys:
- - before:
- subject = { k1 = 1, k2 = 2, k3 = 3 }
- f = M.keys
- - it returns an empty list when subject is empty:
- expect (f {}).should_equal {}
- - it makes a list of table keys:
- cmp = function (a, b) return a < b end
- expect (M.sort (f (subject), cmp)).should_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)).should_not_equal (subject)
- - "it diagnoses non-table arguments":
- expect (f ()).should_error ("table expected")
- expect (f "foo").should_error ("table expected")
-
-
-- describe merge:
- - before: |
- -- Additional merge keys which are moderately unusual
- t1 = { k1 = {"v1"}, k2 = "if", k3 = {"?"} }
- t2 = { ["if"] = true, [{"?"}] = false, _ = "underscore", k3 = t1.k1 }
- f = M.merge
-
- target = {}
- for k, v in pairs (t1) do target[k] = v end
- for k, v in pairs (t2) do target[k] = v end
- - it does not create a whole new table:
- expect (f (t1, t2)).should_be (t1)
- - it does not change t1 when t2 is empty:
- expect (f (t1, {})).should_be (t1)
- - it copies t2 when t1 is empty:
- expect (f ({}, t1)).should_not_be (t1)
- expect (f ({}, t1)).should_equal (t1)
- - it merges keys from t2 into t1:
- expect (f (t1, t2)).should_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).should_be (t2.k3)
- expect (m.k3).should_not_be (original.k3)
- - "it diagnoses non-table arguments":
- expect (f ()).should_error ("table expected")
- expect (f ("foo", "bar")).should_error ("table expected")
-
-
-- describe new:
- - before:
- f = M.new
-
- - context when not setting a default:
- - before: default = nil
- - it returns a new table when nil is passed:
- expect (f (default, nil)).should_equal {}
- - it returns any table passed in:
- t = { "unique table" }
- expect (f (default, t)).should_be (t)
-
- - context when setting a default:
- - before:
- default = "default"
- - it returns a new table when nil is passed:
- expect (f (default, nil)).should_equal {}
- - it returns any table passed in:
- t = { "unique table" }
- expect (f (default, t)).should_be (t)
-
- - it returns the stored value for existing keys:
- t = f ("default")
- v = { "unique value" }
- t[1] = v
- expect (t[1]).should_be (v)
- - it returns the constructor default for unset keys:
- t = f ("default")
- expect (t[1]).should_be "default"
- - it returns the actual default object:
- default = { "unique object" }
- t = f (default)
- expect (t[1]).should_be (default)
- - "it diagnoses non-tables/non-nil in the second argument":
- expect (f (nil, "foo")).should_error ("table expected")
-
-
-- describe pack:
-
-
-- describe ripairs:
-
-
-- describe size:
- - before: |
- -- - 1 - --------- 2 ---------- -- 3 --
- subject = { "one", { { "two" }, "three" }, four = 5 }
- f = M.size
- - it counts the number of keys in a table:
- expect (f (subject)).should_be (3)
- - it counts no keys in an empty table:
- expect (f {}).should_be (0)
- - "it diagnoses non-table arguments":
- expect (f ()).should_error ("table expected")
- expect (f "foo").should_error ("table expected")
-
-
-- 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
- - it sorts elements in place:
- f (subject, cmp)
- expect (subject).should_equal (target)
- - it returns the sorted table:
- expect (f (subject, cmp)).should_equal (target)
- - "it diagnoses non-table arguments":
- expect (f ()).should_error ("table expected")
- expect (f "foo").should_error ("table expected")
-
-
-- describe totable:
-
-
-- describe values:
- - before:
- subject = { k1 = {1}, k2 = {2}, k3 = {3} }
- f = M.values
- - it returns an empty list when subject is empty:
- expect (f {}).should_equal {}
- - it makes a list of table values:
- cmp = function (a, b) return a[1] < b[1] end
- expect (M.sort (f (subject), cmp)).should_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)).should_equal (subject)
- - "it diagnoses non-table arguments":
- expect (f ()).should_error ("table expected")
- expect (f "foo").should_error ("table expected")
diff --git a/stdlib-git-1.rockspec b/stdlib-git-1.rockspec
new file mode 100644
index 0000000..504aee8
--- /dev/null
+++ b/stdlib-git-1.rockspec
@@ -0,0 +1,51 @@
+local _MODREV, _SPECREV = 'git', '-1'
+
+package = 'stdlib'
+version = _MODREV .. _SPECREV
+
+description = {
+ summary = 'General Lua Libraries',
+ detailed = [[
+ stdlib is a library of modules for common programming tasks,
+ including list and table operations, and pretty-printing.
+ ]],
+ homepage = 'http://lua-stdlib.github.io/lua-stdlib',
+ license = 'MIT/X11',
+}
+
+source = (function(gitp)
+ if gitp then
+ return {
+ url = 'git://github.com/lua-stdlib/lua-stdlib.git',
+ }
+ else
+ return {
+ url = 'http://github.com/lua-stdlib/lua-stdlib/archive/v' .. _MODREV .. '.zip',
+ dir = 'lua-stdlib-' .. _MODREV,
+ }
+ end
+end)(_MODREV == 'git')
+
+dependencies = {
+ 'lua >= 5.1, < 5.4',
+ 'std._debug',
+ 'std.normalize >= 2.0',
+}
+
+if _MODREV == 'git' then
+ dependencies[#dependencies + 1] = 'ldoc'
+end
+
+build = {
+ type = 'builtin',
+ modules = {
+ std = 'lib/std/init.lua',
+ ['std._base'] = 'lib/std/_base.lua',
+ ['std.debug'] = 'lib/std/debug.lua',
+ ['std.io'] = 'lib/std/io.lua',
+ ['std.math'] = 'lib/std/math.lua',
+ ['std.package'] = 'lib/std/package.lua',
+ ['std.string'] = 'lib/std/string.lua',
+ ['std.table'] = 'lib/std/table.lua',
+ },
+}