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 `' header file. The option `-nodtk' can be used as -a workaround. If GNU CC is not installed, it is therefore recommended -to try - - ./configure CC="cc" - -and if that doesn't work, try - - ./configure CC="cc -nodtk" - - On Solaris, don't put `/usr/ucb' early in your `PATH'. This -directory contains several dysfunctional programs; working variants of -these programs are available in `/usr/bin'. So, if you need `/usr/ucb' -in your `PATH', put it _after_ `/usr/bin'. - - On Haiku, software installed for all users goes in `/boot/common', -not `/usr/local'. It is recommended to use the following options: - - ./configure --prefix=/boot/common - -Specifying the System Type -========================== - - There may be some features `configure' cannot figure out -automatically, but needs to determine by the type of machine the package -will run on. Usually, assuming the package is built to be run on the -_same_ architectures, `configure' can figure that out, but if it prints -a message saying it cannot guess the machine type, give it the -`--build=TYPE' option. TYPE can either be a short name for the system -type, such as `sun4', or a canonical name which has the form: - - CPU-COMPANY-SYSTEM - -where SYSTEM can have one of these forms: - - OS - KERNEL-OS - - See the file `config.sub' for the possible values of each field. If -`config.sub' isn't included in this package, then this package doesn't -need to know the machine type. - - If you are _building_ compiler tools for cross-compiling, you should -use the option `--target=TYPE' to select the type of system they will -produce code for. - - If you want to _use_ a cross compiler, that generates code for a -platform different from the build platform, you should specify the -"host" platform (i.e., that on which the generated programs will -eventually be run) with `--host=TYPE'. - -Sharing Defaults -================ - - If you want to set default values for `configure' scripts to share, -you can create a site shell script called `config.site' that gives -default values for variables like `CC', `cache_file', and `prefix'. -`configure' looks for `PREFIX/share/config.site' if it exists, then -`PREFIX/etc/config.site' if it exists. Or, you can set the -`CONFIG_SITE' environment variable to the location of the site script. -A warning: not all `configure' scripts look for a site script. - -Defining Variables -================== - - Variables not defined in a site shell script can be set in the -environment passed to `configure'. However, some packages may run -configure again during the build, and the customized values of these -variables may be lost. In order to avoid this problem, you should set -them in the `configure' command line, using `VAR=value'. For example: - - ./configure CC=/usr/local2/bin/gcc - -causes the specified `gcc' to be used as the C compiler (unless it is -overridden in the site shell script). - -Unfortunately, this technique does not work for `CONFIG_SHELL' due to -an Autoconf bug. Until the bug is fixed you can use this workaround: - - CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash - -`configure' Invocation -====================== - - `configure' recognizes the following options to control how it -operates. - -`--help' -`-h' - Print a summary of all of the options to `configure', and exit. - -`--help=short' -`--help=recursive' - Print a summary of the options unique to this package's - `configure', and exit. The `short' variant lists options used - only in the top level, while the `recursive' variant lists options - also present in any nested packages. - -`--version' -`-V' - Print the version of Autoconf used to generate the `configure' - script, and exit. - -`--cache-file=FILE' - Enable the cache: use and save the results of the tests in FILE, - traditionally `config.cache'. FILE defaults to `/dev/null' to - disable caching. - -`--config-cache' -`-C' - Alias for `--cache-file=config.cache'. - -`--quiet' -`--silent' -`-q' - Do not print messages saying which checks are being made. To - suppress all normal output, redirect it to `/dev/null' (any error - messages will still be shown). - -`--srcdir=DIR' - Look for the package's source code in directory DIR. Usually - `configure' can determine that directory automatically. - -`--prefix=DIR' - Use DIR as the installation prefix. *note Installation Names:: - for more details, including other options available for fine-tuning - the installation locations. - -`--no-create' -`-n' - Run the configure checks, but stop before creating any output - files. - -`configure' also accepts some other, not widely useful, options. Run -`configure --help' for more details. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..c2b4620 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +Copyright (C) 2002-2018 stdlib authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGE- +MENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7b8dd8c --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2018 stdlib authors + +LDOC = ldoc +LUA = lua +MKDIR = mkdir -p +SED = sed +SPECL = specl + +VERSION = git + +luadir = lib/std +SOURCES = \ + $(luadir)/_base.lua \ + $(luadir)/debug.lua \ + $(luadir)/init.lua \ + $(luadir)/io.lua \ + $(luadir)/math.lua \ + $(luadir)/package.lua \ + $(luadir)/string.lua \ + $(luadir)/table.lua \ + $(luadir)/version.lua \ + $(NOTHING_ELSE) + + +all: doc + +$(luadir)/version.lua: .FORCE + @echo 'return "General Lua libraries / $(VERSION)"' > '$@T'; \ + if cmp -s '$@' '$@T'; then \ + rm -f '$@T'; \ + else \ + echo 'echo return "General Lua libraries / $(VERSION)" > $@'; \ + mv '$@T' '$@'; \ + fi + +doc: build-aux/config.ld $(SOURCES) + $(LDOC) -c build-aux/config.ld . + +build-aux/config.ld: build-aux/config.ld.in + $(SED) -e "s,@PACKAGE_VERSION@,$(VERSION)," '$<' > '$@' + + +CHECK_ENV = LUA=$(LUA) + +check: + LUA=$(LUA) $(SPECL) $(SPECL_OPTS) spec/*_spec.yaml + +.FORCE: diff --git a/NEWS b/NEWS deleted file mode 100644 index 4124913..0000000 --- a/NEWS +++ /dev/null @@ -1,461 +0,0 @@ -Stdlib NEWS - User visible changes - -* Noteworthy changes in release ?.? (????-??-??) [?] - -** New features: - - - Modules have been refactored so that they can be safely - required individually, and without loading themselves or any - dependencies on other std modules into the global namespace. - - - Objects derived from the `std.object` prototype have a new - :type () method that returns the contents of the new - internal `_type` field. This can be overridden during cloning with, - e.g.: - - local Object = require "std.object" - Prototype = Object { _type = "Prototype", } - - - Objects derived from the `std.object` prototype return a new table - with a shallow copy of all non-internal fields (keys that do not - begin with "_") when passed to `table.totable` - unless overridden - in the derived object's __totable field. - - - list, set and strbuf are now derived from std.object, which means that - they respond to `object.type` with appropriate type names ("set", - "strbuf", etc.) and can be used as prototypes for further derived - objects or clones; support object:type (); respond to totable etc. - - - `string.prettytostring` always displays table elements in the same - order, as provided by `table.sort`. - - - Can now be installed directly from a release tarball by `luarocks`. - No need to run `./configure` or `make`, unless you want to install to - a custom location, or do not use LuaRocks. - -** Bug fixes: - - - string.escape_pattern is now Lua 5.2 compatible. - - - all objects now reuse prototype metatables, as required for __le and - __lt metamethods to work as documented. - -** Incompatible changes: - - - Following on from the Grand Renaming™ change in the last release, - `std.debug_ext`, `std.io_ext`, `std.math_ext`, `std.package_ext`, - `std.string_ext` and `std.table_ext` no longer have the spurious - `_ext` suffix. Instead, you must now use, e.g.: - - local string = require "std.string" - - These names are now stable, and will be available from here for - future releases. - - - The std.list module, as a consequence of returning an List object - prototype rather than a table of functions including a constructor, - now always has the list operand as the first argument, whether that - function is called with `.` syntax or `:` syntax. Functions which - previously had the list operand in a different position when called - with `.` syntax were: list.filter, list.foldl, list.foldr, - list.index_key, list.index_value, list.map, list.map_with, - list.project, list.shape and list.zip_with. Calls made as object - methods using `:` calling syntax are unchanged. - - -* Noteworthy changes in release 35 (2013-05-06) [stable] - -** New features: - - - Move to the Slingshot release system. - - Continuous integration from Travis automatically builds stdilb - with Lua 5.1, Lua 5.2 and luajit-2.0 with every commit, which - should help prevent future release breaking compatibility with - one or another of those interpreters. - -** Bug fixes: - - - `std.package_ext` no longer overwrites the core `package` table, - leaving the core holding on to memory that Lua code could no - longer access. - -** Incompatible changes: - - - The Grand Renaming™ - everything now installs to $luaprefix/std/, - except `std.lua` itself. Importing individual modules now involves: - - local list = require "std.list" - - If you want to have all the symbols previously available from the - global and core module namespaces, you will need to put them there - yourself, or import everything with: - - require "std" - - which still behaves per previous releases. - - Not all of the modules work correctly when imported individually - right now, until we figure out how to break some circular dependencies. - - -* Noteworthy changes in release 34.1 (2013-04-01) [stable] - -** This is a maintenance release to quickly fix a breakage in getopt - from release v34. Getopt no longer parses non-options, but stops - on the first non-option... if a use case for the other method - comes up, we can always add it back in. - - -* Noteworthy changes in release 34 (2013-03-25) [stable] - - - stdlib is moving towards supporting separate requirement of individual - modules, without scribbling on the global environment; the work is not - yet complete, but we're collecting tests along the way to ensure that - once it is all working, it will carry on working; - - there are some requirement loops between modules, so not everything can - be required independently just now; - - `require "std"` will continue to inject std symbols into the system - tables for backwards compatibility; - - stdlib no longer ships a copy of Specl, which you will need to install - separately if you want to run the bundled tests; - - getopt supports parsing of undefined options; useful for programs that - wrap other programs; - - getopt.Option constructor is no longer used, pass a plain Lua table of - options, and getopt will do the rest; - - -* Noteworthy changes in release 33 (2013-07-27) [stable] - -** This release improves stability where Specl has helped locate some - corner cases that are now fixed. - - `string_ext.wrap` and `string_ext.tfind` now diagnose invalid arguments. - -** Specl code coverage is improving. - -** OrdinalSuffix improvements. - - Use '%' instead of math.mod, as the latter does not exist in Lua 5.2. - - Accept negative arguments. - - -* Noteworthy changes in release 32 (2013-02-22) [stable] - -** This release fixes a critical bug preventing getopt from returning - anything in getopt.opt. Gary V. Vaughan is now a co-maintainer, currently - reworking the sources to use (Lua 5.1 compatible) Lua 5.2 style module - packaging, which requires you to assign the return values from your imports: - - getopt = require "getopt" - -** Extension modules, table_ext, package_ext etc. return the unextended module - table before injecting additional package methods, so you can ignore those - return values or save them for programatically backing out the changes: - - table_unextended = require "table_ext" - -** Additionally, Specl (see http://github.com/gvvaughan/specl/) specifications - are being written for stdlib modules to help us stop accidentally breaking - things between releases. - - -* Noteworthy changes in release 31 (2013-02-20) [stable] - -** This release improves the list module: lists now have methods, list.slice - is renamed to list.sub (the old name is provided as an alias for backwards - compatibility), and all functions that construct a new list return a proper - list, not a table. As a result, it is now often possible to write code that - works on both lists and strings. - - -* Noteworthy changes in release 30 (2013-02-17) [stable] - -** This release changes some modules to be written in a Lua 5.2 style (but - not the way they work with 5.1). Some fixes and improvements were made to - the build system. Bugs in the die function, the parser module, and a nasty - bug in the set module introduced in the last release (29) were fixed. - - -* Noteworthy changes in release 29 (2013-02-06) [stable] - -** This release overhauls the build system to have LuaRocks install releases - directly from git rather than from tarballs, and fixes a bug in set (issue - #8). - - -* Noteworthy changes in release 28 (2012-10-28) [stable] - -** This release improves the documentation and build system, and improves - require_version to work by default with more libraries. - - -* Noteworthy changes in release 27 (2012-10-03) [stable] - -** This release changes getopt to return all arguments in a list, rather than - optionally processing them with a function, fixes an incorrect definition - of set.elems introduced in release 26, turns on debugging by default, - removes the not-very-useful string.gsubs, adds constructor functions for - objects, renames table.rearrange to the more descriptive table.clone_rename - and table.indices to table.keys, and makes table.merge not clone but modify - its left-hand argument. A function require_version has been added to allow - version constraints on a module being required. Gary Vaughan has - contributed a memoize function, and minor documentation and build system - improvements have been made. Usage information is now output to stdout, not - stderr. The build system has been fixed to accept Lua 5.2. The luarock now - installs documentation, and the build command used is now more robust - against previous builds in the same tree. - - -* Noteworthy changes in release 26 (2012-02-18) [stable] - -** This release improves getoptâs output messages and conformance to - standard practice for default options. io.processFiles now unsets prog.file - when it finishes, so that a program can tell when itâs no longer - processing a file. Three new tree iterators, inodes, leaves and ileaves, - have been added; the set iterator set.elements (renamed to set.elems for - consistency with list.elems) is now leaves rather than pairs. tree indexing - has been made to work in more circumstances (thanks, Gary Vaughan). - io.writeline is renamed io.writelines for consistency with io.readlines and - its function. A slurping function, io.slurp, has been added. Strings now - have a __concat metamethod. - - -* Noteworthy changes in release 25 (2011-09-19) [stable] - -** This release adds a version string to the std module and fixes a buglet in - the build system. - - -* Noteworthy changes in release 24 (2011-09-19) [stable] - -** This release fixes a rename missing from release 23, and makes a couple of - fixes to the new build system, also from release 23. - - -* Noteworthy changes in release 23 (2011-09-17) [stable] - -** This release removes the posix_ext module, which is now part of luaposix, - renames string.findl to string.tfind to be the same as lrexlib, and - autotoolizes the build system, as well as providing a rockspec file. - - -* Noteworthy changes in release 22 (2011-09-02) [stable] - -** This release adds two new modules: strbuf, a trivial string buffers - implementation, which is used to speed up the stdlib tostring method for - tables, and bin, which contains a couple of routines for converting binary - data into numbers and strings. Some small documentation and build system - fixes have been made. - - -* Noteworthy changes in release 21 (2011-06-06) [stable] - -** This release converts the documentation of stdlib to LuaDoc, adds an - experimental Lua 5.2 module "fstable", for storing tables directly on - disk as files and directories, and fixes a few minor bugs (with help from - David Favro). - -** This release has been tested lightly on Lua 5.2 alpha, but is not - guaranteed to work fully. - - -* Noteworthy changes in release 20 (2011-04-14) [stable] - -** This release fixes a conflict between the global _DEBUG setting and the use - of strict.lua, changes the argument order of some list functions to favour - OO-style use, adds posix.euidaccess, and adds OO-style use to set. mk1file - can now produce a single-file version of a user-supplied list of modules, - not just the standard set. - - -* Noteworthy changes in release 19 (2011-02-26) [stable] - -** This release puts the package.config reflection in a new package_ext - module, where it belongs. Thanks to David Manura for this point, and for a - small improvement to the code. - - -* Noteworthy changes in release 18 (2011-02-26) [stable] - -** This release provides named access to the contents of package.config, which - is undocumented in Lua 5.1. See luaconf.h and the Lua 5.2 manual for more - details. - - -* Noteworthy changes in release 17 (2011-02-07) [stable] - -** This release fixes two bugs in string.pad (thanks to Bob Chapman for the - fixes). - - -* Noteworthy changes in release 16 (2010-12-09) [stable] - -** Adds posix module, using luaposix, and makes various other small fixes and - improvements. - - -* Noteworthy changes in release 15 (2010-06-14) [stable] - -** This release fixes list.foldl, list.foldr, the fold iterator combinator and - io.writeLine. It also simplifies the op table, which now merely sugars the - built-in operators rather than extending them. It adds a new tree module, - which subsumes the old table.deepclone and table.lookup functions. - table.subscript has become op["[]"], and table.subscripts has been removed; - the old treeIter iterator has been simplified and generalised, and renamed - to nodes. The mk1file script and std.lua library loader have had the module - list factored out into modules.lua. strict.lua from the Lua distribution is - now included in stdlib, which has been fixed to work with it. Some minor - documentation and other code improvements and fixes have been made. - - -* Noteworthy changes in release 14 (2010-06-07) [stable] - -** This release makes stdlib compatible with strict.lua, which required a - small change to the debug_ext module. Some other minor changes have also - been made to that module. The table.subscripts function has been removed - from the table_ext.lua. - - -* Noteworthy changes in release 13 (2010-06-02) [stable] - -** This release removes the lcs module from the standard set loaded by - "std", removes an unnecessary definition of print, and tidies up the - implementation of the "op" table of functional versions of the infix - operators and logical operators. - - -* Noteworthy changes in release 12 (2009-09-07) [stable] - -** This release removes io.basename and io.dirname, which are now available in - lposix, and the little-used functions addSuffix and changeSuffix which - dependend on them. io.pathConcat is renamed to io.catdir and io.pathSplit - to io.splitdir, making them behave the same as the corresponding Perl - functions. The dependency on lrexlib has been removed along with the rex - wrapper module. Some of the more esoteric and special-purpose modules - (mbox, xml, parser) are no longer loaded by 'require "std"'. - - This leaves stdlib with no external dependencies, and a rather more - coherent set of basic modules. - - -* Noteworthy changes in release 11 (2009-03-15) [stable] - -** This release fixes a bug in string.format, removes the redundant - string.join (it's the same as table.concat), and adds to table.clone and - table.deepclone the ability to copy without metatables. Thanks to David - Kantowitz for pointing out the various deficiencies. - - -* Noteworthy changes in release 10 (2009-03-13) [stable] - -** This release fixes table.deepclone to copy metatables, as it should. - Thanks to David Kantowitz for the fix. - - -* Noteworthy changes in release 9 (2009-02-19) [stable] - -** This release updates the object module to be the same as that published - in "Lua Gems", and fixes a bug in the utility mk1file which makes a - one-file version of the library, to stop it permanently redefining require. - - -* Noteworthy changes in release 8 (2008-09-04) [stable] - -** This release features fixes and improvements to the set module; thanks to - Jiutian Yanling for a bug report and suggestion which led to this work. - - -* Noteworthy changes in release 7 (2008-09-04) [stable] - -** just a bug fix - - -* Noteworthy changes in release 6 (2008-07-28) [stable] - -** This release rewrites the iterators in a more Lua-ish 5.1 style. - - -* Noteworthy changes in release 5 (2008-03-04) [stable] - -** I'm happy to announce a new release of my standard Lua libraries. It's been - nearly a year since the last release, and I'm happy to say that since then - only one bug has been found (thanks Roberto!). Two functions have been - added in this release, to deal with file paths, and one removed (io.length, - which is handled by lfs.attributes) along with one constant (INTEGER_BITS, - handled by bitlib's bit.bits). - -** For those not familiar with stdlib, it's a pure-Lua library of mostly - fundamental data structures and algorithms, in particular support for - functional and object-oriented programming, string and regex operations and - extensible pretty printing of data structures. More specific modules - include a getopt implementation, a generalised least common subsequences - (i.e. diff algorithm) implementation, a recursive-descent parser generator, - and an mbox parser. - -** It's quite a mixed bag, but almost all written for real projects. It's - written in a doc-string-ish style with the supplied very simple ldoc tool. - -** I am happy with this code base, but there are various things it could use: - - 0. Tests. Tests. Tests. The code has no unit tests. It so needs them. - - 1. More code. Nothing too specialised (unless it's too small to be released - on its own, although very little seems "too small" in the Lua - community). Anything that either has widespread applicability (like - getopt) or is very general (data structures, algorithms, design - patterns) is good. - - 2. Refactoring. The code is not ideally factored. At the moment it is - divided into modules that extend existing libraries, and new modules - constructed along similar lines, but I think that some of the divisions - are confusing. For example, the functional programming support is spread - between the list and base modules, and would probably be better in its - own module, as those who aren't interested in the functional style won't - want the functional list support or the higher-order functions support, - and those who want one will probably want the other. - - 3. Documentation work. There's not a long wrong with the existing - documentation, but it would be nice, now that there is a stable LuaDoc, - to use that instead of the built-in ldoc, which I'm happy to discard now - that LuaDoc is stable. ldoc was always designed as a minimal LuaDoc - substitute in any case. - - 4. Maintenance and advocacy. For a while I have been reducing my work on - Lua, and am also now reducing my work in Lua. If anyone would like to - take on stdlib, please talk to me. It fills a much-needed function: I - suspect a lot of Lua programmers have invented the wheels with which it - is filled over and over again. In particular, many programmers could - benefit from the simplicity of its simple and well-designed functional, - string and regex capabilities, and others will love its comprehensive - getopt. - - -* Noteworthy changes in release 4 (2007-04-26) [beta] - -** This release removes the dependency on the currently unmaintained lposix - library, includes pre-built HTML documentation, and fixes some 5.0-style - uses of variadic arguments. - - Thanks to Matt for pointing out all these problems. stdlib is very much - user-driven at the moment, since it already does everything I need, and I - don't have much time to work on it, so do please contact me if you find - bugs or problems or simply don't understand it, as the one thing I *do* - want to do is make it useful and accessible! - - -* Noteworthy changes in release 3 (2007-02-25) [beta] - -** This release fixes the "set" and "lcs" (longest common subsequence, or - "grep") libraries, which were broken, and adds one or two other bug and - design fixes. Thanks are due to Enrico Tassi for pointing out some of the - problems. - - -* Noteworthy changes in release 2 (2007-01-05) [beta] - -** This release includes some bug fixes, and compatibility with lrexlib 2.0. - - -* Noteworthy changes in release 1 (2011-09-02) [beta] - -** It's just a snapshot of CVS, but it's pretty stable at the moment; stdlib, - until such time as greater interest or participation enables (or forces!) - formal releases will be in permanent beta, and tracking CVS is recommended. diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..fdbf720 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,1414 @@ +# Stdlib NEWS - User visible changes + +## Noteworthy changes in release ?.? (????-??-??) [?] + +### New features + + - Overhaul of the LDoc documentation, adding more introductory + material, clearer usage examples and better internal consistency. At + this point we're pushing the technical limits of what LDoc can do for + us organization-wise, but improvements and corrections to the content + are always welcome! + + - With this release, stdlib is much more focused, and non-core modules + `optparse`, `std.functional`, 'std.prototype', `std.strict` and + `typecheck` have been moved into their own packages and release + cycle. Also, the shared debug initialization, Lua host normalization + and API deprecation code have been split out into new 'std._debug', + 'std.normalize' and 'apimaturity' respectively, and are pulled in + automatically as dependencies for any of any modules that need them. + You can still install them all separately from their own projects or + by using Luarocks: + + ```bash + luarocks install optparse + luarocks install std.functional + luarocks install std.prototype + luarocks install std.strict + luarocks install typecheck + ``` + + - All support for previously deprecated APIs has been removed, reducing + the install size even further. + + - `std.string.prettytostring` continues to use `normalize.string.render` + for more legible deeply nested table output, identically to previous + releases. + + - `std.npairs` and `std.rnpairs` now respect `__len` metamethod, if any. + + - `std.table.okeys` has been removed for lack of utility. If you + still need it, use this instead: + + ```lua + local okeys = std.functional.compose (std.table.keys, std.table.sort) + ``` + +### Bug fixes + + - `std.string.wrap` doesn't throw a StrBuf deprecation warning any more. + + - `std.getmetamethod` now returns functable valued metamethods + correctly, rather than `nil` as in previous releases. It's also + considerably faster now that it doesn't use `pcall` any more. + + - `table.pack` now sets `n` field to number of arguments packed, even + in Lua 5.1. + +### Incompatible changes + + - `std.container`, `std.functional`, `std.list`, `std.maturity`, + `std.object`, `std.operator`, `std.optparse`, `std.set`, + `std.strbuf`, `std.strict` and `std.tuple` have been moved to their + own packages, and are no longer shipped as part of stdlib. + + - Monkey patching calls `std.barrel`, `std.monkey_patch`, + `std.io.monkey_patch`, `std.math.monkey_patch`, + `std.string.monkey_patch` and `std.table.monkey_patch` have all + been removed. + + - `std.debug.argerror`, `std.debug.argcheck`, `std.debug.argscheck`, + `std.debug.extramsg_mismatch`, `std.debug.extramsg_toomany`, + `std.debug.parsetypes`, `std.debug.resulterror` and `std.debug.typesplit` + have all been moved to their own package, and are no longer shipped + as part of stdlib. + + - `std.debug.DEPRECATED` and `std.debug.DEPRECATIONMSG` have been + removed. At some point these will resurface in a new standalone + package. + + - Deprecated functions `string.assert`, `string.require_version`, + `string.tostring`, `table.clone_rename`, `table.metamethod`, + `table.ripairs` and `table.totable` have been removed. See previous + NEWS entries below for what they were replaced by. + + - Passing a table with a `__len` metamethod, that returns a value other + the index of the largest non-nil valued integer key, to `std.npairs` + now iterates upto whatever `__len` returns rather than `std.table.maxn`. + If `__len` is not present, or gives the same result as `maxn` then + `npairs` continues to behave as in the previous release. + + - `std.tostring` and `std.string.render` have been superceded by their + equivalents from 'std.normalize': `str` and `string.render`. Those + implementations handle skipping initial sequence keys for a more + compact output, escaping of whitespace and other C escape characters + for even more compact output and stringification of nested Objects and + Containers using their `__tostring` metamethods. + + - For consistency with std.normalize and other package symbols, we now + spell `package.path_mark` as `package.pathmark`. + + +## Noteworthy changes in release 41.2.0 (2015-03-08) [stable] + +### New features + + - New iterators, `std.npairs` and `std.rnpairs` behave like + `std.ipairs` and `std.ripairs` resp., except that they will visit + all integer keyed elements, including nil-valued "holes". This is + useful for iterating over argument lists with nils: + + ```lua + function fn(a, b, c) for _, v in npairs {...} do print(v) end + fn(nil, nil, 3) --> nil nil 3 + ``` + + - New `debug.getfenv` and `debug.setfenv` that work with Lua 5.2 and + 5.3. + + - `debug.argscheck` will skip typecheck for `self` parameter if the + function name specification contains a colon. + + - New `debug.resulterror` is much like `debug.argerror`, but uses the + message format "bad result #n from 'fname'". + + - New `debug.extramsg_mismatch` to generate `extramsg` argument for + `debug.argerror` or `debug.resulterror` on encountering a type + mismatch. + + - New `debug.extramsg_toomany` to generate a too many arguments or + similar `extramsg` argument. + +### Deprecations + + - `debug.toomanyargmsg` has been deprecated in favour of the more + orthogal `debug.extramsg_toomany` api. You can rewrite clients of + deprecated api like this: + + ```lua + if maxn(argt) > 7 then + argerror('fname', 8, extramsg_toomany('argument', 7, maxn(argt)), 2) + end + ``` + +### Bug fixes + + - `std.getmetamethod` no longer rejects non-table subjects when + `_DEBUG.argcheck` is set. + + - `functional.bind`, `functional.collect`, `functional.compose`, + `functional.filter` and `functional.map` propagate nil valued + arguments correctly. + + - `functional.callable` no longer raises an argument error when passed + a nil valued argument. + + - `debug.argcheck` and `debug.argscheck` accept "bool" as an alias for + "boolean" consistently. + + - `io.catdir` and `io.dirname` no longer leak extra results from + implementation details. + +### Incompatible changes + + - `functional.collect` uses `std.npairs` as a default iterator rather + than `std.ipairs`. + + +## Noteworthy changes in release 41.1.1 (2015-01-31) [stable] + +### Bug fixes + + - `std.barrel` no longer gets stuck in an infinite loop when called in + Lua 5.3. + + +## Noteworthy changes in release 41.1.0 (2015-01-30) [stable] + +### New features + + - Anything that responds to `tostring` can be appended to a `std.strbuf`: + + ```lua + local a, b = StrBuf {'foo', 'bar'}, StrBuf {'baz', 'quux'} + a = a .. b --> 'foobarbazquux' + ``` + + - `std.strbuf` stringifies lazily, so adding tables to a StrBuf + object, and then changing the content of them before calling + `tostring` also changes the contents of the buffer. See LDocs for + an example. + + - `debug.argscheck` accepts square brackets around final optional + parameters, which is distinct to the old way of appending `?` or + `|nil` in that no spurious "or nil" is reported for type mismatches + against a final bracketed argument. + + - `debug.argscheck` can also check types of function return values, when + specified as: + + ```lua + fn = argscheck('fname(?any...) => int, table or nil, string', fname) + ``` + + Optional results can be marked with brackets, and an ellipsis following + the final type denotes any additional results must match that final + type specification. Alternative result type groups are separated by "or". + + - New `table.unpack(t, [i, [j]])` function that defaults j to + `table.maxn(t)`, even on luajit which stops before the first nil + valued numeric index otherwise. + +### Deprecations + + - `std.strbuf.tostring` has been deprecated in favour of `tostring`. + Why write `std.strbuf.tostring(sb)` or `sb:tostring()` when it is + more idiomatic to write `tostring(sb)`? + +### Bug fixes + + - `std.barrel` and the various `monkey_patch` functions now return + their parent module table as documented. + + - stdlib modules are all `std.strict` compliant; require 'std.strict' + before requiring other modules no longer raises an error. + + - `debug.argscheck` can now diagnose when there are too many arguments, + even in the case where the earlier arguments match parameters by + skipping bracketed optionals, and the total number of arguments is + still less than the absolute maximum allowed if optionals are counted + too. + + - `package.normalize` now leaves valid ./../../ path prefixes unmolested. + +### Incompatible changes + + - `debug.argscheck` requires nil parameter type `?` notation to be + prepended to match Specl and TypedLua syntax. `?` suffixes are a + syntax error. + + - `debug.argscheck` uses `...` instead of `*` appended to the final element + if all unmatched argument types should match. The trailing `*` syntax + was confusing, because it was easy to misread it as "followed by zero-or- + more of this type". + + +## Noteworthy changes in release 41.0.0 (2015-01-03) [beta] + +### New features + + - Preliminary Lua 5.3.0 compatibility. + + - `object.prototype` now reports "file" for open file handles, and + "closed file" for closed file handles. + + - New `debug.argerror` and `debug.argcheck` functions that provide Lua + equivalents of `luaL_argerror` and `luaL_argcheck`. + + - New `debug.argscheck` function for checking all function parameter + types with a single function call in the common case. + + - New `debug.export` function, which returns a wrapper function for + checking all arguments of an inner function against a type list. + + - New `_DEBUG.argcheck` field that disables `debug.argcheck`, and + changes `debug.argscheck` to return its function argument unwrapped, + for production code. Similarly `_DEBUG = false` deactivates these + functions in the same way. + + - New `std.operator` module, with easier to type operator names (`conj`, + `deref`, `diff`, `disj`, `eq`, `neg`, `neq`, `prod`, `quot`, and `sum`), + and a functional operator for concatenation `concat`; plus new mathematical + operators `mod`, and `pow`; and relational operators `lt`, `lte`, `gt` and + `gte`. + + - `functional.case` now accepts non-callable branch values, which are + simply returned as is, and functable values which are called and + their return value propagated back to the case caller. Function + values behave the same as in previous releases. + + - `functional.collect`, `functional.filter`, `functional.map` and + `functional.reduce` now work with standard multi-return iterators, + such as `std.pairs`. + + - `functional.collect` defaults to using `std.ipairs` as an iterator. + + - New `functional.cond`, for evaluating multiple distinct expressions + to determine what following value to be the returned. + + - `functional.filter` and `functional.map` default to using `std.pairs` + as an iterator. + + - The init argument to `functional.foldl` and `functional.foldr` is now + optional; when omitted these functions automatically start with + the left- or right-most element of the table argument resp. + + - New `functional.callable` function for unwrapping objects or + primitives that can be called as if they were a function. + + - New `functional.lambda` function for compiling lambda strings: + + ```lua + table.sort(t, lambda '|a,b| a + explaining why any deprecation should be reinstated or at least kept + around for more than 1 year. + + - By default, deprecated APIs will issue a warning to stderr on every + call. However, in production code, you can turn off these warnings + entirely with any of: + + ```lua + _DEBUG = false + _DEBUG = {deprecate=false} + require 'std.debug_init'.deprecate = false + ``` + + Or, to confirm you're not trying to call a deprecated function at + runtime, you can prevent deprecated functions from being defined at + all with any of: + + ```lua + _DEBUG = true + _DEBUG = {deprecate=true} + require 'std.debug_init'.deprecate = true + ``` + + The `_DEBUG` global must be set before requiring any stdlib modules, + but you can adjust the fields in the `std.debug_init` table at any + time. + + - `functional.eval` has been moved to `std.eval`, the old name now + gives a deprecation warning. + + - `functional.fold` has been renamed to `functional.reduce`, the old + name now gives a deprecation warning. + + - `functional.op` has been moved to a new `std.operator` module, the + old function names now gives deprecation warnings. + + - `list.depair` and `list.enpair` have been moved to `table.depair` and + `table.enpair`, the old names now give deprecation warnings. + + - `list.filter` has been moved to `functional.filter`, the old name now + gives a deprecation warning. + + - `list.flatten` has been moved to `table.flatten`, the old name now + gives a deprecation warning. + + - `list.foldl` and `list.foldr` have been replaced by the richer + `functional.foldl` and `functional.foldr` respectively. The old + names now give a deprecation warning. Note that List object methods + `foldl` and `foldr` are not affected. + + - `list.index_key` and `list.index_value` have been deprecated. These + functions are not general enough to belong in lua-stdlib, because + (among others) they only work correctly with tables that can be + inverted without loss of key values. They currently give deprecation + warnings. + + - `list.map` and `list.map_with` has been deprecated, in favour of the + more powerful new `functional.map` and `functional.map_with` which + handle tables as well as lists. + + - `list.project` has been deprecated in favour of `table.project`, the + old name now gives a deprecation warning. + + - `list.relems` has been deprecated, in favour of the more idiomatic + `functional.compose(std.ireverse, std.ielems)`. + + - `list.reverse` has been deprecated in favour of the more general + and more accurately named `std.ireverse`. + + - `list.shape` has been deprecated in favour of `table.shape`, the old + name now gives a deprecation warning. + + - `list.transpose` has been deprecated in favour of `functional.zip`, + see above for details. + + - `list.zip_with` has been deprecated in favour of `functional.zip_with`, + see above for details. + + - `string.assert` has been moved to `std.assert`, the old name now + gives a deprecation warning. + + - `string.require_version` has been moved to `std.require`, the old + name now gives a deprecation warning. + + - `string.tostring` has been moved to `std.tostring`, the old name now + gives a deprecation warning. + + - `table.metamethod` has been moved to `std.getmetamethod`, the old + name now gives a deprecation warning. + + - `table.ripairs` has been moved to `std.ripairs`, the old name now + gives a deprecation warning. + + - `table.totable` has been deprecated and now gives a warning when used. + +### Incompatible changes + + - `std.monkey_patch` works the same way as the other submodule + monkey_patch functions now, by injecting its methods into the given + (or global) namespace. To get the previous effect of running all the + monkey_patch functions, either run them all manually, or call + `std.barrel()` as before. + + - `functional.bind` sets fixed positional arguments when called as + before, but when the newly bound function is called, those arguments + fill remaining unfixed positions rather than being overwritten by + original fixed arguments. For example, where this would have caused + an error previously, it now prints "100" as expected. + + ```lua + local function add(a, b) return a + b end + local incr = functional.bind(add, {1}) + print(incr(99)) + ``` + + If you have any code that calls functions returned from `bind`, you + need to remove the previously ignored arguments that correspond to + the fixed argument positions in the `bind` invocation. + + - `functional.collect`, `functional.filter` and `functional.map` still + make a list from the results from an iterator that returns single + values, but when an iterator returns multiple values they now make a + table with key:value pairs taken from the first two returned values of + each iteration. + + - The `functional.op` table has been factored out into its own new + module `std.operator`. It will also continue to be available from the + legacy `functional.op` access point for the forseeable future. + + - The `functional.op['..']` operator is no longer a list concatenation + only loaded when `std.list` is required, but a regular string + concatenation just like Lua's `..` operator. + + - `io.catdir` now raises an error when called with no arguments, for + consistency with `io.catfile`. + + - `io.die` no longer calls `io.warn` to write the error message to + stderr, but passes that error message to the core `error` function. + + - `std.set` objects used to be lax about enforcing type correctness in + function arguments, but now that we have strict type-checking on all + apis, table arguments are not coerced to Set objects but raise an + error. Due to an accident of implementation, you can get the old + inconsistent behaviour back for now by turning off type checking + before loading any stdlib modules: + + ```lua + _DEBUG = {argcheck=false} + local set = require 'std.set' + ``` + + - `string.pad` will still (by implementation accident) coerce non- + string initial arguments to a string using `string.tostring` as long + as argument checking is disabled. Under normal circumstances, + passing a non-string will now raise an error as specified in the api + documentation. + + - `table.totable` is deprecated, and thus objects no longer provide or + use a `__totable` metamethod. Instead, using a `__pairs` metamethod + to return key/value pairs, and that will automatically be used by + `__tostring`, `object.mapfields` etc. The base object now provides a + `__pairs` metamethod that returns key/value pairs in order, and + ignores private fields. If you have objects that relied on the + previous treatment of `__totable`, please convert them to set a + custom `__pairs` instead. + + +### Bug fixes + + - Removed LDocs for unused `_DEBUG.std` field. + + - `debug.trace` works with Lua 5.2.x again. + + - `list:foldr` works again instead of raising a "bad argument #1 to + 'List'" error. + + - `list.transpose` works again, and handles empty lists without + raising an error; but is deprecated and will be removed in a future + release (see above). + + - `list.zip_with` no longer raises an argument error on every call; but, + like `list.transpose`, is also deprecated (see above). + + - `optparse.on` now works with `std.strict` enabled. + + - `std.require` (nee `string.require_version`) now extracts the last + substring made entirely of digits and periods from the required + module's version string before splitting on period. That means, for + version strings like luaposix's "posix library for Lua 5.2 / 32" we + now correctly compare just the numeric part against specified version + range rather than an ASCII comparison of the whole thing as before! + + - The documentation now correcly notes that `std.require` looks + first in `module.version` and then `module._VERSION` to match the + long-standing implementation. + + - `string.split` now really does split on whitespace when no split + pattern argument is provided. Also, the documentation now + correctly cites `%s+` as the default whitespace splitting pattern + (not `%s*` which splits between every non-whitespace character). + + +## Noteworthy changes in release 40 (2014-05-01) [stable] + +### New features + + - `functional.memoize` now accepts a user normalization function, + falling back on `string.tostring` otherwise. + + - `table.merge` now supports `map` and `nometa` arguments orthogonally + to `table.clone`. + + - New `table.merge_select` function, orthogonal to + `table.clone_select`. See LDocs for details. + +### Incompatible changes + + - Core methods and metamethods are no longer monkey patched by default + when you `require 'std'` (or `std.io`, `std.math`, `std.string` or + `std.table`). Instead they provide a new `monkey_patch` method you + should use when you don't care about interactions with other + modules: + + ```lua + local io = require 'std.io'.monkey_patch() + ``` + + To install all of stdlib's monkey patches, the `std` module itself + has a `monkey_patch` method that loads all submodules with their own + `monkey_patch` method and runs them all. + + If you want full compatibility with the previous release, in addition + to the global namespace scribbling snippet above, then you need to + adjust the first line to: + + ```lua + local std = require 'std'.monkey_patch() + ``` + + - The global namespace is no longer clobbered by `require 'std'`. To + get the old behaviour back: + + ```lua + local std = require 'std'.barrel(_G) + ``` + + This will execute all available monkey_patch functions, and then + scribble all over the `_G` namespace, just like the old days. + + - The `metamethod` call is no longer in `std.functional`, but has moved + to `std.table` where it properly belongs. It is a utility method for + tables and has nothing to do with functional programming. + + - The following deprecated camelCase names have been removed, you + should update your code to use the snake_case equivalents: + `std.io.processFiles`, `std.list.indexKey`, `std.list.indexValue`, + `std.list.mapWith`, `std.list.zipWith`, `std.string.escapePattern`, + `std.string. escapeShell`, `std.string.ordinalSuffix`. + + - The following deprecated function names have been removed: + `std.list.new` (call `std.list` directly instead), + `std.list.slice` (use `std.list.sub` instead), + `std.set.new` (call `std.set` directly instead), + `std.strbuf.new` (call `std.strbuf` directly instead), and + `std.tree.new` (call `std.tree` directly instead). + +### Bug fixes + + - Allow `std.object` derived tables as `std.tree` keys again. + + +## Noteworthy changes in release 39 (2014-04-23) [stable] + +### New features + + - New `std.functional.case` function for rudimentary case statements. + The main difference from serial if/elseif/end comparisons is that + `with` is evaluated only once, and then the match function is looked + up with an O(1) table reference and function call, as opposed to + hoisting an expression result into a temporary variable, and O(n) + comparisons. + + The function call overhead is much more significant than several + comparisons, and so `case` is slower for all but the largest series + of if/elseif/end comparisons. It can make your code more readable, + however. + + See LDocs for usage. + + - New pathstring management functions in `std.package`. + + Manage `package.path` with normalization, duplicate removal, + insertion & removal of elements and automatic folding of '/' and '?' + onto `package.dirsep` and `package.path_mark`, for easy addition of + new paths. For example, instead of all this: + + ```lua + lib = std.io.catfile('.', 'lib', package.path_mark .. '.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: + + ```lua + package.path = package.normalize('./lib/?.lua', package.path) + ``` + + - `std.optparse:parse` accepts a second optional parameter, a table of + default option values. + + - `table.clone` accepts an optional table of key field renames in the + form of `{oldkey=newkey, ...}` subsuming the functionality of + `table.clone_rename`. The final `nometa` parameter is supported + whether or not a rename map is given: + + ```lua + r = table.clone(t, 'nometa') + r = table.clone(t, {oldkey=newkey}, 'nometa') + ``` + +### Deprecations + + - `table.clone_rename` now gives a warning on first call, and will be + removed entirely in a few releases. The functionality has been + subsumed by the improvements to `table.clone` described above. + +### Bug fixes + + - `std.optparse` no longer throws an error when it encounters an + unhandled option in a combined (i.e. `-xyz`) short option string. + + - Surplus unmapped fields are now discarded during object cloning, for + example when a prototype has `_init` set to `{'first', 'second'}`, + and is cloned using `Proto {'one', 'two', 'three'}`, then the + unmapped `three` argument is now discarded. + + - The path element returned by `std.tree.nodes` can now always be + used as a key list to dereference the root of the tree, particularly + `tree[{}]` now returns the root node of `tree`, to match the initial + `branch` and final `join` results from a full traversal by + `std.tree.nodes(tree)`. + +### Incompatible changes + + - `std.string` no longer sets `__append`, `__concat` and `__index` in + the core strings metatable by default, though `require 'std'` does + continue to do so. See LDocs for `std.string` for details. + + - `std.optparse` no longer normalizes unhandled options. For example, + `--unhandled-option=argument` is returned unmolested from `parse`, + rather than as two elements split on the `=`; and if a combined + short option string contains an unhandled option, then whatever was + typed at the command line is returned unmolested, rather than first + stripping off and processing handled options, and returning only the + unhandled substring. + + - Setting `_init` to `{}` in a prototype object will now discard all + positional parameters passed during cloning, because a table valued + `_init` is a list of field names, beyond which surplus arguments (in + this case, all arguments!) are discarded. + + +## Noteworthy changes in release 38 (2014-01-30) [stable] + +### New features + + - The separator parameter to `std.string.split` is now optional. It + now splits strings with `%s+` when no separator is specified. The + new implementation is faster too. + + - New `std.object.mapfields` method factors out the table field copying + and mapping performed when cloning a table `_init` style object. This + means you can call it from a function `_init` style object after + collecting a table to serve as `src` to support derived objects with + normal std.object syntax: + + ```lua + Proto = Object { + _type = 'proto' + _init = function(self, arg, ...) + if type(arg) == 'table' then + mapfields(self, arg) + else + -- non-table instantiation code + end + end, + } + new = Proto(str, #str) + Derived = proto {_type='Derived', ...} + ``` + + - Much faster object cloning; `mapfields` is in imperative style and + makes one pass over each table it looks at, where previous releases + used functional style (stack frame overhead) and multiple passes over + input tables. + + On my 2013 Macbook Air with 1.3GHz Core i5 CPU, I can now create a + million std.objects with several assorted fields in 3.2s. Prior to + this release, the same process took 8.15s... and even release 34.1, + with drastically simpler Objects (19SLOC vs over 120) took 5.45s. + + - `std.object.prototype` is now almost an order of magnitude faster + than previous releases, taking about 20% of the time it previously + used to return its results. + + - `io.warn` and `io.die` now integrate properly with `std.optparse`, + provided you save the `opts` return from `parser:parse` back to the + global namespace where they can access it: + + ```lua + 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 + ``` + + will, when run, output to stderr: "eg: oh noes!" + +### Bug fixes + + - Much improved documentation for `optparse`, so you should be able + to use it without reading the source code now! + + - `io.warn` and `io.die` no longer output a line-number when there is + no file name to append it to. + + - `io.warn` and `io.die` no longer crash in the absence of a global + `prog` table. + + - `string.split` no longer goes into an infinite loop when given an + empty separator string. + + - Fix `getmetatable(container._functions) == getmetatable(container)`, + which made tostring on containers misbehave, among other latent bugs. + + - `_functions` is never copied into a metatable now, finally solving + the conflicted concerns of needing metatables to be shared between + all objects of the same `_type` (for `__lt` to work correctly for one + thing) and not leaving a dangling `_functions` list in the metatable + of cloned objects, which could delete functions with matching names + from subsequent clones. + + +## Noteworthy changes in release 37 (2014-01-19) [stable] + +### New features + + - Lazy loading of submodules into `std` on first reference. On initial + load, `std` has the usual single `version` entry, but the `__index` + metatable will automatically require submodules on first reference: + + ```lua + local std = require 'std' + local prototype = std.container.prototype + ``` + + - New `std.optparse` module: A civilised option parser. + (L)Documentation distributed in doc/classes/std.optparse.html. + +### Bug fixes + + - Modules no longer leak `new' and `proper_subset' into the global + table. + + - Cloned `Object` and `Container` derived types are more aggressive + about sharing metatables, where previously the metatable was copied + unnecessarily the base object used `_functions` for module functions + + - The retracted release 36 changed the operand order of many `std.list` + module functions unnecessarily. Now that `_function` support is + available, there's no need to be so draconian, so the original v35 + and earlier operand order works as before again. + + - `std.list.new`, `std.set.new`, `set.strbuf.new` and `std.tree.new` + are available again for backwards compatibility. + + - LuaRocks install doesn't copy config.ld and config.ld to $docdir. + +### Incompatible changes + + - `std.getopt` is no more. It appears to have no users, though if there + is a great outcry, it should be easy to make a compatibility api over + `std.optparse` in the next release. + + +## Noteworthy changes in release 36 (2014-01-16) [stable] + +### New features + + - Modules have been refactored so that they can be safely + required individually, and without loading themselves or any + dependencies on other std modules into the global namespace. + + - Objects derived from the `std.object` prototype have a new + :prototype() method that returns the contents of the + new internal `_type` field. This can be overridden during cloning + with, e.g.: + + ```lua + local Object = require 'std.object' + Prototype = Object {_type='Prototype', } + ``` + + - Objects derived from the `std.object` prototype return a new table + with a shallow copy of all non-private fields (keys that do not + begin with '_') when passed to `table.totable` - unless overridden + in the derived object's __totable field. + + - list and strbuf are now derived from `std.object`, which means that + they respond to `object.prototype` with appropriate type names ('List', + 'StrBuf', etc.) and can be used as prototypes for further derived + objects or clones; support object:prototype(); respond to totable etc. + + - A new Container module at `std.container` makes separation between + container objects (which are free to use __index as a '[]' access + metamethod, but) which have no object methods, and regular objects + (which do have object methods, but) which cannot use the __index + metamethod for '[]' access to object contents. + + - set and tree are now derived from `std.container`, so there are no + object methods. Instead there are a full complement of equivalent + module functions. Metamethods continue to work as before. + + - `string.prettytostring` always displays table elements in the same + order, as provided by `table.sort`. + + - `table.totable` now accepts a string, and returns a list of the + characters that comprise the string. + + - Can now be installed directly from a release tarball by `luarocks`. + No need to run `./configure` or `make`, unless you want to install to + a custom location, or do not use LuaRocks. + +### Bug fixes + + - string.escape_pattern is now Lua 5.2 compatible. + + - all objects now reuse prototype metatables, as required for __le and + __lt metamethods to work as documented. + +### Deprecations + + - To avoid confusion between the builtin Lua `type` function and the + method for finding the object prototype names, `std.object.type` is + deprecated in favour of `std.object.prototype`. `std.object.type` + continues to work for now, but might be removed from a future + release. + + ```lua + local prototype = require 'std.object'.prototype + ``` + + ...makes for more readable code, rather than confusion between the + different flavours of `type`. + +### Incompatible changes + + - Following on from the Grand Renaming™ change in the last release, + `std.debug_ext`, `std.io_ext`, `std.math_ext`, `std.package_ext`, + `std.string_ext` and `std.table_ext` no longer have the spurious + `_ext` suffix. Instead, you must now use, e.g.: + + ```lua + local string = require 'std.string' + ``` + + These names are now stable, and will be available from here for + future releases. + + - The `std.list` module, as a consequence of returning a List object + prototype rather than a table of functions including a constructor, + now always has the list operand as the first argument, whether that + function is called with `.` syntax or `:` syntax. Functions which + previously had the list operand in a different position when called + with `.` syntax were: list.filter, list.foldl, list.foldr, + list.index_key, list.index_value, list.map, list.map_with, + list.project, list.shape and list.zip_with. Calls made as object + methods using `:` calling syntax are unchanged. + + - The `std.set` module is a `std.container` with no object methods, + and now uses prototype functions instead: + + ```lua + local union = Set.union(set1, set2) + ``` + + +## Noteworthy changes in release 35 (2013-05-06) [stable] + +### New features + + - Move to the Slingshot release system. + - Continuous integration from Travis automatically builds stdilb + with Lua 5.1, Lua 5.2 and luajit-2.0 with every commit, which + should help prevent future release breaking compatibility with + one or another of those interpreters. + +### Bug fixes + + - `std.package_ext` no longer overwrites the core `package` table, + leaving the core holding on to memory that Lua code could no + longer access. + +### Incompatible changes + + - The Grand Renaming™ - everything now installs to $luaprefix/std/, + except `std.lua` itself. Importing individual modules now involves: + + ```lua + local list = require 'std.list' + ``` + + If you want to have all the symbols previously available from the + global and core module namespaces, you will need to put them there + yourself, or import everything with: + + ```lua + require 'std' + ``` + + which still behaves per previous releases. + + Not all of the modules work correctly when imported individually + right now, until we figure out how to break some circular dependencies. + + +## Noteworthy changes in release 34.1 (2013-04-01) [stable] + + - This is a maintenance release to quickly fix a breakage in getopt + from release v34. Getopt no longer parses non-options, but stops + on the first non-option... if a use case for the other method + comes up, we can always add it back in. + + +## Noteworthy changes in release 34 (2013-03-25) [stable] + + - stdlib is moving towards supporting separate requirement of individual + modules, without scribbling on the global environment; the work is not + yet complete, but we're collecting tests along the way to ensure that + once it is all working, it will carry on working; + + - there are some requirement loops between modules, so not everything can + be required independently just now; + + - `require 'std'` will continue to inject std symbols into the system + tables for backwards compatibility; + + - stdlib no longer ships a copy of Specl, which you will need to install + separately if you want to run the bundled tests; + + - getopt supports parsing of undefined options; useful for programs that + wrap other programs; + + - getopt.Option constructor is no longer used, pass a plain Lua table of + options, and getopt will do the rest; + + +## Noteworthy changes in release 33 (2013-07-27) [stable] + + - This release improves stability where Specl has helped locate some + corner cases that are now fixed. + + - `string_ext.wrap` and `string_ext.tfind` now diagnose invalid arguments. + + - Specl code coverage is improving. + + - OrdinalSuffix improvements. + + - Use '%' instead of math.mod, as the latter does not exist in Lua 5.2. + + - Accept negative arguments. + + +## Noteworthy changes in release 32 (2013-02-22) [stable] + + - This release fixes a critical bug preventing getopt from returning + anything in getopt.opt. Gary V. Vaughan is now a co-maintainer, currently + reworking the sources to use (Lua 5.1 compatible) Lua 5.2 style module + packaging, which requires you to assign the return values from your imports: + + ```lua + getopt = require 'getopt' + ``` + + - Extension modules, table_ext, package_ext etc. return the unextended module + table before injecting additional package methods, so you can ignore those + return values or save them for programatically backing out the changes: + + ```lua + table_unextended = require 'table_ext' + ``` + + - Additionally, Specl (see http://github.com/gvvaughan/specl/) specifications + are being written for stdlib modules to help us stop accidentally breaking + things between releases. + + +## Noteworthy changes in release 31 (2013-02-20) [stable] + + - This release improves the list module: lists now have methods, list.slice + is renamed to list.sub (the old name is provided as an alias for backwards + compatibility), and all functions that construct a new list return a proper + list, not a table. As a result, it is now often possible to write code that + works on both lists and strings. + + +## Noteworthy changes in release 30 (2013-02-17) [stable] + + - This release changes some modules to be written in a Lua 5.2 style (but + not the way they work with 5.1). Some fixes and improvements were made to + the build system. Bugs in the die function, the parser module, and a nasty + bug in the set module introduced in the last release (29) were fixed. + + +## Noteworthy changes in release 29 (2013-02-06) [stable] + + - This release overhauls the build system to have LuaRocks install releases + directly from git rather than from tarballs, and fixes a bug in set (issue + #8). + + +## Noteworthy changes in release 28 (2012-10-28) [stable] + + - This release improves the documentation and build system, and improves + require_version to work by default with more libraries. + + +## Noteworthy changes in release 27 (2012-10-03) [stable] + + - This release changes getopt to return all arguments in a list, rather than + optionally processing them with a function, fixes an incorrect definition + of set.elems introduced in release 26, turns on debugging by default, + removes the not-very-useful string.gsubs, adds constructor functions for + objects, renames table.rearrange to the more descriptive table.clone_rename + and table.indices to table.keys, and makes table.merge not clone but modify + its left-hand argument. A function require_version has been added to allow + version constraints on a module being required. Gary Vaughan has + contributed a memoize function, and minor documentation and build system + improvements have been made. Usage information is now output to stdout, not + stderr. The build system has been fixed to accept Lua 5.2. The luarock now + installs documentation, and the build command used is now more robust + against previous builds in the same tree. + + +## Noteworthy changes in release 26 (2012-02-18) [stable] + + - This release improves getopt's output messages and conformance to + standard practice for default options. io.processFiles now unsets prog.file + when it finishes, so that a program can tell when itâs no longer + processing a file. Three new tree iterators, inodes, leaves and ileaves, + have been added; the set iterator set.elements (renamed to set.elems for + consistency with list.elems) is now leaves rather than pairs. tree indexing + has been made to work in more circumstances (thanks, Gary Vaughan). + io.writeline is renamed io.writelines for consistency with io.readlines and + its function. A slurping function, io.slurp, has been added. Strings now + have a __concat metamethod. + + +## Noteworthy changes in release 25 (2011-09-19) [stable] + + - This release adds a version string to the std module and fixes a buglet in + the build system. + + +## Noteworthy changes in release 24 (2011-09-19) [stable] + + - This release fixes a rename missing from release 23, and makes a couple of + fixes to the new build system, also from release 23. + + +## Noteworthy changes in release 23 (2011-09-17) [stable] + + - This release removes the posix_ext module, which is now part of luaposix, + renames string.findl to string.tfind to be the same as lrexlib, and + autotoolizes the build system, as well as providing a rockspec file. + + +## Noteworthy changes in release 22 (2011-09-02) [stable] + + - This release adds two new modules: strbuf, a trivial string buffers + implementation, which is used to speed up the stdlib tostring method for + tables, and bin, which contains a couple of routines for converting binary + data into numbers and strings. Some small documentation and build system + fixes have been made. + + +## Noteworthy changes in release 21 (2011-06-06) [stable] + + - This release converts the documentation of stdlib to LuaDoc, adds an + experimental Lua 5.2 module "fstable", for storing tables directly on + disk as files and directories, and fixes a few minor bugs (with help from + David Favro). + + - This release has been tested lightly on Lua 5.2 alpha, but is not + guaranteed to work fully. + + +## Noteworthy changes in release 20 (2011-04-14) [stable] + + - This release fixes a conflict between the global _DEBUG setting and the use + of strict.lua, changes the argument order of some list functions to favour + OO-style use, adds posix.euidaccess, and adds OO-style use to set. mk1file + can now produce a single-file version of a user-supplied list of modules, + not just the standard set. + + +## Noteworthy changes in release 19 (2011-02-26) [stable] + + - This release puts the package.config reflection in a new package_ext + module, where it belongs. Thanks to David Manura for this point, and for a + small improvement to the code. + + +## Noteworthy changes in release 18 (2011-02-26) [stable] + + - This release provides named access to the contents of package.config, which + is undocumented in Lua 5.1. See luaconf.h and the Lua 5.2 manual for more + details. + + +## Noteworthy changes in release 17 (2011-02-07) [stable] + + - This release fixes two bugs in string.pad (thanks to Bob Chapman for the + fixes). + + +## Noteworthy changes in release 16 (2010-12-09) [stable] + + - Adds posix module, using luaposix, and makes various other small fixes and + improvements. + + +## Noteworthy changes in release 15 (2010-06-14) [stable] + + - This release fixes list.foldl, list.foldr, the fold iterator combinator and + io.writeLine. It also simplifies the op table, which now merely sugars the + built-in operators rather than extending them. It adds a new tree module, + which subsumes the old table.deepclone and table.lookup functions. + table.subscript has become op['[]'], and table.subscripts has been removed; + the old treeIter iterator has been simplified and generalised, and renamed + to nodes. The mk1file script and std.lua library loader have had the module + list factored out into modules.lua. strict.lua from the Lua distribution is + now included in stdlib, which has been fixed to work with it. Some minor + documentation and other code improvements and fixes have been made. + + +## Noteworthy changes in release 14 (2010-06-07) [stable] + + - This release makes stdlib compatible with strict.lua, which required a + small change to the debug_ext module. Some other minor changes have also + been made to that module. The table.subscripts function has been removed + from the table_ext.lua. + + +## Noteworthy changes in release 13 (2010-06-02) [stable] + + - This release removes the lcs module from the standard set loaded by + 'std', removes an unnecessary definition of print, and tidies up the + implementation of the "op" table of functional versions of the infix + operators and logical operators. + + +## Noteworthy changes in release 12 (2009-09-07) [stable] + + - This release removes io.basename and io.dirname, which are now available in + lposix, and the little-used functions addSuffix and changeSuffix which + dependend on them. io.pathConcat is renamed to io.catdir and io.pathSplit + to io.splitdir, making them behave the same as the corresponding Perl + functions. The dependency on lrexlib has been removed along with the rex + wrapper module. Some of the more esoteric and special-purpose modules + (mbox, xml, parser) are no longer loaded by 'require 'std''. + + This leaves stdlib with no external dependencies, and a rather more + coherent set of basic modules. + + +## Noteworthy changes in release 11 (2009-03-15) [stable] + + - This release fixes a bug in string.format, removes the redundant + string.join (it's the same as table.concat), and adds to table.clone and + table.deepclone the ability to copy without metatables. Thanks to David + Kantowitz for pointing out the various deficiencies. + + +## Noteworthy changes in release 10 (2009-03-13) [stable] + + - This release fixes table.deepclone to copy metatables, as it should. + Thanks to David Kantowitz for the fix. + + +## Noteworthy changes in release 9 (2009-02-19) [stable] + + - This release updates the object module to be the same as that published + in "Lua Gems", and fixes a bug in the utility mk1file which makes a + one-file version of the library, to stop it permanently redefining require. + + +## Noteworthy changes in release 8 (2008-09-04) [stable] + + - This release features fixes and improvements to the set module; thanks to + Jiutian Yanling for a bug report and suggestion which led to this work. + + +## Noteworthy changes in release 7 (2008-09-04) [stable] + + - just a bug fix + + +## Noteworthy changes in release 6 (2008-07-28) [stable] + + - This release rewrites the iterators in a more Lua-ish 5.1 style. + + +## Noteworthy changes in release 5 (2008-03-04) [stable] + + - I'm happy to announce a new release of my standard Lua libraries. It's been + nearly a year since the last release, and I'm happy to say that since then + only one bug has been found (thanks Roberto!). Two functions have been + added in this release, to deal with file paths, and one removed (io.length, + which is handled by lfs.attributes) along with one constant (INTEGER_BITS, + handled by bitlib's bit.bits). + + - For those not familiar with stdlib, it's a pure-Lua library of mostly + fundamental data structures and algorithms, in particular support for + functional and object-oriented programming, string and regex operations and + extensible pretty printing of data structures. More specific modules + include a getopt implementation, a generalised least common subsequences + (i.e. diff algorithm) implementation, a recursive-descent parser generator, + and an mbox parser. + + - It's quite a mixed bag, but almost all written for real projects. It's + written in a doc-string-ish style with the supplied very simple ldoc tool. + + - I am happy with this code base, but there are various things it could use: + + 0. Tests. Tests. Tests. The code has no unit tests. It so needs them. + + 1. More code. Nothing too specialised (unless it's too small to be released + on its own, although very little seems "too small" in the Lua + community). Anything that either has widespread applicability (like + getopt) or is very general (data structures, algorithms, design + patterns) is good. + + 2. Refactoring. The code is not ideally factored. At the moment it is + divided into modules that extend existing libraries, and new modules + constructed along similar lines, but I think that some of the divisions + are confusing. For example, the functional programming support is spread + between the list and base modules, and would probably be better in its + own module, as those who aren't interested in the functional style won't + want the functional list support or the higher-order functions support, + and those who want one will probably want the other. + + 3. Documentation work. There's not a long wrong with the existing + documentation, but it would be nice, now that there is a stable LuaDoc, + to use that instead of the built-in ldoc, which I'm happy to discard now + that LuaDoc is stable. ldoc was always designed as a minimal LuaDoc + substitute in any case. + + 4. Maintenance and advocacy. For a while I have been reducing my work on + Lua, and am also now reducing my work in Lua. If anyone would like to + take on stdlib, please talk to me. It fills a much-needed function: I + suspect a lot of Lua programmers have invented the wheels with which it + is filled over and over again. In particular, many programmers could + benefit from the simplicity of its simple and well-designed functional, + string and regex capabilities, and others will love its comprehensive + getopt. + + +## Noteworthy changes in release 4 (2007-04-26) [beta] + + - This release removes the dependency on the currently unmaintained lposix + library, includes pre-built HTML documentation, and fixes some 5.0-style + uses of variadic arguments. + + Thanks to Matt for pointing out all these problems. stdlib is very much + user-driven at the moment, since it already does everything I need, and I + don't have much time to work on it, so do please contact me if you find + bugs or problems or simply don't understand it, as the one thing I *do* + want to do is make it useful and accessible! + + +## Noteworthy changes in release 3 (2007-02-25) [beta] + + - This release fixes the "set" and "lcs" (longest common subsequence, or + "grep") libraries, which were broken, and adds one or two other bug and + design fixes. Thanks are due to Enrico Tassi for pointing out some of the + problems. + + +## Noteworthy changes in release 2 (2007-01-05) [beta] + + - This release includes some bug fixes, and compatibility with lrexlib 2.0. + + +## Noteworthy changes in release 1 (2011-09-02) [beta] + + - It's just a snapshot of CVS, but it's pretty stable at the moment; stdlib, + until such time as greater interest or participation enables (or forces!) + formal releases will be in permanent beta, and tracking CVS is recommended. + + +[optparse]: https://github.com/gvvaughan/optparse +[strict]: https://github.com/lua-stdlib/strict diff --git a/README.md b/README.md index 0a7bb94..49b0328 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,92 @@ Standard Lua libraries ====================== -by the [stdlib project][github] +Copyright (C) 2000-2018 [stdlib authors][github] -[github]: http://github.com/rrthomas/lua-stdlib/ "Github repository" +[![License](http://img.shields.io/:license-mit-blue.svg)](http://mit-license.org) +[![travis-ci status](https://secure.travis-ci.org/lua-stdlib/lua-stdlib.png?branch=master)](http://travis-ci.org/lua-stdlib/lua-stdlib/builds) +[![codecov.io](https://codecov.io/gh/lua-stdlib/lua-stdlib/branch/master/graph/badge.svg)](https://codecov.io/gh/lua-stdlib/lua-stdlib) +[![Stories in Ready](https://badge.waffle.io/lua-stdlib/lua-stdlib.png?label=ready&title=Ready)](https://waffle.io/lua-stdlib/lua-stdlib) -[![travis-ci status](https://secure.travis-ci.org/rrthomas/lua-stdlib.png?branch=master)](http://travis-ci.org/rrthomas/lua-stdlib/builds) - -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 under the MIT license (the same +This is a collection of Lua libraries for Lua 5.1 (including LuaJIT), 5.2 +and 5.3. The libraries are copyright by their authors (see the [AUTHORS][] +file for details), and released under the [MIT license][mit] (the same license as Lua itself). There is no warranty. -Stdlib has no prerequisites beyond a standard Lua system. +_stdlib_ has no run-time prerequisites beyond a standard Lua system, +though it will take advantage of [strict][] and [typecheck][] if they +are installed. + +[authors]: http://github.com/lua-stdlib/lua-stdlib/blob/master/AUTHORS.md +[github]: http://github.com/lua-stdlib/lua-stdlib/ "Github repository" +[lua]: http://www.lua.org "The Lua Project" +[mit]: http://mit-license.org "MIT License" +[strict]: https://github.com/lua-stdlib/strict "strict variables" +[typecheck]: https://github.com/gvvaughan/typecheck "function type checks" Installation ------------ -The simplest way to install stdlib is with [LuaRocks][]. To install the -latest release (recommended): +The simplest and best way to install stdlib is with [LuaRocks][]. To +install the latest release (recommended): +```bash luarocks install stdlib +``` -To install current git master (for testing): +To install current git master (for testing, before submitting a bug +report for example): - luarocks install https://raw.github.com/rrthomas/lua-stdlib/release/stdlib-git-1.rockspec +```bash + luarocks install http://raw.githubusercontent.com/lua-stdlib/lua-stdlib/master/stdlib-git-1.rockspec +``` -To install without LuaRocks, check out the sources from the -[repository][github], and then run the following commands: the -dependencies are listed in the dependencies entry of the file -`stdlib-rockspec.lua`. You will also need autoconf and automake. +The best way to install without [LuaRocks][] is to copy the `std` +folder and its contents into a directory on your package search path. - cd lua-stdlib - autoreconf --force --version --install - ./configure --prefix=INSTALLATION-ROOT-DIRECTORY - make all check install +[luarocks]: http://www.luarocks.org "Lua package manager" -See [INSTALL][] for instructions for `configure`. -[luarocks]: http://www.luarocks.org "LuaRocks Project" -[install]: https://raw.github.com/rrthomas/lua-stdlib/master/INSTALL +Documentation +------------- -Use ---- +The latest release of these libraries is [documented in LDoc][github.io]. +Pre-built HTML files are included in the release. -As well as requiring individual libraries, you can load the standard -set with +[github.io]: http://lua-stdlib.github.io/lua-stdlib - require "std" -Modules not in the standard set may be removed from future versions of -stdlib. +Bug reports and code contributions +---------------------------------- +These libraries are written and maintained by their users. -Documentation -------------- +Please make bug reports and suggestions as [GitHub Issues][issues]. +Pull requests are especially appreciated. -The libraries are [documented in LuaDoc][github.io]. Pre-built HTML -files are included. +But first, please check that your issue has not already been reported by +someone else, and that it is not already fixed by [master][github] in +preparation for the next release (see Installation section above for how +to temporarily install master with [LuaRocks][]). -[github.io]: http://rrthomas.github.io/lua-stdlib +There is no strict coding style, but please bear in mind the following +points when proposing changes: +0. Follow existing code. There are a lot of useful patterns and avoided + traps there. -Bug reports and code contributions ----------------------------------- +1. 3-character indentation using SPACES in Lua sources: It makes rogue + TABS easier to see, and lines up nicely with 'if' and 'end' keywords. + +2. Simple strings are easiest to type using single-quote delimiters, + saving double-quotes for where a string contains apostrophes. + +3. Save horizontal space by only using SPACES where the parser requires + them. + +4. Use vertical space to separate out compound statements to help the + coverage reports discover untested lines. -These libraries are written and maintained by their users. Please make -bug report and suggestions on GitHub (see URL at top of file). Pull -requests are especially appreciated. +[issues]: http://github.com/lua-stdlib/lua-stdlib/issues diff --git a/STYLE.md b/STYLE.md new file mode 100644 index 0000000..f01f630 --- /dev/null +++ b/STYLE.md @@ -0,0 +1,97 @@ +## Lua + + - Requiring any stdlib module must not leak any symbols into the global + namespace. + + - Any stdlib module may `require "std.base"`, and use any functions from + there, as well as functions from `std.debug` (and `debug_init`); but, + all other modules export argument checked functions that should not be + called from anywhere in stdlib -- this is the client API. If a + function is needed by more than one module, move it to `std.base` + without argument checking, and re-export with `argscheck` if necess- + ary. + + Obviously, for objects it's perfectly fine to require the file that + defines the object being derived from. But to prevent accidentally + calling argchecked methods, we always immediately create a prototype + object with, e.g: + + local Container = require "std.container" {} + + (`std.object` is an exception to this rule because of how tightly + bound to `std.container` it is, and does directly call some of + containers methods by design). + + - Minimise forward declarations of functions, because having some + declared as `local` in line, and others not is ugly and can easily + cause rogue `local` keywords to be introduced that end up shadowing + the intended declaration. Mutually recursive functions, and + alternate definitions are acceptable, in which case keep the forward + declarations and definitions as close together as possible to + minimise any possible misunderstandings later. + + - Try to maintain asciibetical ordering of function definitions in each + source file, except where doing so would require forward declar- + ations. In that case use topological ordering to avoid the forward + declarations. + + - Unless a table cannot possibly have a __len metamethod (i.e. it was + constructed without one in the current scope), always use + `base.insert` and `base.len` rather than core `table.insert` and the + `#` operator, which do not honor __len in all implementations. + + - Unless a table cannot possibly have __pairs or __len metamethods + (i.e. it was constructed without them in the current scope), always + use `base.pairs` or `base.ipairs` rather than core `pairs` and + `ipairs`, which do not honor __pairs or __len in all implementations. + + - Use consistent short names for common parameters: + + fh a file handle, usually from io.open or similar + fmt a format string + fn a function + i an index + k a value, usually from pairs or similar + l a list-like table + n a number + s a string + t a table + + - Do argument check all object methods (functions available from an + object created by a module function -- usually listed in the + `__index` subtable of the object metatable), to catch pathological + calls early, preferably using a `typecheck.argscheck` wrapper around + the internal implementation: this way, implementation functions can + call each other without excessive rechecking of argument types. + + - Do argument check all module functions (functions available in the + table returned from requiring that module). + + - Do argument check metamethods, to catch pathological calls early. + + +## LDocs + + - LDocs should be next to each function's argcheck wrapper (if it has + one) in the export table, so that it's easy to check the consistency + between the types declared in the LDocs and the argument types + enforced by `typecheck.argscheck` or equivalent. + + - `backtick_references` is disabled for stdlib, if you want an inline + cross-reference, use `@{reference}`. + + - Be liberal with `@see` references to similar apis. + + - Refer to other argument names with italics (`*italic*` in markdown). + + - Try to add entries for callback function signatures, and name them + with the suffix `cb`. + + - Rely on the reader to understand how `:` call syntax works in Lua, and + don't waste effort documenting methods that are already documented as + functions. + + - Do document the prototype chain. Don't document methods inherited + from the prototype, even they have been overridden to behave consist- + ently from a UI perspective even though the implementation needs to be + different to provide that same UI. diff --git a/bootstrap b/bootstrap deleted file mode 100755 index fec6f33..0000000 --- a/bootstrap +++ /dev/null @@ -1,4826 +0,0 @@ -#! /bin/sh - -# Bootstrap an Autotooled package from checked-out sources. -# Written by Gary V. Vaughan, 2010 - -# Copyright (C) 2010-2013 Free Software Foundation, Inc. -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -# Source required external libraries: -# Set a version string for this script. -scriptversion=2013-08-23.20; # UTC - -# General shell script boiler plate, and helper functions. -# Written by Gary V. Vaughan, 2004 - -# Copyright (C) 2004-2013 Free Software Foundation, Inc. -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. - -# As a special exception to the GNU General Public License, if you distribute -# this file as part of a program or library that is built using GNU Libtool, -# you may include this file under the same distribution terms that you use -# for the rest of that program. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNES FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Please report bugs or propose patches to gary@gnu.org. - - -## ------ ## -## Usage. ## -## ------ ## - -# Evaluate this file near the top of your script to gain access to -# the functions and variables defined here: -# -# . `echo "$0" | ${SED-sed} 's|[^/]*$||'`/build-aux/funclib.sh -# -# If you need to override any of the default environment variable -# settings, do that before evaluating this file. - - -## -------------------- ## -## Shell normalisation. ## -## -------------------- ## - -# Some shells need a little help to be as Bourne compatible as possible. -# Before doing anything else, make sure all that help has been provided! - -DUALCASE=1; export DUALCASE # for MKS sh -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : - emulate sh - NULLCMD=: - # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which - # is contrary to our usage. Disable this feature. - alias -g '${1+"$@"}'='"$@"' - setopt NO_GLOB_SUBST -else - case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac -fi - -# NLS nuisances: We save the old values in case they are required later. -_G_user_locale= -_G_safe_locale= -for _G_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES -do - eval "if test set = \"\${$_G_var+set}\"; then - save_$_G_var=\$$_G_var - $_G_var=C - export $_G_var - _G_user_locale=\"$_G_var=\\\$save_\$_G_var; \$_G_user_locale\" - _G_safe_locale=\"$_G_var=C; \$_G_safe_locale\" - fi" -done - -# CDPATH. -(unset CDPATH) >/dev/null 2>&1 && unset CDPATH - -# Make sure IFS has a sensible default -sp=' ' -nl=' -' -IFS="$sp $nl" - -# There are still modern systems that have problems with 'echo' mis- -# handling backslashes, among others, so make sure $bs_echo is set to a -# command that correctly interprets backslashes. -# (this code from Autoconf 2.68) - -# Printing a long string crashes Solaris 7 /usr/bin/printf. -bs_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' -bs_echo=$bs_echo$bs_echo$bs_echo$bs_echo$bs_echo -bs_echo=$bs_echo$bs_echo$bs_echo$bs_echo$bs_echo$bs_echo -# Prefer a ksh shell builtin over an external printf program on Solaris, -# but without wasting forks for bash or zsh. -if test -z "$BASH_VERSION$ZSH_VERSION" \ - && (test "X`print -r -- $bs_echo`" = "X$bs_echo") 2>/dev/null; then - bs_echo='print -r --' - bs_echo_n='print -rn --' -elif (test "X`printf %s $bs_echo`" = "X$bs_echo") 2>/dev/null; then - bs_echo='printf %s\n' - bs_echo_n='printf %s' -else - if test "X`(/usr/ucb/echo -n -n $bs_echo) 2>/dev/null`" = "X-n $bs_echo"; then - bs_echo_body='eval /usr/ucb/echo -n "$1$nl"' - bs_echo_n='/usr/ucb/echo -n' - else - bs_echo_body='eval expr "X$1" : "X\\(.*\\)"' - bs_echo_n_body='eval - arg=$1; - case $arg in #( - *"$nl"*) - expr "X$arg" : "X\\(.*\\)$nl"; - arg=`expr "X$arg" : ".*$nl\\(.*\\)"`;; - esac; - expr "X$arg" : "X\\(.*\\)" | tr -d "$nl" - ' - export bs_echo_n_body - bs_echo_n='sh -c $bs_echo_n_body bs_echo' - fi - export bs_echo_body - bs_echo='sh -c $bs_echo_body bs_echo' -fi - - -## ------------------------------- ## -## User overridable command paths. ## -## ------------------------------- ## - -# All uppercase variable names are used for environment variables. These -# variables can be overridden by the user before calling a script that -# uses them if a suitable command of that name is not already available -# in the command search PATH. - -: ${CP="cp -f"} -: ${ECHO="$bs_echo"} -: ${EGREP="grep -E"} -: ${FGREP="grep -F"} -: ${GREP="grep"} -: ${LN_S="ln -s"} -: ${MAKE="make"} -: ${MKDIR="mkdir"} -: ${MV="mv -f"} -: ${RM="rm -f"} -: ${SED="sed"} -: ${SHELL="${CONFIG_SHELL-/bin/sh}"} - - -## -------------------- ## -## Useful sed snippets. ## -## -------------------- ## - -sed_dirname='s|/[^/]*$||' -sed_basename='s|^.*/||' - -# Sed substitution that helps us do robust quoting. It backslashifies -# metacharacters that are still active within double-quoted strings. -sed_quote_subst='s|\([`"$\\]\)|\\\1|g' - -# Same as above, but do not quote variable references. -sed_double_quote_subst='s/\(["`\\]\)/\\\1/g' - -# Sed substitution that turns a string into a regex matching for the -# string literally. -sed_make_literal_regex='s|[].[^$\\*\/]|\\&|g' - -# Sed substitution that converts a w32 file name or path -# that contains forward slashes, into one that contains -# (escaped) backslashes. A very naive implementation. -sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g' - -# Re-'\' parameter expansions in output of sed_double_quote_subst that -# were '\'-ed in input to the same. If an odd number of '\' preceded a -# '$' in input to sed_double_quote_subst, that '$' was protected from -# expansion. Since each input '\' is now two '\'s, look for any number -# of runs of four '\'s followed by two '\'s and then a '$'. '\' that '$'. -_G_bs='\\' -_G_bs2='\\\\' -_G_bs4='\\\\\\\\' -_G_dollar='\$' -sed_double_backslash="\ - s/$_G_bs4/&\\ -/g - s/^$_G_bs2$_G_dollar/$_G_bs&/ - s/\\([^$_G_bs]\\)$_G_bs2$_G_dollar/\\1$_G_bs2$_G_bs$_G_dollar/g - s/\n//g" - - -## ----------------- ## -## Global variables. ## -## ----------------- ## - -# Except for the global variables explicitly listed below, the following -# functions in the '^func_' namespace, and the '^require_' namespace -# variables initialised in the 'Resource management' section, sourcing -# this file will not pollute your global namespace with anything -# else. There's no portable way to scope variables in Bourne shell -# though, so actually running these functions will sometimes place -# results into a variable named after the function, and often use -# temporary variables in the '^_G_' namespace. If you are careful to -# avoid using those namespaces casually in your sourcing script, things -# should continue to work as you expect. And, of course, you can freely -# overwrite any of the functions or variables defined here before -# calling anything to customize them. - -EXIT_SUCCESS=0 -EXIT_FAILURE=1 -EXIT_MISMATCH=63 # $? = 63 is used to indicate version mismatch to missing. -EXIT_SKIP=77 # $? = 77 is used to indicate a skipped test to automake. - -# Allow overriding, eg assuming that you follow the convention of -# putting '$debug_cmd' at the start of all your functions, you can get -# bash to show function call trace with: -# -# debug_cmd='eval echo "${FUNCNAME[0]} $*" >&2' bash your-script-name -debug_cmd=${debug_cmd-":"} -exit_cmd=: - -# By convention, finish your script with: -# -# exit $exit_status -# -# so that you can set exit_status to non-zero if you want to indicate -# something went wrong during execution without actually bailing out at -# the point of failure. -exit_status=$EXIT_SUCCESS - -# Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh -# is ksh but when the shell is invoked as "sh" and the current value of -# the _XPG environment variable is not equal to 1 (one), the special -# positional parameter $0, within a function call, is the name of the -# function. -progpath=$0 - -# The name of this program. -progname=`$bs_echo "$progpath" |$SED "$sed_basename"` - -# Make sure we have an absolute progpath for reexecution: -case $progpath in - [\\/]*|[A-Za-z]:\\*) ;; - *[\\/]*) - progdir=`$bs_echo "$progpath" |$SED "$sed_dirname"` - progdir=`cd "$progdir" && pwd` - progpath=$progdir/$progname - ;; - *) - _G_IFS=$IFS - IFS=${PATH_SEPARATOR-:} - for progdir in $PATH; do - IFS=$_G_IFS - test -x "$progdir/$progname" && break - done - IFS=$_G_IFS - test -n "$progdir" || progdir=`pwd` - progpath=$progdir/$progname - ;; -esac - - -## ----------------- ## -## Standard options. ## -## ----------------- ## - -# The following options affect the operation of the functions defined -# below, and should be set appropriately depending on run-time para- -# meters passed on the command line. - -opt_dry_run=false -opt_quiet=false -opt_verbose=false - -# Categories 'all' and 'none' are always available. Append any others -# you will pass as the first argument to func_warning from your own -# code. -warning_categories= - -# By default, display warnings according to 'opt_warning_types'. Set -# 'warning_func' to ':' to elide all warnings, or func_fatal_error to -# treat the next displayed warning as a fatal error. -warning_func=func_warn_and_continue - -# Set to 'all' to display all warnings, 'none' to suppress all -# warnings, or a space delimited list of some subset of -# 'warning_categories' to display only the listed warnings. -opt_warning_types=all - - -## -------------------- ## -## Resource management. ## -## -------------------- ## - -# This section contains definitions for functions that each ensure a -# particular resource (a file, or a non-empty configuration variable for -# example) is available, and if appropriate to extract default values -# from pertinent package files. Call them using their associated -# 'require_*' variable to ensure that they are executed, at most, once. -# -# It's entirely deliberate that calling these functions can set -# variables that don't obey the namespace limitations obeyed by the rest -# of this file, in order that that they be as useful as possible to -# callers. - - -# require_term_colors -# ------------------- -# Allow display of bold text on terminals that support it. -require_term_colors=func_require_term_colors -func_require_term_colors () -{ - $debug_cmd - - test -t 1 && { - # COLORTERM and USE_ANSI_COLORS environment variables take - # precedence, because most terminfo databases neglect to describe - # whether color sequences are supported. - test -n "${COLORTERM+set}" && : ${USE_ANSI_COLORS="1"} - - if test 1 = "$USE_ANSI_COLORS"; then - # Standard ANSI escape sequences - tc_reset='' - tc_bold=''; tc_standout='' - tc_red=''; tc_green='' - tc_blue=''; tc_cyan='' - else - # Otherwise trust the terminfo database after all. - test -n "`tput sgr0 2>/dev/null`" && { - tc_reset=`tput sgr0` - test -n "`tput bold 2>/dev/null`" && tc_bold=`tput bold` - tc_standout=$tc_bold - test -n "`tput smso 2>/dev/null`" && tc_standout=`tput smso` - test -n "`tput setaf 1 2>/dev/null`" && tc_red=`tput setaf 1` - test -n "`tput setaf 2 2>/dev/null`" && tc_green=`tput setaf 2` - test -n "`tput setaf 4 2>/dev/null`" && tc_blue=`tput setaf 4` - test -n "`tput setaf 5 2>/dev/null`" && tc_cyan=`tput setaf 5` - } - fi - } - - require_term_colors=: -} - - -## ----------------- ## -## Function library. ## -## ----------------- ## - -# This section contains a variety of useful functions to call in your -# scripts. Take note of the portable wrappers for features provided by -# some modern shells, which will fall back to slower equivalents on -# less featureful shells. - - -# func_append VAR VALUE -# --------------------- -# Append VALUE onto the existing contents of VAR. - - # We should try to minimise forks, especially on Windows where they are - # unreasonably slow, so skip the feature probes when bash or zsh are - # being used: - if test set = "${BASH_VERSION+set}${ZSH_VERSION+set}"; then - : ${_G_HAVE_ARITH_OP="yes"} - : ${_G_HAVE_XSI_OPS="yes"} - # The += operator was introduced in bash 3.1 - case $BASH_VERSION in - [12].* | 3.0 | 3.0*) ;; - *) - : ${_G_HAVE_PLUSEQ_OP="yes"} - ;; - esac - fi - - # _G_HAVE_PLUSEQ_OP - # Can be empty, in which case the shell is probed, "yes" if += is - # useable or anything else if it does not work. - test -z "$_G_HAVE_PLUSEQ_OP" \ - && (eval 'x=a; x+=" b"; test "a b" = "$x"') 2>/dev/null \ - && _G_HAVE_PLUSEQ_OP=yes - -if test yes = "$_G_HAVE_PLUSEQ_OP" -then - # This is an XSI compatible shell, allowing a faster implementation... - eval 'func_append () - { - $debug_cmd - - eval "$1+=\$2" - }' -else - # ...otherwise fall back to using expr, which is often a shell builtin. - func_append () - { - $debug_cmd - - eval "$1=\$$1\$2" - } -fi - - -# func_append_quoted VAR VALUE -# ---------------------------- -# Quote VALUE and append to the end of shell variable VAR, separated -# by a space. -if test yes = "$_G_HAVE_PLUSEQ_OP"; then - eval 'func_append_quoted () - { - $debug_cmd - - func_quote_for_eval "$2" - eval "$1+=\\ \$func_quote_for_eval_result" - }' -else - func_append_quoted () - { - $debug_cmd - - func_quote_for_eval "$2" - eval "$1=\$$1\\ \$func_quote_for_eval_result" - } -fi - - -# func_append_uniq VAR VALUE -# -------------------------- -# Append unique VALUE onto the existing contents of VAR, assuming -# entries are delimited by the first character of VALUE. For example: -# -# func_append_uniq options " --another-option option-argument" -# -# will only append to $options if " --another-option option-argument " -# is not already present somewhere in $options already (note spaces at -# each end implied by leading space in second argument). -func_append_uniq () -{ - $debug_cmd - - eval _G_current_value='`$bs_echo $'$1'`' - _G_delim=`expr "$2" : '\(.\)'` - - case $_G_delim$_G_current_value$_G_delim in - *"$2$_G_delim"*) ;; - *) func_append "$@" ;; - esac -} - - -# func_arith TERM... -# ------------------ -# Set func_arith_result to the result of evaluating TERMs. - test -z "$_G_HAVE_ARITH_OP" \ - && (eval 'test 2 = $(( 1 + 1 ))') 2>/dev/null \ - && _G_HAVE_ARITH_OP=yes - -if test yes = "$_G_HAVE_ARITH_OP"; then - eval 'func_arith () - { - $debug_cmd - - func_arith_result=$(( $* )) - }' -else - func_arith () - { - $debug_cmd - - func_arith_result=`expr "$@"` - } -fi - - -# func_basename FILE -# ------------------ -# Set func_basename_result to FILE with everything up to and including -# the last / stripped. -if test yes = "$_G_HAVE_XSI_OPS"; then - # If this shell supports suffix pattern removal, then use it to avoid - # forking. Hide the definitions single quotes in case the shell chokes - # on unsupported syntax... - _b='func_basename_result=${1##*/}' - _d='case $1 in - */*) func_dirname_result=${1%/*}$2 ;; - * ) func_dirname_result=$3 ;; - esac' - -else - # ...otherwise fall back to using sed. - _b='func_basename_result=`$ECHO "$1" |$SED "$sed_basename"`' - _d='func_dirname_result=`$ECHO "$1" |$SED "$sed_dirname"` - if test "X$func_dirname_result" = "X$1"; then - func_dirname_result=$3 - else - func_append func_dirname_result "$2" - fi' -fi - -eval 'func_basename () -{ - $debug_cmd - - '"$_b"' -}' - - -# func_dirname FILE APPEND NONDIR_REPLACEMENT -# ------------------------------------------- -# Compute the dirname of FILE. If nonempty, add APPEND to the result, -# otherwise set result to NONDIR_REPLACEMENT. -eval 'func_dirname () -{ - $debug_cmd - - '"$_d"' -}' - - -# func_dirname_and_basename FILE APPEND NONDIR_REPLACEMENT -# -------------------------------------------------------- -# Perform func_basename and func_dirname in a single function -# call: -# dirname: Compute the dirname of FILE. If nonempty, -# add APPEND to the result, otherwise set result -# to NONDIR_REPLACEMENT. -# value returned in "$func_dirname_result" -# basename: Compute filename of FILE. -# value retuned in "$func_basename_result" -# For efficiency, we do not delegate to the functions above but instead -# duplicate the functionality here. -eval 'func_dirname_and_basename () -{ - $debug_cmd - - '"$_b"' - '"$_d"' -}' - - -# func_echo ARG... -# ---------------- -# Echo program name prefixed message. -func_echo () -{ - $debug_cmd - - _G_message=$* - - func_echo_IFS=$IFS - IFS=$nl - for _G_line in $_G_message; do - IFS=$func_echo_IFS - $bs_echo "$progname: $_G_line" - done - IFS=$func_echo_IFS -} - - -# func_echo_all ARG... -# -------------------- -# Invoke $ECHO with all args, space-separated. -func_echo_all () -{ - $ECHO "$*" -} - - -# func_echo_infix_1 INFIX ARG... -# ------------------------------ -# Echo program name, followed by INFIX on the first line, with any -# additional lines not showing INFIX. -func_echo_infix_1 () -{ - $debug_cmd - - $require_term_colors - - _G_infix=$1; shift - _G_indent=$_G_infix - _G_prefix="$progname: $_G_infix: " - _G_message=$* - - # Strip color escape sequences before counting printable length - for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan" - do - test -n "$_G_tc" && { - _G_esc_tc=`$bs_echo "$_G_tc" | sed "$sed_make_literal_regex"` - _G_indent=`$bs_echo "$_G_indent" | sed "s|$_G_esc_tc||g"` - } - done - _G_indent="$progname: "`echo "$_G_indent" | sed 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes - - func_echo_infix_1_IFS=$IFS - IFS=$nl - for _G_line in $_G_message; do - IFS=$func_echo_infix_1_IFS - $bs_echo "$_G_prefix$tc_bold$_G_line$tc_reset" >&2 - _G_prefix=$_G_indent - done - IFS=$func_echo_infix_1_IFS -} - - -# func_error ARG... -# ----------------- -# Echo program name prefixed message to standard error. -func_error () -{ - $debug_cmd - - $require_term_colors - - func_echo_infix_1 " $tc_standout${tc_red}error$tc_reset" "$*" >&2 -} - - -# func_fatal_error ARG... -# ----------------------- -# Echo program name prefixed message to standard error, and exit. -func_fatal_error () -{ - $debug_cmd - - func_error "$*" - exit $EXIT_FAILURE -} - - -# func_grep EXPRESSION FILENAME -# ----------------------------- -# Check whether EXPRESSION matches any line of FILENAME, without output. -func_grep () -{ - $debug_cmd - - $GREP "$1" "$2" >/dev/null 2>&1 -} - - -# func_len STRING -# --------------- -# Set func_len_result to the length of STRING. STRING may not -# start with a hyphen. - test -z "$_G_HAVE_XSI_OPS" \ - && (eval 'x=a/b/c; - test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ - && _G_HAVE_XSI_OPS=yes - -if test yes = "$_G_HAVE_XSI_OPS"; then - eval 'func_len () - { - $debug_cmd - - func_len_result=${#1} - }' -else - func_len () - { - $debug_cmd - - func_len_result=`expr "$1" : ".*" 2>/dev/null || echo $max_cmd_len` - } -fi - - -# func_mkdir_p DIRECTORY-PATH -# --------------------------- -# Make sure the entire path to DIRECTORY-PATH is available. -func_mkdir_p () -{ - $debug_cmd - - _G_directory_path=$1 - _G_dir_list= - - if test -n "$_G_directory_path" && test : != "$opt_dry_run"; then - - # Protect directory names starting with '-' - case $_G_directory_path in - -*) _G_directory_path=./$_G_directory_path ;; - esac - - # While some portion of DIR does not yet exist... - while test ! -d "$_G_directory_path"; do - # ...make a list in topmost first order. Use a colon delimited - # list incase some portion of path contains whitespace. - _G_dir_list=$_G_directory_path:$_G_dir_list - - # If the last portion added has no slash in it, the list is done - case $_G_directory_path in */*) ;; *) break ;; esac - - # ...otherwise throw away the child directory and loop - _G_directory_path=`$ECHO "$_G_directory_path" | $SED -e "$sed_dirname"` - done - _G_dir_list=`$ECHO "$_G_dir_list" | $SED 's|:*$||'` - - func_mkdir_p_IFS=$IFS; IFS=: - for _G_dir in $_G_dir_list; do - IFS=$func_mkdir_p_IFS - # mkdir can fail with a 'File exist' error if two processes - # try to create one of the directories concurrently. Don't - # stop in that case! - $MKDIR "$_G_dir" 2>/dev/null || : - done - IFS=$func_mkdir_p_IFS - - # Bail out if we (or some other process) failed to create a directory. - test -d "$_G_directory_path" || \ - func_fatal_error "Failed to create '$1'" - fi -} - - -# func_mktempdir [BASENAME] -# ------------------------- -# Make a temporary directory that won't clash with other running -# libtool processes, and avoids race conditions if possible. If -# given, BASENAME is the basename for that directory. -func_mktempdir () -{ - $debug_cmd - - _G_template=${TMPDIR-/tmp}/${1-$progname} - - if test : = "$opt_dry_run"; then - # Return a directory name, but don't create it in dry-run mode - _G_tmpdir=$_G_template-$$ - else - - # If mktemp works, use that first and foremost - _G_tmpdir=`mktemp -d "$_G_template-XXXXXXXX" 2>/dev/null` - - if test ! -d "$_G_tmpdir"; then - # Failing that, at least try and use $RANDOM to avoid a race - _G_tmpdir=$_G_template-${RANDOM-0}$$ - - func_mktempdir_umask=`umask` - umask 0077 - $MKDIR "$_G_tmpdir" - umask $func_mktempdir_umask - fi - - # If we're not in dry-run mode, bomb out on failure - test -d "$_G_tmpdir" || \ - func_fatal_error "cannot create temporary directory '$_G_tmpdir'" - fi - - $ECHO "$_G_tmpdir" -} - - -# func_normal_abspath PATH -# ------------------------ -# Remove doubled-up and trailing slashes, "." path components, -# and cancel out any ".." path components in PATH after making -# it an absolute path. -func_normal_abspath () -{ - $debug_cmd - - # These SED scripts presuppose an absolute path with a trailing slash. - _G_pathcar='s|^/\([^/]*\).*$|\1|' - _G_pathcdr='s|^/[^/]*||' - _G_removedotparts=':dotsl - s|/\./|/|g - t dotsl - s|/\.$|/|' - _G_collapseslashes='s|/\{1,\}|/|g' - _G_finalslash='s|/*$|/|' - - # Start from root dir and reassemble the path. - func_normal_abspath_result= - func_normal_abspath_tpath=$1 - func_normal_abspath_altnamespace= - case $func_normal_abspath_tpath in - "") - # Empty path, that just means $cwd. - func_stripname '' '/' "`pwd`" - func_normal_abspath_result=$func_stripname_result - return - ;; - # The next three entries are used to spot a run of precisely - # two leading slashes without using negated character classes; - # we take advantage of case's first-match behaviour. - ///*) - # Unusual form of absolute path, do nothing. - ;; - //*) - # Not necessarily an ordinary path; POSIX reserves leading '//' - # and for example Cygwin uses it to access remote file shares - # over CIFS/SMB, so we conserve a leading double slash if found. - func_normal_abspath_altnamespace=/ - ;; - /*) - # Absolute path, do nothing. - ;; - *) - # Relative path, prepend $cwd. - func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath - ;; - esac - - # Cancel out all the simple stuff to save iterations. We also want - # the path to end with a slash for ease of parsing, so make sure - # there is one (and only one) here. - func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ - -e "$_G_removedotparts" -e "$_G_collapseslashes" -e "$_G_finalslash"` - while :; do - # Processed it all yet? - if test / = "$func_normal_abspath_tpath"; then - # If we ascended to the root using ".." the result may be empty now. - if test -z "$func_normal_abspath_result"; then - func_normal_abspath_result=/ - fi - break - fi - func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \ - -e "$_G_pathcar"` - func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ - -e "$_G_pathcdr"` - # Figure out what to do with it - case $func_normal_abspath_tcomponent in - "") - # Trailing empty path component, ignore it. - ;; - ..) - # Parent dir; strip last assembled component from result. - func_dirname "$func_normal_abspath_result" - func_normal_abspath_result=$func_dirname_result - ;; - *) - # Actual path component, append it. - func_append func_normal_abspath_result "/$func_normal_abspath_tcomponent" - ;; - esac - done - # Restore leading double-slash if one was found on entry. - func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result -} - - -# func_notquiet ARG... -# -------------------- -# Echo program name prefixed message only when not in quiet mode. -func_notquiet () -{ - $debug_cmd - - $opt_quiet || func_echo ${1+"$@"} - - # A bug in bash halts the script if the last line of a function - # fails when set -e is in force, so we need another command to - # work around that: - : -} - - -# func_relative_path SRCDIR DSTDIR -# -------------------------------- -# Set func_relative_path_result to the relative path from SRCDIR to DSTDIR. -func_relative_path () -{ - $debug_cmd - - func_relative_path_result= - func_normal_abspath "$1" - func_relative_path_tlibdir=$func_normal_abspath_result - func_normal_abspath "$2" - func_relative_path_tbindir=$func_normal_abspath_result - - # Ascend the tree starting from libdir - while :; do - # check if we have found a prefix of bindir - case $func_relative_path_tbindir in - $func_relative_path_tlibdir) - # found an exact match - func_relative_path_tcancelled= - break - ;; - $func_relative_path_tlibdir*) - # found a matching prefix - func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir" - func_relative_path_tcancelled=$func_stripname_result - if test -z "$func_relative_path_result"; then - func_relative_path_result=. - fi - break - ;; - *) - func_dirname $func_relative_path_tlibdir - func_relative_path_tlibdir=$func_dirname_result - if test -z "$func_relative_path_tlibdir"; then - # Have to descend all the way to the root! - func_relative_path_result=../$func_relative_path_result - func_relative_path_tcancelled=$func_relative_path_tbindir - break - fi - func_relative_path_result=../$func_relative_path_result - ;; - esac - done - - # Now calculate path; take care to avoid doubling-up slashes. - func_stripname '' '/' "$func_relative_path_result" - func_relative_path_result=$func_stripname_result - func_stripname '/' '/' "$func_relative_path_tcancelled" - if test -n "$func_stripname_result"; then - func_append func_relative_path_result "/$func_stripname_result" - fi - - # Normalisation. If bindir is libdir, return '.' else relative path. - if test -n "$func_relative_path_result"; then - func_stripname './' '' "$func_relative_path_result" - func_relative_path_result=$func_stripname_result - fi - - test -n "$func_relative_path_result" || func_relative_path_result=. - - : -} - - -# func_quote_for_eval ARG... -# -------------------------- -# Aesthetically quote ARGs to be evaled later. -# This function returns two values: -# i) func_quote_for_eval_result -# double-quoted, suitable for a subsequent eval -# ii) func_quote_for_eval_unquoted_result -# has all characters that are still active within double -# quotes backslashified. -func_quote_for_eval () -{ - $debug_cmd - - func_quote_for_eval_unquoted_result= - func_quote_for_eval_result= - while test 0 -lt $#; do - case $1 in - *[\\\`\"\$]*) - _G_unquoted_arg=`printf '%s\n' "$1" |$SED "$sed_quote_subst"` ;; - *) - _G_unquoted_arg=$1 ;; - esac - if test -n "$func_quote_for_eval_unquoted_result"; then - func_append func_quote_for_eval_unquoted_result " $_G_unquoted_arg" - else - func_append func_quote_for_eval_unquoted_result "$_G_unquoted_arg" - fi - - case $_G_unquoted_arg in - # Double-quote args containing shell metacharacters to delay - # word splitting, command substitution and variable expansion - # for a subsequent eval. - # Many Bourne shells cannot handle close brackets correctly - # in scan sets, so we specify it separately. - *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") - _G_quoted_arg=\"$_G_unquoted_arg\" - ;; - *) - _G_quoted_arg=$_G_unquoted_arg - ;; - esac - - if test -n "$func_quote_for_eval_result"; then - func_append func_quote_for_eval_result " $_G_quoted_arg" - else - func_append func_quote_for_eval_result "$_G_quoted_arg" - fi - shift - done -} - - -# func_quote_for_expand ARG -# ------------------------- -# Aesthetically quote ARG to be evaled later; same as above, -# but do not quote variable references. -func_quote_for_expand () -{ - $debug_cmd - - case $1 in - *[\\\`\"]*) - _G_arg=`$ECHO "$1" | $SED \ - -e "$sed_double_quote_subst" -e "$sed_double_backslash"` ;; - *) - _G_arg=$1 ;; - esac - - case $_G_arg in - # Double-quote args containing shell metacharacters to delay - # word splitting and command substitution for a subsequent eval. - # Many Bourne shells cannot handle close brackets correctly - # in scan sets, so we specify it separately. - *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") - _G_arg=\"$_G_arg\" - ;; - esac - - func_quote_for_expand_result=$_G_arg -} - - -# func_stripname PREFIX SUFFIX NAME -# --------------------------------- -# strip PREFIX and SUFFIX from NAME, and store in func_stripname_result. -# PREFIX and SUFFIX must not contain globbing or regex special -# characters, hashes, percent signs, but SUFFIX may contain a leading -# dot (in which case that matches only a dot). -if test yes = "$_G_HAVE_XSI_OPS"; then - eval 'func_stripname () - { - $debug_cmd - - # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are - # positional parameters, so assign one to ordinary variable first. - func_stripname_result=$3 - func_stripname_result=${func_stripname_result#"$1"} - func_stripname_result=${func_stripname_result%"$2"} - }' -else - func_stripname () - { - $debug_cmd - - case $2 in - .*) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%\\\\$2\$%%"`;; - *) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%$2\$%%"`;; - esac - } -fi - - -# func_show_eval CMD [FAIL_EXP] -# ----------------------------- -# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is -# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP -# is given, then evaluate it. -func_show_eval () -{ - $debug_cmd - - _G_cmd=$1 - _G_fail_exp=${2-':'} - - func_quote_for_expand "$_G_cmd" - eval "func_notquiet $func_quote_for_expand_result" - - $opt_dry_run || { - eval "$_G_cmd" - _G_status=$? - if test 0 -ne "$_G_status"; then - eval "(exit $_G_status); $_G_fail_exp" - fi - } -} - - -# func_show_eval_locale CMD [FAIL_EXP] -# ------------------------------------ -# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is -# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP -# is given, then evaluate it. Use the saved locale for evaluation. -func_show_eval_locale () -{ - $debug_cmd - - _G_cmd=$1 - _G_fail_exp=${2-':'} - - $opt_quiet || { - func_quote_for_expand "$_G_cmd" - eval "func_echo $func_quote_for_expand_result" - } - - $opt_dry_run || { - eval "$_G_user_locale - $_G_cmd" - _G_status=$? - eval "$_G_safe_locale" - if test 0 -ne "$_G_status"; then - eval "(exit $_G_status); $_G_fail_exp" - fi - } -} - - -# func_tr_sh -# ---------- -# Turn $1 into a string suitable for a shell variable name. -# Result is stored in $func_tr_sh_result. All characters -# not in the set a-zA-Z0-9_ are replaced with '_'. Further, -# if $1 begins with a digit, a '_' is prepended as well. -func_tr_sh () -{ - $debug_cmd - - case $1 in - [0-9]* | *[!a-zA-Z0-9_]*) - func_tr_sh_result=`$ECHO "$1" | $SED -e 's/^\([0-9]\)/_\1/' -e 's/[^a-zA-Z0-9_]/_/g'` - ;; - * ) - func_tr_sh_result=$1 - ;; - esac -} - - -# func_verbose ARG... -# ------------------- -# Echo program name prefixed message in verbose mode only. -func_verbose () -{ - $debug_cmd - - $opt_verbose && func_echo "$*" - - : -} - - -# func_warn_and_continue ARG... -# ----------------------------- -# Echo program name prefixed warning message to standard error. -func_warn_and_continue () -{ - $debug_cmd - - $require_term_colors - - func_echo_infix_1 "${tc_red}warning$tc_reset" "$*" >&2 -} - - -# func_warning CATEGORY ARG... -# ---------------------------- -# Echo program name prefixed warning message to standard error. Warning -# messages can be filtered according to CATEGORY, where this function -# elides messages where CATEGORY is not listed in the global variable -# 'opt_warning_types'. -func_warning () -{ - $debug_cmd - - # CATEGORY must be in the warning_categories list! - case " $warning_categories " in - *" $1 "*) ;; - *) func_internal_error "invalid warning category '$1'" ;; - esac - - _G_category=$1 - shift - - case " $opt_warning_types " in - *" $_G_category "*) $warning_func ${1+"$@"} ;; - esac -} - - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" -# time-stamp-time-zone: "UTC" -# End: -#! /bin/sh - -# Set a version string for this script. -scriptversion=2012-10-21.11; # UTC - -# A portable, pluggable option parser for Bourne shell. -# Written by Gary V. Vaughan, 2010 - -# Copyright (C) 2010-2013 Free Software Foundation, Inc. -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Please report bugs or propose patches to gary@gnu.org. - - -## ------ ## -## Usage. ## -## ------ ## - -# This file is a library for parsing options in your shell scripts along -# with assorted other useful supporting features that you can make use -# of too. -# -# For the simplest scripts you might need only: -# -# #!/bin/sh -# . relative/path/to/funclib.sh -# . relative/path/to/options-parser -# scriptversion=1.0 -# func_options ${1+"$@"} -# eval set dummy "$func_options_result"; shift -# ...rest of your script... -# -# In order for the '--version' option to work, you will need to have a -# suitably formatted comment like the one at the top of this file -# starting with '# Written by ' and ending with '# warranty; '. -# -# For '-h' and '--help' to work, you will also need a one line -# description of your script's purpose in a comment directly above the -# '# Written by ' line, like the one at the top of this file. -# -# The default options also support '--debug', which will turn on shell -# execution tracing (see the comment above debug_cmd below for another -# use), and '--verbose' and the func_verbose function to allow your script -# to display verbose messages only when your user has specified -# '--verbose'. -# -# After sourcing this file, you can plug processing for additional -# options by amending the variables from the 'Configuration' section -# below, and following the instructions in the 'Option parsing' -# section further down. - -## -------------- ## -## Configuration. ## -## -------------- ## - -# You should override these variables in your script after sourcing this -# file so that they reflect the customisations you have added to the -# option parser. - -# The usage line for option parsing errors and the start of '-h' and -# '--help' output messages. You can embed shell variables for delayed -# expansion at the time the message is displayed, but you will need to -# quote other shell meta-characters carefully to prevent them being -# expanded when the contents are evaled. -usage='$progpath [OPTION]...' - -# Short help message in response to '-h' and '--help'. Add to this or -# override it after sourcing this library to reflect the full set of -# options your script accepts. -usage_message="\ - --debug enable verbose shell tracing - -W, --warnings=CATEGORY - report the warnings falling in CATEGORY [all] - -v, --verbose verbosely report processing - --version print version information and exit - -h, --help print short or long help message and exit -" - -# Additional text appended to 'usage_message' in response to '--help'. -long_help_message=" -Warning categories include: - 'all' show all warnings - 'none' turn off all the warnings - 'error' warnings are treated as fatal errors" - -# Help message printed before fatal option parsing errors. -fatal_help="Try '\$progname --help' for more information." - - - -## ------------------------- ## -## Hook function management. ## -## ------------------------- ## - -# This section contains functions for adding, removing, and running hooks -# to the main code. A hook is just a named list of of function, that can -# be run in order later on. - -# func_hookable FUNC_NAME -# ----------------------- -# Declare that FUNC_NAME will run hooks added with -# 'func_add_hook FUNC_NAME ...'. -func_hookable () -{ - $debug_cmd - - func_append hookable_fns " $1" -} - - -# func_add_hook FUNC_NAME HOOK_FUNC -# --------------------------------- -# Request that FUNC_NAME call HOOK_FUNC before it returns. FUNC_NAME must -# first have been declared "hookable" by a call to 'func_hookable'. -func_add_hook () -{ - $debug_cmd - - case " $hookable_fns " in - *" $1 "*) ;; - *) func_fatal_error "'$1' does not accept hook functions." ;; - esac - - eval func_append ${1}_hooks '" $2"' -} - - -# func_remove_hook FUNC_NAME HOOK_FUNC -# ------------------------------------ -# Remove HOOK_FUNC from the list of functions called by FUNC_NAME. -func_remove_hook () -{ - $debug_cmd - - eval ${1}_hooks='`$bs_echo "\$'$1'_hooks" |$SED "s| '$2'||"`' -} - - -# func_run_hooks FUNC_NAME [ARG]... -# --------------------------------- -# Run all hook functions registered to FUNC_NAME. -# It is assumed that the list of hook functions contains nothing more -# than a whitespace-delimited list of legal shell function names, and -# no effort is wasted trying to catch shell meta-characters or preserve -# whitespace. -func_run_hooks () -{ - $debug_cmd - - case " $hookable_fns " in - *" $1 "*) ;; - *) func_fatal_error "'$1' does not support hook funcions.n" ;; - esac - - eval _G_hook_fns=\$$1_hooks; shift - - for _G_hook in $_G_hook_fns; do - eval $_G_hook '"$@"' - - # store returned options list back into positional - # parameters for next 'cmd' execution. - eval _G_hook_result=\$${_G_hook}_result - eval set dummy "$_G_hook_result"; shift - done - - func_quote_for_eval ${1+"$@"} - func_run_hooks_result=$func_quote_for_eval_result -} - - - -## --------------- ## -## Option parsing. ## -## --------------- ## - -# In order to add your own option parsing hooks, you must accept the -# full positional parameter list in your hook function, remove any -# options that you action, and then pass back the remaining unprocessed -# options in '_result', escaped suitably for -# 'eval'. Like this: -# -# my_options_prep () -# { -# $debug_cmd -# -# # Extend the existing usage message. -# usage_message=$usage_message' -# -s, --silent don'\''t print informational messages -# ' -# -# func_quote_for_eval ${1+"$@"} -# my_options_prep_result=$func_quote_for_eval_result -# } -# func_add_hook func_options_prep my_options_prep -# -# -# my_silent_option () -# { -# $debug_cmd -# -# # Note that for efficiency, we parse as many options as we can -# # recognise in a loop before passing the remainder back to the -# # caller on the first unrecognised argument we encounter. -# while test $# -gt 0; do -# opt=$1; shift -# case $opt in -# --silent|-s) opt_silent=: ;; -# # Separate non-argument short options: -# -s*) func_split_short_opt "$_G_opt" -# set dummy "$func_split_short_opt_name" \ -# "-$func_split_short_opt_arg" ${1+"$@"} -# shift -# ;; -# *) set dummy "$_G_opt" "$*"; shift; break ;; -# esac -# done -# -# func_quote_for_eval ${1+"$@"} -# my_silent_option_result=$func_quote_for_eval_result -# } -# func_add_hook func_parse_options my_silent_option -# -# -# my_option_validation () -# { -# $debug_cmd -# -# $opt_silent && $opt_verbose && func_fatal_help "\ -# '--silent' and '--verbose' options are mutually exclusive." -# -# func_quote_for_eval ${1+"$@"} -# my_option_validation_result=$func_quote_for_eval_result -# } -# func_add_hook func_validate_options my_option_validation -# -# You'll alse need to manually amend $usage_message to reflect the extra -# options you parse. It's preferable to append if you can, so that -# multiple option parsing hooks can be added safely. - - -# func_options [ARG]... -# --------------------- -# All the functions called inside func_options are hookable. See the -# individual implementations for details. -func_hookable func_options -func_options () -{ - $debug_cmd - - func_options_prep ${1+"$@"} - eval func_parse_options \ - ${func_options_prep_result+"$func_options_prep_result"} - eval func_validate_options \ - ${func_parse_options_result+"$func_parse_options_result"} - - eval func_run_hooks func_options \ - ${func_validate_options_result+"$func_validate_options_result"} - - # save modified positional parameters for caller - func_options_result=$func_run_hooks_result -} - - -# func_options_prep [ARG]... -# -------------------------- -# All initialisations required before starting the option parse loop. -# Note that when calling hook functions, we pass through the list of -# positional parameters. If a hook function modifies that list, and -# needs to propogate that back to rest of this script, then the complete -# modified list must be put in 'func_run_hooks_result' before -# returning. -func_hookable func_options_prep -func_options_prep () -{ - $debug_cmd - - # Option defaults: - opt_verbose=false - opt_warning_types= - - func_run_hooks func_options_prep ${1+"$@"} - - # save modified positional parameters for caller - func_options_prep_result=$func_run_hooks_result -} - - -# func_parse_options [ARG]... -# --------------------------- -# The main option parsing loop. -func_hookable func_parse_options -func_parse_options () -{ - $debug_cmd - - func_parse_options_result= - - # this just eases exit handling - while test $# -gt 0; do - # Defer to hook functions for initial option parsing, so they - # get priority in the event of reusing an option name. - func_run_hooks func_parse_options ${1+"$@"} - - # Adjust func_parse_options positional parameters to match - eval set dummy "$func_run_hooks_result"; shift - - # Break out of the loop if we already parsed every option. - test $# -gt 0 || break - - _G_opt=$1 - shift - case $_G_opt in - --debug|-x) debug_cmd='set -x' - func_echo "enabling shell trace mode" - $debug_cmd - ;; - - --no-warnings|--no-warning|--no-warn) - set dummy --warnings none ${1+"$@"} - shift - ;; - - --warnings|--warning|-W) - test $# = 0 && func_missing_arg $_G_opt && break - case " $warning_categories $1" in - *" $1 "*) - # trailing space prevents matching last $1 above - func_append_uniq opt_warning_types " $1" - ;; - *all) - opt_warning_types=$warning_categories - ;; - *none) - opt_warning_types=none - warning_func=: - ;; - *error) - opt_warning_types=$warning_categories - warning_func=func_fatal_error - ;; - *) - func_fatal_error \ - "unsupported warning category: '$1'" - ;; - esac - shift - ;; - - --verbose|-v) opt_verbose=: ;; - --version) func_version ;; - -\?|-h) func_usage ;; - --help) func_help ;; - - # Separate optargs to long options (plugins may need this): - --*=*) func_split_equals "$_G_opt" - set dummy "$func_split_equals_lhs" \ - "$func_split_equals_rhs" ${1+"$@"} - shift - ;; - - # Separate optargs to short options: - -W*) - func_split_short_opt "$_G_opt" - set dummy "$func_split_short_opt_name" \ - "$func_split_short_opt_arg" ${1+"$@"} - shift - ;; - - # Separate non-argument short options: - -\?*|-h*|-v*|-x*) - func_split_short_opt "$_G_opt" - set dummy "$func_split_short_opt_name" \ - "-$func_split_short_opt_arg" ${1+"$@"} - shift - ;; - - --) break ;; - -*) func_fatal_help "unrecognised option: '$_G_opt'" ;; - *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; - esac - done - - # save modified positional parameters for caller - func_quote_for_eval ${1+"$@"} - func_parse_options_result=$func_quote_for_eval_result -} - - -# func_validate_options [ARG]... -# ------------------------------ -# Perform any sanity checks on option settings and/or unconsumed -# arguments. -func_hookable func_validate_options -func_validate_options () -{ - $debug_cmd - - # Display all warnings if -W was not given. - test -n "$opt_warning_types" || opt_warning_types=" $warning_categories" - - func_run_hooks func_validate_options ${1+"$@"} - - # Bail if the options were screwed! - $exit_cmd $EXIT_FAILURE - - # save modified positional parameters for caller - func_validate_options_result=$func_run_hooks_result -} - - - -## ------------------## -## Helper functions. ## -## ------------------## - -# This section contains the helper functions used by the rest of the -# hookable option parser framework in ascii-betical order. - - -# func_fatal_help ARG... -# ---------------------- -# Echo program name prefixed message to standard error, followed by -# a help hint, and exit. -func_fatal_help () -{ - $debug_cmd - - eval \$bs_echo \""Usage: $usage"\" - eval \$bs_echo \""$fatal_help"\" - func_error ${1+"$@"} - exit $EXIT_FAILURE -} - - -# func_help -# --------- -# Echo long help message to standard output and exit. -func_help () -{ - $debug_cmd - - func_usage_message - $bs_echo "$long_help_message" - exit 0 -} - - -# func_missing_arg ARGNAME -# ------------------------ -# Echo program name prefixed message to standard error and set global -# exit_cmd. -func_missing_arg () -{ - $debug_cmd - - func_error "Missing argument for '$1'." - exit_cmd=exit -} - - -# func_split_equals STRING -# ------------------------ -# Set func_split_equals_lhs and func_split_equals_rhs shell variables after -# splitting STRING at the '=' sign. -test -z "$_G_HAVE_XSI_OPS" \ - && (eval 'x=a/b/c; - test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ - && _G_HAVE_XSI_OPS=yes - -if test yes = "$_G_HAVE_XSI_OPS" -then - # This is an XSI compatible shell, allowing a faster implementation... - eval 'func_split_equals () - { - $debug_cmd - - func_split_equals_lhs=${1%%=*} - func_split_equals_rhs=${1#*=} - test "x$func_split_equals_lhs" = "x$1" \ - && func_split_equals_rhs= - }' -else - # ...otherwise fall back to using expr, which is often a shell builtin. - func_split_equals () - { - $debug_cmd - - func_split_equals_lhs=`expr "x$1" : 'x\([^=]*\)'` - func_split_equals_rhs= - test "x$func_split_equals_lhs" = "x$1" \ - || func_split_equals_rhs=`expr "x$1" : 'x[^=]*=\(.*\)$'` - } -fi #func_split_equals - - -# func_split_short_opt SHORTOPT -# ----------------------------- -# Set func_split_short_opt_name and func_split_short_opt_arg shell -# variables after splitting SHORTOPT after the 2nd character. -if test yes = "$_G_HAVE_XSI_OPS" -then - # This is an XSI compatible shell, allowing a faster implementation... - eval 'func_split_short_opt () - { - $debug_cmd - - func_split_short_opt_arg=${1#??} - func_split_short_opt_name=${1%"$func_split_short_opt_arg"} - }' -else - # ...otherwise fall back to using expr, which is often a shell builtin. - func_split_short_opt () - { - $debug_cmd - - func_split_short_opt_name=`expr "x$1" : 'x-\(.\)'` - func_split_short_opt_arg=`expr "x$1" : 'x-.\(.*\)$'` - } -fi #func_split_short_opt - - -# func_usage -# ---------- -# Echo short help message to standard output and exit. -func_usage () -{ - $debug_cmd - - func_usage_message - $bs_echo "Run '$progname --help |${PAGER-more}' for full usage" - exit 0 -} - - -# func_usage_message -# ------------------ -# Echo short help message to standard output. -func_usage_message () -{ - $debug_cmd - - eval \$bs_echo \""Usage: $usage"\" - echo - $SED -n 's|^# || - /^Written by/{ - x;p;x - } - h - /^Written by/q' < "$progpath" - echo - eval \$bs_echo \""$usage_message"\" -} - - -# func_version -# ------------ -# Echo version message to standard output and exit. -func_version () -{ - $debug_cmd - - printf '%s\n' "$progname $scriptversion" - $SED -n '/^##/q - /(C)/!b go - :more - /\./!{ - N - s|\n# | | - b more - } - :go - /^# Written by /,/# warranty; / { - s|^# || - s|^# *$|| - s|\((C)\)[ 0-9,-]*[ ,-]\([1-9][0-9]* \)|\1 \2| - p - } - /^# Written by / { - s|^# || - p - } - /^warranty; /q' < "$progpath" - - exit $? -} - - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" -# time-stamp-time-zone: "UTC" -# End: -#! /bin/sh - -# Extract macro arguments from autotools input with GNU M4. -# Written by Gary V. Vaughan, 2010 -# -# Copyright (C) 2010-2013 Free Software Foundation, Inc. -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -# Make sure we've evaluated scripts we depend on. -test -z "$progpath" && . `echo "$0" |${SED-sed} 's|[^/]*$||'`/funclib.sh -test extract-trace = "$progname" && . `echo "$0" |${SED-sed} 's|[^/]*$||'`/options-parser - -# Set a version string. -scriptversion=2013-08-22.10; # UTC - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Please report bugs or propose patches to gary@gnu.org. - - -## ------ ## -## Usage. ## -## ------ ## - -# Run './extract-trace --help' for help with using this script from the -# command line. -# -# Or source first 'options-parser' and then this file into your own -# scripts in order to make use of the function and variable framework -# they define, and also to avoid the overhead of forking to run this -# script in its own process on every call. - - - -## ------------------## -## Helper functions. ## -## ------------------## - -# This section contains the helper functions used by the rest of -# 'extract-trace'. - - -# func_autoconf_configure MAYBE-CONFIGURE-FILE -# -------------------------------------------- -# Ensure that MAYBE-CONFIGURE-FILE is the name of a file in the current -# directory that contains an uncommented call to AC_INIT. -func_autoconf_configure () -{ - $debug_cmd - - _G_sed_no_comment=' - s|#.*$|| - s|^dnl .*$|| - s| dnl .*$||' - _G_ac_init= - - # If we were passed a genuine file, make sure it calls AC_INIT. - test -f "$1" \ - && _G_ac_init=`$SED "$_G_sed_no_comment" "$1" |grep AC_INIT` - - # Otherwise it is not a genuine Autoconf input file. - test -n "$_G_ac_init" - _G_status=$? - - test 0 -ne "$_G_status" \ - && func_verbose "'$1' not using Autoconf" - - (exit $_G_status) -} - - -# func_find_tool ENVVAR NAMES... -# ------------------------------ -# Search for a required program. Use the value of ENVVAR, if set, -# otherwise find the first of the NAMES that can be run (i.e., -# supports --version). If found, set ENVVAR to the program name, -# die otherwise. -func_find_tool () -{ - $debug_cmd - - _G_find_tool_envvar=$1 - shift - _G_find_tool_names=$@ - eval "_G_find_tool_res=\$$_G_find_tool_envvar" - if test -n "$_G_find_tool_res"; then - _G_find_tool_error_prefix="\$$find_tool_envvar: " - else - for _G_prog - do - if func_tool_version_output $_G_prog >/dev/null; then - _G_find_tool_res=$_G_prog - break - fi - done - fi - if test -n "$_G_find_tool_res"; then - func_tool_version_output >/dev/null $_G_find_tool_res "\ -${_G_find_tool_error_prefix}Cannot run '$_G_find_tool_res --version'" - - # Make sure the result is exported to the environment for children - # to use. - eval "$_G_find_tool_envvar=\$_G_find_tool_res" - eval "export $_G_find_tool_envvar" - else - func_error "\ -One of these is required: - $_G_find_tool_names" - fi -} - - -# func_tool_version_output CMD [FATAL-ERROR-MSG] -# ---------------------------------------------- -# Attempt to run 'CMD --version', discarding errors. The output can be -# ignored by redirecting stdout, and this function used simply to test -# whether the command exists and exits normally when passed a -# '--version' argument. -# When FATAL-ERROR-MSG is given, then this function will display the -# message and exit if running 'CMD --version' returns a non-zero exit -# status. -func_tool_version_output () -{ - $debug_cmd - - _G_cmd=$1 - _G_fatal_error_msg=$2 - - # Some tools, like 'git2cl' produce thousands of lines of output - # unless stdin is /dev/null - in that case we want to return - # successfully without saving all of that output. Other tools, - # such as 'help2man' exit with a non-zero status when stdin comes - # from /dev/null, so we re-execute without /dev/null if that - # happens. This means that occasionally, the output from both calls - # ends up in the result, but the alternative would be to discard the - # output from one call, and hope the other produces something useful. - { $_G_cmd --version /dev/null - _G_status=$? - - test 0 -ne "$_G_status" && test -n "$_G_fatal_error_msg" \ - && func_fatal_error "$_G_fatal_error_msg" - - (exit $_G_status) -} - - -## -------------------- ## -## Resource management. ## -## -------------------- ## - -# This section contains definitions for functions that each ensure a -# particular resource (a file, or a non-empty configuration variable for -# example) is available, and if appropriate to extract default values -# from pertinent package files. Where a variable already has a non- -# empty value (as set by the package's 'bootstrap.conf'), that value is -# used in preference to deriving the default. Call them using their -# associated 'require_*' variable to ensure that they are executed, at -# most, once. -# -# It's entirely deliberate that calling these functions can set -# variables that don't obey the namespace limitations obeyed by the rest -# of this file, in order that that they be as useful as possible to -# callers. - - -# require_configure_ac -# -------------------- -# Ensure that there is a 'configure.ac' or 'configure.in' file in the -# current directory that contains an uncommented call to AC_INIT, and -# that '$configure_ac' contains its name. -require_configure_ac=func_require_configure_ac -func_require_configure_ac () -{ - $debug_cmd - - test -z "$configure_ac" \ - && func_autoconf_configure configure.ac && configure_ac=configure.ac - test -z "$configure_ac" \ - && func_autoconf_configure configure.in && configure_ac=configure.in - test -z "$configure_ac" \ - || func_verbose "found '$configure_ac'" - - require_configure_ac=: -} - - -# require_gnu_m4 -# -------------- -# Search for GNU M4, and export it in $M4. -require_gnu_m4=func_require_gnu_m4 -func_require_gnu_m4 () -{ - $debug_cmd - - test -n "$M4" || { - # Find the first m4 binary that responds to --version. - func_find_tool M4 gm4 gnum4 m4 - } - - test -n "$M4" || func_fatal_error "\ -Please install GNU M4, or 'export M4=/path/to/gnu/m4'." - - func_verbose "export M4='$M4'" - - # Make sure the search result is visible to subshells - export M4 - - require_gnu_m4=: -} - - -## --------------- ## -## Core functions. ## -## --------------- ## - -# This section contains the high level functions used when calling this -# file as a script. 'func_extract_trace' is probably the only one that you -# won't want to replace if you source this file into your own script. - - -# func_extract_trace MACRO_NAMES [FILENAME]... -# -------------------------------------------- -# set '$func_extract_trace_result' to a colon delimited list of arguments -# to any of the comma separated list of MACRO_NAMES in FILENAME. If no -# FILENAME is given, then '$configure_ac' is assumed. -func_extract_trace () -{ - $debug_cmd - - $require_configure_ac - $require_gnu_m4 - - _G_m4_traces=`$bs_echo "--trace=$1" |$SED 's%,% --trace=%g'` - _G_re_macros=`$bs_echo "($1)" |$SED 's%,%|%g'` - _G_macros="$1"; shift - test $# -gt 0 || { - set dummy $configure_ac - shift - } - - # Generate an error if the first file is missing - <"$1" - - # Sadly, we can't use 'autom4te' tracing to extract macro arguments, - # because it complains about things we want to ignore at bootstrap - # time - like missing m4_include files; AC_PREREQ being newer than - # the installed autoconf; and returns nothing when tracing - # 'AM_INIT_AUTOMAKE' when aclocal hasn't been generated yet. - # - # The following tries to emulate a less persnickety version of (and - # due to not having to wait for Perl startup on every invocation, - # it's probably faster too): - # - # autom4te --language=Autoconf --trace=$my_macro:\$% "$@" - # - # First we give a minimal set of macro declarations to M4 to prime - # it for reading Autoconf macros, while still providing some of the - # functionality generally used at m4-time to supply dynamic - # arguments to Autocof functions, but without following - # 'm4_s?include' files. - _G_mini=' - # Initialisation. - m4_changequote([,]) - m4_define([m4_copy], [m4_define([$2], m4_defn([$1]))]) - m4_define([m4_rename], [m4_copy([$1], [$2])m4_undefine([$1])]) - - # Disable these macros. - m4_undefine([m4_dnl]) - m4_undefine([m4_include]) - m4_undefine([m4_m4exit]) - m4_undefine([m4_m4wrap]) - m4_undefine([m4_maketemp]) - - # Copy and rename macros not handled by "m4 --prefix". - m4_define([dnl], [m4_builtin([dnl])]) - m4_copy([m4_define], [m4_defun]) - m4_rename([m4_ifelse], [m4_if]) - m4_ifdef([m4_mkstemp], [m4_undefine([m4_mkstemp])]) - m4_rename([m4_patsubst], [m4_bpatsubst]) - m4_rename([m4_regexp], [m4_bregexp]) - - # "m4sugar.mini" - useful m4-time macros for dynamic arguments. - # If we discover packages that need more m4 macros defined in - # order to bootstrap correctly, add them here: - m4_define([m4_bmatch], - [m4_if([$#], 0, [], [$#], 1, [], [$#], 2, [$2], - [m4_if(m4_bregexp([$1], [$2]), -1, - [$0([$1], m4_shift3($@))], [$3])])]) - m4_define([m4_ifndef], [m4_ifdef([$1], [$3], [$2])]) - m4_define([m4_ifset], - [m4_ifdef([$1], [m4_ifval(m4_defn([$1]), [$2], [$3])], [$3])]) - m4_define([m4_require], [$1]) - m4_define([m4_shift3], [m4_shift(m4shift(m4shift($@)))]) - - # "autoconf.mini" - things from autoconf macros we care about. - m4_copy([m4_defun], [AC_DEFUN]) - - # Dummy definitions for the macros we want to trace. - # AM_INIT_AUTOMAKE at least produces no trace without this. - ' - - _G_save=$IFS - IFS=, - for _G_macro in $_G_macros; do - IFS=$_G_save - func_append _G_mini "AC_DEFUN([$_G_macro])$nl" - done - IFS=$_G_save - - # We discard M4's stdout, but the M4 trace output from reading our - # "autoconf.mini" followed by any other files passed to this - # function is then scanned by sed to transform it into a colon - # delimited argument list assigned to a shell variable. - _G_transform='s|#.*$||; s|^dnl .*$||; s| dnl .*$||;' - - # Unfortunately, alternation in regexp addresses doesn't work in at - # least BSD (and hence Mac OS X) sed, so we have to append a capture - # and print block for each traced macro to the sed transform script. - _G_save=$IFS - IFS=, - for _G_macro in $_G_macros; do - IFS=$_G_save - func_append _G_transform ' - /^m4trace: -1- '"$_G_macro"'/ { - s|^m4trace: -1- '"$_G_macro"'[([]*|| - s|], [[]|:|g - s|[])]*$|:| - s|\(.\):$|\1| - p - }' - done - IFS=$_G_save - - # Save the command pipeline results for further use by callers of - # this function. - func_extract_trace_result=`$bs_echo "$_G_mini" \ - |$M4 -daq --prefix $_G_m4_traces - "$@" 2>&1 1>/dev/null \ - |$SED -n -e "$_G_transform"` -} - - -# func_extract_trace_first MACRO_NAMES [FILENAME]... -# -------------------------------------------------- -# Exactly like func_extract_trace, except that only the first argument -# to the first invocation of one of the comma separated MACRO_NAMES is -# returned in '$func_extract_trace_first_result'. -func_extract_trace_first () -{ - $debug_cmd - - func_extract_trace ${1+"$@"} - func_extract_trace_first_result=`$bs_echo "$func_extract_trace_result" \ - |$SED -e 's|:.*$||g' -e 1q` -} - - -# func_main [ARG]... -# ------------------ -func_main () -{ - $debug_cmd - - # Configuration. - usage='$progname MACRO_NAME FILE [...]' - - long_help_message=' -The first argument to this program is the name of an autotools macro -whose arguments you want to extract by examining the files listed in the -remaining arguments using the same tool that Autoconf and Automake use, -GNU M4. - -The arguments are returned separated by colons, with each traced call -on a separate line.' - - # Option processing. - func_options "$@" - eval set dummy "$func_options_result"; shift - - # Validate remaining non-option arguments. - test $# -gt 1 \ - || func_fatal_help "not enough arguments" - - # Pass non-option arguments to extraction function. - func_extract_trace "$@" - - # Display results. - test -n "$func_extract_trace_result" \ - && $bs_echo "$func_extract_trace_result" - - # The End. - exit $EXIT_SUCCESS -} - - -## --------------------------- ## -## Actually perform the trace. ## -## --------------------------- ## - -# Only call 'func_main' if this script was called directly. -test extract-trace = "$progname" && func_main "$@" - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" -# time-stamp-time-zone: "UTC" -# End: - -# Set a version string for *this* script. -scriptversion=2013-08-29.21; # UTC - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Originally written by Paul Eggert. The canonical version of this -# script is maintained as build-aux/bootstrap in gnulib, however, to -# be useful to your project, you should place a copy of it under -# version control in the top-level directory of your project. The -# intent is that all customization can be done with a bootstrap.conf -# file also maintained in your version control; gnulib comes with a -# template build-aux/bootstrap.conf to get you started. - -# Please report bugs or propose patches to bug-gnulib@gnu.org. - - -## ------ ## -## Usage. ## -## ------ ## - -# Most GNUish projects do not keep all of the generated Autotool -# files under version control, but running all of the right tools -# with the right arguments, in the correct order to regenerate -# all of those files in readiness for configuration and building -# can be surprisingly involved! Many projects have a 'bootstrap' -# script under version control to invoke Autotools and perform -# other assorted book-keeping with version numbers and the like. -# -# This bootstrap script aims to probe the configure.ac and top -# Makefile.am of your project to automatically determine what -# the correct ordering and arguments are and then run the tools for -# you. In order to use it, you can generate an initial standalone -# script with: -# -# gl/build-aux/inline-source gl/build-aux/bootstrap.in > bootstrap -# -# You should then store than script in version control for other -# developers in you project. It will give you instructions about -# how to keep it up to date if the sources change. -# -# See gl/doc/bootstrap.texi for documentation on how to write -# a bootstrap.conf to customize it for your project's -# idiosyncracies. - - -## ================================================================== ## -## ## -## DO NOT EDIT THIS FILE, CUSTOMIZE IT USING A BOOTSTRAP.CONF ## -## ## -## ================================================================== ## - -## ------------------------------- ## -## User overridable command paths. ## -## ------------------------------- ## - -# All uppercase denotes values stored in the environment. These -# variables should generally be overridden by the user - however, we do -# set them to 'true' in some parts of this script to prevent them being -# called at the wrong time by other tools that we call ('autoreconf', -# for example). -# -# We also allow 'LIBTOOLIZE', 'M4', 'SHA1SUM' and some others to be -# overridden, and export the result for child processes, but they are -# handled by the function 'func_find_tool' and not defaulted in this -# section. - -: ${ACLOCAL="aclocal"} -: ${AUTOCONF="autoconf"} -: ${AUTOHEADER="autoheader"} -: ${AUTOM4TE="autom4te"} -: ${AUTOHEADER="autoheader"} -: ${AUTOMAKE="automake"} -: ${AUTOPOINT="autopoint"} -: ${AUTORECONF="autoreconf"} -: ${CMP="cmp"} -: ${CONFIG_SHELL="/bin/sh"} -: ${DIFF="diff"} -: ${EGREP="grep -E"} -: ${FGREP="grep -F"} -: ${GIT="git"} -: ${GREP="grep"} -: ${LN_S="ln -s"} -: ${RM="rm"} -: ${SED="sed"} - -export ACLOCAL -export AUTOCONF -export AUTOHEADER -export AUTOM4TE -export AUTOHEADER -export AUTOMAKE -export AUTOPOINT -export AUTORECONF -export CONFIG_SHELL - - -## -------------- ## -## Configuration. ## -## -------------- ## - -# A newline delimited list of triples of programs (that respond to -# --version), the minimum version numbers required (or just '-' in the -# version field if any version will be sufficient) and homepage URLs -# to help locate missing packages. -buildreq= - -# Name of a file containing instructions on installing missing packages -# required in 'buildreq'. -buildreq_readme=README-hacking - -# These are extracted from AC_INIT in configure.ac, though you can -# override those values in 'bootstrap.conf' if you prefer. -build_aux= -macro_dir= -package= -package_name= -package_version= -package_bugreport= - -# These are extracted from 'gnulib-cache.m4', or else fall-back -# automatically on the gnulib defaults; unless you set the values -# manually in 'bootstrap.conf'. -doc_base= -gnulib_mk= -gnulib_name= -local_gl_dir= -source_base= -tests_base= - -# The list of gnulib modules required at 'gnulib-tool' time. If you -# check 'gnulib-cache.m4' into your repository, then this list will be -# extracted automatically. -gnulib_modules= - -# Extra gnulib files that are not in modules, which override files of -# the same name installed by other bootstrap tools. -gnulib_non_module_files=" - build-aux/compile - build-aux/install-sh - build-aux/mdate-sh - build-aux/texinfo.tex - build-aux/depcomp - build-aux/config.guess - build-aux/config.sub - doc/INSTALL -" - -# Relative path to the local gnulib submodule, and url to the upstream -# git repository. If you have a gnulib entry in your .gitmodules file, -# these values are ignored. -gnulib_path= -gnulib_url= - -# Additional gnulib-tool options to use. -gnulib_tool_options=" - --no-changelog -" - -# bootstrap removes any macro-files that are not included by aclocal.m4, -# except for files listed in this variable that are always kept. -gnulib_precious=" - gnulib-tool.m4 -" - -# When truncating long commands for display, always allow at least this -# many characters before truncating. -min_cmd_len=160 - -# The command to download all .po files for a specified domain into -# a specified directory. Fill in the first %s is the domain name, and -# the second with the destination directory. Use rsync's -L and -r -# options because the latest/%s directory and the .po files within are -# all symlinks. -po_download_command_format=\ -"rsync --delete --exclude '*.s1' -Lrtvz \ -'translationproject.org::tp/latest/%s/' '%s'" - -# Other locale categories that need message catalogs. -extra_locale_categories= - -# Additional xgettext options to use. Gnulib might provide you with an -# extensive list of additional options to append to this, but gettext -# 0.16.1 and newer appends them automaticaly, so you can safely ignore -# the complaints from 'gnulib-tool' if your $configure_ac states: -# -# AM_GNU_GETTEXT_VERSION([0.16.1]) -xgettext_options=" - --flag=_:1:pass-c-format - --flag=N_:1:pass-c-format -" - -# Package copyright holder for gettext files. Defaults to FSF if unset. -copyright_holder= - -# File that should exist in the top directory of a checked out hierarchy, -# but not in a distribution tarball. -checkout_only_file= - -# Whether to use copies instead of symlinks by default (if set to true, -# the --copy option has no effect). -copy=false - -# Set this to ".cvsignore .gitignore" in 'bootstrap.conf' if you want -# those files to be generated in directories like 'lib/', 'm4/', and 'po/', -# or set it to "auto" to make this script select what to use based -# on what version control system (if any) is used in the source directory. -# Or set it to "none" to ignore VCS ignore files entirely. Default is -# "auto". -vc_ignore= - - -## ------------------- ## -## Hookable functions. ## -## ------------------- ## - -# After 'bootstrap.conf' has been sourced, execution proceeds by calling -# 'func_bootstrap'. Wherever a function is decorated with -# 'func_hookable func_name', you will find a matching 'func_run_hooks -# func_name', which executes all functions added with 'func_add_hook -# func_name my_func'. -# -# You might notice that many of these functions begin with a series of -# '$require_foo' lines. See the docu-comments at the start of the -# 'Resource management' section for a description of what these are. - - -# func_bootstrap [ARG]... -# ----------------------- -# All the functions called inside func_bootstrap are hookable. See the -# the individual implementations for details. -func_bootstrap () -{ - $debug_cmd - - # Save the current positional parameters to prevent them being - # corrupted by calls to 'set' in 'func_init'. - func_quote_for_eval ${1+"$@"} - _G_saved_positional_parameters=$func_quote_for_eval_result - - # Initialisation. - func_init - - # Option processing. - eval func_options "$_G_saved_positional_parameters" - - # Post-option preparation. - func_prep - - # Ensure ChangeLog presence. - func_ifcontains "$gnulib_modules" gitlog-to-changelog \ - func_ensure_changelog - - # Reconfigure the package. - func_reconfigure - - # Ensure .version is up-to-date. - func_update_dotversion - - # Finalisation. - func_fini -} - - -# func_init -# --------- -# Any early initialisations can be hooked to this function. Consider -# whether you can hook onto 'func_prep' instead, because if you hook -# any slow to execute code in here, it will also add to the time before -# './bootstrap --version' can respond. -func_hookable func_init -func_init () -{ - $debug_cmd - - func_run_hooks func_init -} - - -# func_prep -# --------- -# Function to perform preparation for remaining bootstrap process. If -# your hooked code relies on the outcome of 'func_options' hook it here -# rather than to 'func_init'. -# -# All the functions called inside func_prep are hookable. See the -# individual implementations for details. -func_hookable func_prep -func_prep () -{ - $debug_cmd - - $require_buildtools_uptodate - $require_checkout_only_file - - $require_gnulib_merge_changelog - - # fetch update files from the translation project - func_update_translations - - func_run_hooks func_prep -} - - -# func_update_translations -# ------------------------ -# Update package po files and translations. -func_hookable func_update_translations -func_update_translations () -{ - $debug_cmd - - $opt_skip_po || { - test -d po && { - $require_package - - func_update_po_files po $package || exit $? - } - - func_run_hooks func_update_translations - } -} - - -# func_reconfigure -# ---------------- -# Reconfigure the current package by running the appropriate autotools in a -# suitable order. -func_hookable func_reconfigure -func_reconfigure () -{ - $debug_cmd - - # Released 'autopoint' has the tendency to install macros that have - # been obsoleted in current 'gnulib., so run this before 'gnulib-tool'. - func_autopoint - - # Autoreconf runs 'aclocal' before 'libtoolize', which causes spurious - # warnings if the initial 'aclocal' is confused by the libtoolized - # (or worse: out-of-date) macro directory. - func_libtoolize - - # If you need to do anything after 'gnulib-tool' is done, but before - # 'autoreconf' runs, you don't need to override this whole function, - # because 'func_gnulib_tool' is hookable. - func_gnulib_tool - - func_autoreconf - - func_run_hooks func_reconfigure -} - - -# func_gnulib_tool -# ---------------- -# Run 'gnulib-tool' to fetch gnulib modules into the current package. -# -# It's assumed that since you are using gnulib's 'bootstrap' script, -# you're also using gnulib elsewhere in your package. If not, then -# you can replace this function in 'bootstrap.conf' with: -# -# func_gnulib_tool () { :; } -# -# (although the function returns immediately if $gnulib_tool is set to -# true in any case). -func_hookable func_gnulib_tool -func_gnulib_tool () -{ - $debug_cmd - - $require_gnulib_tool - $require_libtoolize - - test true = "$gnulib_tool" || { - # bootstrap.conf written for gnulib bootstrap expects - # gnulib_tool_option_extras to which --no-changelog is appended, - # but libtool bootstrap expects you to append to gnulib_tool_options - # so that you can override the --no-changelog default: make sure we - # support both styles so users can migrate between them easily. - gnulib_tool_all_options="$gnulib_tool_options $gnulib_tool_option_extras" - - if test -n "$gnulib_modules"; then - $require_gnulib_cache - $require_gnulib_tool_base_options - - gnulib_mode=--import - - # Try not to pick up any stale values from 'gnulib-cache.m4'. - rm -f "$gnulib_cache" - - test -n "$gnulib_tool_base_options" \ - && func_append_uniq gnulib_tool_all_options " $gnulib_tool_base_options" - test -n "$gnulib_mk" \ - && func_append_uniq gnulib_tool_all_options " --makefile-name=$gnulib_mk" - test -n "$tests_base" && { - func_append_uniq gnulib_tool_all_options " --tests-base=$tests_base" - func_append_uniq gnulib_tool_all_options " --with-tests" - } - else - - # 'gnulib_modules' and others are cached in 'gnulib-cache.m4': - # Use 'gnulib --update' to fetch gnulib modules. - gnulib_mode=--update - fi - - # Add a sensible default libtool option to gnulib_tool_options. - # The embedded echo is to squash whitespace before globbing. - case `echo " "$gnulib_tool_all_options" "` in - *" --no-libtool "*|*" --libtool "*) ;; - *) if test true = "$LIBTOOLIZE"; then - func_append_uniq gnulib_tool_all_options " --no-libtool" - else - func_append_uniq gnulib_tool_all_options " --libtool" - fi - ;; - esac - - $opt_copy || func_append_uniq gnulib_tool_all_options " --symlink" - - func_append_uniq gnulib_tool_all_options " $gnulib_mode" - func_append gnulib_tool_all_options " $gnulib_modules" - - # The embedded echo is to squash whitespace before display. - gnulib_cmd=`echo $gnulib_tool $gnulib_tool_all_options` - - func_show_eval "$gnulib_cmd" 'exit $?' - - # Use 'gnulib-tool --copy-file' to install non-module files. - func_install_gnulib_non_module_files - } - - func_run_hooks func_gnulib_tool -} - - -# func_fini -# --------- -# Function to perform all finalisation for the bootstrap process. -func_hookable func_fini -func_fini () -{ - $debug_cmd - - func_gettext_configuration - func_clean_dangling_symlinks - func_clean_unused_macros - func_skip_po_recommendation - - func_run_hooks func_fini - - $require_bootstrap_uptodate - - func_echo "Done. Now you can run './configure'." -} - - -# func_gettext_configuration -# -------------------------- -# Edit configuration values into po/Makevars. -func_hookable func_gettext_configuration -func_gettext_configuration () -{ - $debug_cmd - - $require_autopoint - - test true = "$AUTOPOINT" || { - $require_copyright_holder - $require_extra_locale_categories - $require_package_bugreport - - # Escape xgettext options for sed Makevars generation below. - # We have to delete blank lines in a separate script so that we don't - # append \\\ to the penultimate line, and then delete the last empty - # line, which messes up the variable substitution later in this - # function. Note that adding a literal \\\ requires double escaping - # here, once for the execution subshell, and again for the assignment, - # which is why there are actually 12 (!!) backslashes in the script. - _G_xgettext_options=`echo "$xgettext_options$nl" |$SED '/^$/d' |$SED ' - $b - s|$| \\\\\\\\\\\\|'` - - # Create gettext configuration. - func_echo "Creating po/Makevars from po/Makevars.template ..." - $RM -f po/Makevars - $SED ' - /^EXTRA_LOCALE_CATEGORIES *=/s|=.*|= '"$extra_locale_categories"'| - /^COPYRIGHT_HOLDER *=/s|=.*|= '"$copyright_holder"'| - /^MSGID_BUGS_ADDRESS *=/s|=.*|= '"$package_bugreport"'| - /^XGETTEXT_OPTIONS *=/{ - s|$| \\| - a\ - '"$_G_xgettext_options"' \\\ - $${end_of_xgettext_options+} - } - ' po/Makevars.template >po/Makevars || exit 1 - } - - func_run_hooks func_gettext_configuration -} - - - -## --------------- ## -## Core functions. ## -## --------------- ## - -# This section contains the main functions called from the 'Hookable -# functions' (shown above), and are the ones you're most likely -# to want to replace with your own implementations in 'bootstrap.conf'. - - -# func_autopoint -# -------------- -# If this package uses gettext, then run 'autopoint'. -func_autopoint () -{ - $debug_cmd - - $require_autopoint - - test true = "$AUTOPOINT" \ - || func_show_eval "$AUTOPOINT --force" 'exit $?' -} - - -# func_libtoolize -# --------------- -# If this package uses libtool, then run 'libtoolize'. -func_libtoolize () -{ - $debug_cmd - - $require_libtoolize - - test true = "$LIBTOOLIZE" || { - _G_libtoolize_options= - $opt_copy && func_append _G_libtoolize_options " --copy" - $opt_force && func_append _G_libtoolize_options " --force" - $opt_verbose || func_append _G_libtoolize_options " --quiet" - func_show_eval "$LIBTOOLIZE$_G_libtoolize_options" 'exit $?' - } -} - - -# func_gnulib_tool_copy_file SRC DEST -# ----------------------------------- -# Copy SRC, a path relative to the gnulib sub-tree, to DEST, a path -# relative to the top-level source directory using gnulib-tool so that -# any patches or replacements in $local_gl_dir are applied. -func_gnulib_tool_copy_file () -{ - $debug_cmd - - $require_gnulib_path - $require_gnulib_tool - $require_patch - - gnulib_copy_cmd="$gnulib_tool --copy-file" - $opt_copy || func_append gnulib_copy_cmd " --symlink" - - if test true = "$gnulib_tool"; then - # If gnulib-tool is not available (e.g. bootstrapping in a - # distribution tarball), make sure that at least we have some - # version of the required file already in place. - test -f "$2" || func_fatal_error "\ -Can't find, copy or download '$2', a required -gnulib supplied file, please provide the location of a -complete 'gnulib' tree by setting 'gnulib_path' in your -'bootstrap.conf' or with the '--gnulib-srcdir' option - -or else specify the location of your 'git' binary by -setting 'GIT' in the environment so that a fresh -'gnulib' submodule can be cloned." - else - test -f "$gnulib_path/$1" || { - func_error "'$gnulib_path/$1' does not exist" - return 1 - } - - $gnulib_copy_cmd $1 $2 - fi -} - - -# func_install_gnulib_non_module_files -# ------------------------------------ -# Get additional non-module files from gnulib, overriding existing files. -func_install_gnulib_non_module_files () -{ - $debug_cmd - - $require_build_aux - $require_gnulib_tool - - test -n "$gnulib_non_module_files" && { - maybe_exit_cmd=: - - for file in $gnulib_non_module_files; do - case $file in - */COPYING*) dest=COPYING;; - */INSTALL) dest=INSTALL;; - build-aux/missing) dest= - func_warning settings "\ -Please remove build-aux/missing from gnulib_module_files in -'bootstrap.conf', as it may clash with Automake's version." - ;; - build-aux/*) dest=$build_aux/`expr "$file" : 'build-aux/\(.*\)'`;; - *) dest=$file;; - esac - - # Be sure to show all copying errors before bailing out - test -z "$dest" \ - || func_gnulib_tool_copy_file "$file" "$dest" \ - || maybe_exit_cmd="exit $EXIT_FAILURE" - done - - $maybe_exit_cmd - } -} - - -# func_ensure_changelog -# --------------------- -# Even with 'gitlog-to-changelog' generated ChangeLogs, automake -# will not run to completion with no ChangeLog file. -func_ensure_changelog () -{ - $debug_cmd - - test -f ChangeLog && mv -f ChangeLog ChangeLog~ - - cat >ChangeLog <<'EOT' -## ---------------------- ## -## DO NOT EDIT THIS FILE! ## -## ---------------------- ## - -ChangeLog is generated by gitlog-to-changelog. -EOT - - _G_message="creating dummy 'ChangeLog'" - test -f ChangeLog~ \ - && func_append _G_message ' (backup in ChangeLog~)' - func_verbose "$_G_message" - - return 0 -} - - -# func_autoreconf -# --------------- -# Being careful not to re-run 'autopoint' or 'libtoolize', and not to -# try to run 'autopoint', 'libtoolize' or 'autoheader' on packages that -# don't use them, defer to 'autoreconf' for execution of the remaining -# autotools to bootstrap this package. -func_autoreconf () -{ - $debug_cmd - - $require_autoheader - $require_build_aux # automake and others put files in here - $require_macro_dir # aclocal and others put files in here - - # We ran these manually already, and autoreconf won't exec ':' - save_AUTOPOINT=$AUTOPOINT; AUTOPOINT=true - save_LIBTOOLIZE=$LIBTOOLIZE; LIBTOOLIZE=true - - _G_autoreconf_options= - $opt_copy || func_append _G_autoreconf_options " --symlink" - $opt_force && func_append _G_autoreconf_options " --force" - $opt_verbose && func_append _G_autoreconf_options " --verbose" - func_show_eval "$AUTORECONF$_G_autoreconf_options --install" 'exit $?' - - AUTOPOINT=$save_AUTOPOINT - LIBTOOLIZE=$save_LIBTOOLIZE -} - - -# func_check_configuration VARNAME [CONFIGURE_MACRO] -# -------------------------------------------------- -# Exit with a suitable diagnostic for an important configuration change -# that needs to be made before bootstrap can run correctly. -func_check_configuration () -{ - $debug_cmd - - $require_configure_ac - - eval 'test -n "$'$1'"' || { - _G_error_msg="please set '$1' in 'bootstrap.conf'" - if test -n "$configure_ac" && test -n "$2"; then - func_append _G_error_msg " -or add the following (or similar) to your '$configure_ac': -$2" - fi - - func_fatal_error "$_G_error_msg" - } -} - - -# func_clean_dangling_symlinks -# ---------------------------- -# Remove any dangling symlink matching "*.m4" or "*.[ch]" in some -# gnulib-populated directories. Such .m4 files would cause aclocal to -# fail. The following requires GNU find 4.2.3 or newer. Considering -# the usual portability constraints of this script, that may seem a very -# demanding requirement, but it should be ok. Ignore any failure, -# which is fine, since this is only a convenience to help developers -# avoid the relatively unusual case where a symlinked-to .m4 file is -# git-removed from gnulib between successive runs of this script. -func_clean_dangling_symlinks () -{ - $debug_cmd - - $require_macro_dir - $require_source_base - - func_verbose "cleaning dangling symlinks" - - find "$macro_dir" "$source_base" \ - -depth \( -name '*.m4' -o -name '*.[ch]' \) \ - -type l -xtype l -delete > /dev/null 2>&1 -} - - -# func_clean_unused_macros -# ------------------------ -# Autopoint can result in over-zealously adding macros into $macro_dir -# even though they are not actually used, for example tests to help -# build the 'intl' directory even though you have specified -# 'AM_GNU_GETTEXT([external])' in your configure.ac. This function -# looks removes any macro files that can be found in gnulib, but -# are not 'm4_include'd by 'aclocal.m4'. -func_clean_unused_macros () -{ - $debug_cmd - - $require_gnulib_path - $require_macro_dir - - test -n "$gnulib_path" && test -f aclocal.m4 && { - aclocal_m4s=`find . -name aclocal.m4 -print` - - # We use 'ls|grep' instead of 'ls *.m4' to avoid exceeding - # command line length limits in some shells. - for file in `cd "$macro_dir" && ls -1 |grep '\.m4$'`; do - - # Remove a macro file when aclocal.m4 does not m4_include it... - func_grep_q 'm4_include([[]'$macro_dir/$file'])' $aclocal_m4s \ - || test ! -f "$gnulib_path/m4/$file" || { - - # ...and there is an identical file in gnulib... - if func_cmp_s "$gnulib_path/m4/$file" "$macro_dir/$file"; then - - # ...and it's not in the precious list ('echo' is needed - # here to squash whitespace for the match expression). - case " "`echo $gnulib_precious`" " in - *" $file "*) ;; - *) rm -f "$macro_dir/$file" - func_verbose \ - "removing unused gnulib file '$macro_dir/$file'" - esac - fi - } - done - } -} - - -# func_skip_po_recommendation -# --------------------------- -# If there is a po directory, and '--skip-po' wasn't passed, let the -# user know that they can use '--skip-po' on subsequent invocations. -func_skip_po_recommendation () -{ - $debug_cmd - - test ! -d po \ - || $opt_skip_po \ - || func_warning recommend "\ -If your pofiles are up-to-date, you can rerun bootstrap -as '$progname --skip-po' to avoid redownloading." -} - - -# func_update_dotversion -# ---------------------- -# Even with 'gitlog-to-changelog' generated ChangeLogs, automake -# will not run to completion with no ChangeLog file. -func_update_dotversion () -{ - $debug_cmd - - test -f "$build_aux/git-version-gen" && { - _G_message="updating .version" - test -f .version && { - mv .version .version~ - func_append _G_message " (backup in .version~)" - } - func_verbose "updating .version" - - $build_aux/git-version-gen dummy-arg > .version - } -} - - - -## -------------------- ## -## Resource management. ## -## -------------------- ## - -# This section contains definitions for functions that each ensure a -# particular resource (a file, or a non-empty configuration variable for -# example) is available, and if appropriate to extract default values -# from pertinent package files. Where a variable already has a non- -# empty value (as set by the package's 'bootstrap.conf'), that value is -# used in preference to deriving the default. Call them using their -# associated 'require_*' variable to ensure that they are executed, at -# most, once. - - -# require_checkout_only_file -# -------------------------- -# Bail out if this package only bootstraps properly from a repository -# checkout. -require_checkout_only_file=func_require_checkout_only_file -func_require_checkout_only_file () -{ - $debug_cmd - - $opt_force || { - test -n "$checkout_only_file" && test ! -f "$checkout_only_file" \ - && func_fatal_error "\ -Bootstrapping from a non-checked-out distribution is risky. -If you wish to bootstrap anyway, use the '--force' option." - } - - require_checkout_only_file=: -} - - -# require_aclocal_amflags -# ----------------------- -# Ensure '$aclocal_amflags' has a sensible default, extracted from -# 'Makefile.am' if necessary. -require_aclocal_amflags=func_require_aclocal_amflags -func_require_aclocal_amflags () -{ - $debug_cmd - - $require_makefile_am - - _G_sed_extract_aclocal_amflags='s|#.*$|| - /^[ ]*ACLOCAL_AMFLAGS[ ]*=/ { - s|^.*=[ ]*\(.*\)|aclocal_amflags="\1"| - p - }' - - _G_aclocal_flags_cmd=`$SED -n "$_G_sed_extract_aclocal_amflags" \ - "$makefile_am"` - eval "$_G_aclocal_flags_cmd" - - func_verbose "ACLOCAL_AMFLAGS='$aclocal_amflags'" - - require_aclocal_amflags=: -} - - -# require_autoheader -# ------------------ -# Skip autoheader if it's not needed. -require_autoheader=func_require_autoheader -func_require_autoheader () -{ - $debug_cmd - - test true = "$AUTOHEADER" || { - func_extract_trace AC_CONFIG_HEADERS - test -n "$func_extract_trace_result" \ - || func_extract_trace AC_CONFIG_HEADER - - test -n "$func_extract_trace_result" || { - AUTOHEADER=true - - func_verbose "export AUTOHEADER='$AUTOHEADER'" - - # Make sure the search result is visible to subshells - export AUTOHEADER - } - } - - require_autoheader=: -} - - -# require_autopoint -# ----------------- -# Skip autopoint if it's not needed. -require_autopoint=func_require_autopoint -func_require_autopoint () -{ - $debug_cmd - - test true = "$AUTOPOINT" || { - func_extract_trace AM_GNU_GETTEXT_VERSION - - test -n "$func_extract_trace_result" || { - AUTOPOINT=true - - func_verbose "export AUTOPOINT='$AUTOPOINT'" - - # Make sure the search result is visible to subshells - export AUTOPOINT - } - } - - require_autopoint=: -} - - -# require_bootstrap_uptodate -# -------------------------- -# Complain if the version of bootstrap in the gnulib directory differs -# from the one we are running. -require_bootstrap_uptodate=func_require_bootstrap_uptodate -func_require_bootstrap_uptodate () -{ - $debug_cmd - - $require_build_aux - - _G_bootstrap_sources=" - $build_aux/bootstrap.in - $build_aux/extract-trace - $build_aux/funclib.sh - $build_aux/options-parser - " - - _G_missing_bootstrap_sources=false - for _G_src in $_G_bootstrap_sources; do - test -f "$_G_src" || _G_missing_bootstrap_sources=: - done - - if $_G_missing_bootstrap_sources; then - func_warning upgrade "\ -Please add bootstrap to your gnulib_modules list in -'bootstrap.conf', so that I can tell you when there are -updates available." - else - $build_aux/inline-source $build_aux/bootstrap.in > bootstrap.new - - if func_cmp_s "$progpath" bootstrap.new; then - rm -f bootstrap.new - func_verbose "bootstrap script up to date" - else - func_warning upgrade "\ -An updated bootstrap script has been generated for you in -'bootstrap.new'. After you've verified that you want -the changes, you can update with: - cat bootstrap.new > $progname - ./$progname - -Or you can disable this check permanently by adding the -following to 'bootstrap.conf': - require_bootstrap_uptodate=:" - fi - fi - - require_bootstrap_uptodate=: -} - - -# require_build_aux -# ----------------- -# Ensure that '$build_aux' is set, and if it doesn't already point to an -# existing directory, create one. -require_build_aux=func_require_build_aux -func_require_build_aux () -{ - $debug_cmd - - test -n "$build_aux" || { - func_extract_trace_first AC_CONFIG_AUX_DIR - build_aux=$func_extract_trace_first_result - func_check_configuration build_aux \ - "AC_CONFIG_AUX_DIR([name of a directory for build scripts])" - - func_verbose "build_aux='$build_aux'" - } - - $require_vc_ignore_files - - # If the build_aux directory doesn't exist, create it now, and mark it - # as ignored for the VCS. - if test ! -d "$build_aux"; then - func_show_eval "mkdir '$build_aux'" - - test -n "$vc_ignore_files" \ - || func_insert_if_absent "$build_aux" $vc_ignore_files - fi - - require_build_aux=: -} - - -# require_buildreq_autobuild -# -------------------------- -# Try to find whether the bootstrap requires autobuild. -require_buildreq_autobuild=func_require_buildreq_autobuild -func_require_buildreq_autobuild () -{ - $debug_cmd - - $require_macro_dir - - test -f "$macro_dir/autobuild.m4" \ - || printf '%s\n' "$buildreq" |func_grep_q '^[ ]*autobuild' \ - || { - func_extract_trace AB_INIT - test -n "$func_extract_trace_result" && { - func_append buildreq 'autobuild - http://josefsson.org/autobuild/ -' - func_verbose "auto-adding 'autobuild' to build requirements" - } - } - - require_buildreq_autobuild=: -} - - -# require_buildreq_autoconf -# require_buildreq_autopoint -# require_buildreq_libtoolize -# --------------------------- -# Try to find the minimum compatible version of autoconf/libtool -# required to bootstrap successfully, and add it to '$buildreq'. -for tool in autoconf libtoolize autopoint; do - b=$tool - v=require_buildreq_${tool} - f=func_$v - case $tool in - autoconf) m=AC_PREREQ ;; - libtoolize) m=LT_PREREQ; b=libtool ;; - autopoint) m=AM_GNU_GETTEXT_VERSION b=gettext ;; - esac - - eval $v'='$f' - '$f' () - { - $debug_cmd - - # The following is ignored if undefined, but might be necessary - # in order for `func_find_tool` to run. - ${require_'$tool'-:} - - printf '\''%s\n'\'' "$buildreq" |func_grep_q '\''^[ ]*'$tool\'' || { - func_extract_trace '$m' - _G_version=$func_extract_trace_result - test -n "$_G_version" && { - func_append buildreq "\ - '$tool' $_G_version http://www.gnu.org/s/'$b' -" - func_verbose \ - "auto-adding '\'$tool'-'$_G_version\'' to build requirements" - } - } - - '$v'=: - } -' -done - - -# require_buildreq_automake -# ------------------------- -# Try to find the minimum compatible version of automake required to -# bootstrap successfully, and add it to '$buildreq'. -require_buildreq_automake=func_require_buildreq_automake -func_require_buildreq_automake () -{ - $debug_cmd - - # if automake is not already listed in $buildreq... - printf '%s\n' "$buildreq" |func_grep_q automake || { - func_extract_trace AM_INIT_AUTOMAKE - - # ...and AM_INIT_AUTOMAKE is declared... - test -n "$func_extract_trace_result" && { - automake_version=`$bs_echo "$func_extract_trace_result" \ - |$SED -e 's|[^0-9]*||' -e 's| .*$||'` - test -n "$automake_version" || automake_version=- - - func_append buildreq "\ - automake $automake_version http://www.gnu.org/s/automake -" - func_verbose \ - "auto-adding 'automake-$automake_version' to build requirements" - } - } - - require_buildreq_automake=: -} - - -# require_buildreq_patch -# ---------------------- -# Automatically add a patch build-requirement if there are diff files -# in $local_gl_dir. -require_buildreq_patch=func_require_buildreq_patch -func_require_buildreq_patch () -{ - $debug_cmd - - # This ensures PATCH is set appropriately by the time - # func_check_versions enforces $buildreq. - $require_patch - - # If patch is not already listed in $buildreq... - printf '%s\n' "$buildreq" |func_grep_q '^[ ]*patch' || { - # The ugly find invocation is necessary to exit with non-zero - # status for old find binaries that don't support -exec fully. - if test ! -d "$local_gl_dir" \ - || find "$local_gl_dir" -name *.diff -exec false {} \; ; then : - else - func_append buildreq 'patch - http://www.gnu.org/s/patch -' - fi - } - - require_buildreq_patch=: -} - - -# require_buildtools_uptodate -# --------------------------- -# Ensure all the packages listed in BUILDREQS are available on the build -# machine at the minimum versions or better. -require_buildtools_uptodate=func_require_buildtools_uptodate -func_require_buildtools_uptodate () -{ - $debug_cmd - - $require_buildreq_autobuild - $require_buildreq_autoconf - $require_buildreq_automake - $require_buildreq_libtoolize - $require_buildreq_autopoint - $require_buildreq_patch - - test -n "$buildreq" && { - _G_error_hdr= - - func_check_versions $buildreq - $func_check_versions_result || { - test -n "$buildreq_readme" \ - && test -f "$buildreq_readme" \ - && _G_error_hdr="\ -$buildreq_readme explains how to obtain these prerequisite programs: -" - func_strtable 0 11 12 36 \ - "Program" "Min_version" "Homepage" $buildreq - func_fatal_error "$_G_error_hdr$func_strtable_result" - } - } - - require_buildtools_uptodate=: -} - - -# require_copyright_holder -# ------------------------ -# Ensure there is a sensible non-empty default value in '$copyright_holder'. -require_copyright_holder=func_require_copyright_holder -func_require_copyright_holder () -{ - $debug_cmd - - test -n "$copyright_holder" || { - copyright_holder='Free Software Foundation, Inc.' - func_warning settings "\ -Please set copyright_holder explicitly in 'bootstrap.conf'; -defaulting to '$copyright_holder'." - } - - require_copyright_holder=: -} - - -# require_dotgitmodules -# --------------------- -# Ensure we have a '.gitmodules' file, with appropriate 'gnulib' settings. -require_dotgitmodules=func_require_dotgitmodules -func_require_dotgitmodules () -{ - $debug_cmd - - $require_git - - test true = "$GIT" || { - # A gnulib entry in .gitmodules always takes precedence. - _G_path=`$GIT config --file .gitmodules submodule.gnulib.path 2>/dev/null` - - test -n "$_G_path" || { - $require_vc_ignore_files - - func_verbose "creating '.gitmodules'" - - # If the .gitmodules file doesn't exist, create it now, and mark - # it as ignored for the VCS. - test -n "$gnulib_path" || gnulib_path=gnulib - test -n "$gnulib_url" || gnulib_url=git://git.sv.gnu.org/gnulib - - { - echo '[submodule "gnulib"]' - echo " path = $gnulib_path" - echo " url = $gnulib_url" - } >> .gitmodules - - test -n "$vc_ignore_files" \ - || func_insert_if_absent ".gitmodules" $vc_ignore_files - } - } - - require_dotgitmodules=: -} - - -# require_extra_locale_categories -# ------------------------------- -# Ensure there is a default value in '$extra_locale_categories' -require_extra_locale_categories=func_require_extra_locale_categories -func_require_extra_locale_categories () -{ - $debug_cmd - - # Defaults to empty, so run with whatever value may have been set in - # 'bootstrap.conf'. - require_extra_locale_categories=: -} - - -# require_git -# ----------- -# Ignore git if it's not available, or we're not in a git checkout tree. -require_git=func_require_git -func_require_git () -{ - $debug_cmd - - $opt_skip_git && GIT=true - - test true = "$GIT" || { - if test -f .gitignore && ($GIT --version) >/dev/null 2>&1; then :; else - GIT=true - fi - } - - func_verbose "GIT='$GIT'" - - require_git=: -} - - -# require_gnulib_cache -# -------------------- -# Ensure there is a non-empty default for '$gnulib_cache', and that it -# names an existing file. -require_gnulib_cache=func_require_gnulib_cache -func_require_gnulib_cache () -{ - $debug_cmd - - $require_macro_dir - - test -n "$gnulib_cache" \ - || gnulib_cache=$macro_dir/gnulib-cache.m4 - - func_verbose "found '$gnulib_cache'" - - require_gnulib_cache=: -} - - -# require_gnulib_merge_changelog -# ------------------------------ -# See if we can use gnulib's git-merge-changelog merge driver. -require_gnulib_merge_changelog=func_require_gnulib_merge_changelog -func_require_gnulib_merge_changelog () -{ - $debug_cmd - - test -f ChangeLog && { - $require_git - - func_grep_q '^\(/\|\)ChangeLog$' .gitignore || test true = "$GIT" || { - if $GIT config merge.merge-changelog.driver >/dev/null; then - : - elif (git-merge-changelog --version) >/dev/null 2>&1; then - func_echo "initializing git-merge-changelog driver" - $GIT config merge.merge-changelog.name 'GNU-style ChangeLog merge driver' - $GIT config merge.merge-changelog.driver 'git-merge-changelog %O %A %B' - else - func_warning recommend \ - "Consider installing git-merge-changelog from gnulib." - fi - } - } - - require_gnulib_merge_changelog=: -} - - -# require_gnulib_mk -# ----------------- -# Ensure gnulib_mk has a sensible value, extracted from 'gnulib-cache.m4' -# if possible, otherwise letting 'gnulib-tool' pick a default. -require_gnulib_mk=func_require_gnulib_mk -func_require_gnulib_mk () -{ - $debug_cmd - - test -f "$gnulib_cache" && test -z "$gnulib_mk" && { - $require_gnulib_cache - $require_macro_dir - - func_extract_trace_first "gl_MAKEFILE_NAME" "$gnulib_cache" - gnulib_mk=$func_extract_trace_first_result - - test -n "$gnulib_mk" && func_verbose "gnulib_mk='$gnulib_mk'" - } - - require_gnulib_mk=: -} - - -# require_gnulib_path -# require_gnulib_url -# ------------------- -# Ensure 'gnulib_path' and 'gnulib_url' are set. -require_gnulib_path=func_require_dotgitmodules_parameters -require_gnulib_url=func_require_dotgitmodules_parameters -func_require_dotgitmodules_parameters () -{ - $debug_cmd - - $require_git - - test true = "$GIT" && { - # If we can't find git (or if the user specified '--skip-git'), - # then use an existing gnulib directory specified with - # '--gnulib-srcdir' if possible. - test -n "$gnulib_path" \ - || test ! -x "$opt_gnulib_srcdir/gnulib-tool" \ - || gnulib_path=$opt_gnulib_srcdir - } - - - $require_dotgitmodules - - test -f .gitmodules && { - # Extract the parameters with sed, since git may be missing - test -n "$gnulib_path" \ - || gnulib_path=`$SED -e '/^.submodule "gnulib".$/,${ - /[ ]*path *= */{ - s|[ ]*||g;s|^[^=]*=||;p - } - } - d' .gitmodules |$SED 1q` - test -n "$gnulib_url" \ - || gnulib_url=`$SED -e '/^.submodule "gnulib".$/,${ - /[ ]*url *= */{ - s|[ ]*||g;s|^[^=]*=||;p - } - } - d' .gitmodules |$SED 1q` - - func_verbose "gnulib_path='$gnulib_path'" - func_verbose "gnulib_url='$gnulib_url'" - } - - require_gnulib_path=: - require_gnulib_url=: -} - - -# require_gnulib_submodule -# ------------------------ -# Ensure that there is a current gnulib submodule at '$gnulib_path'. -require_gnulib_submodule=func_require_gnulib_submodule -func_require_gnulib_submodule () -{ - $debug_cmd - - $require_git - - if test true = "$GIT"; then - func_warning recommend \ - "No 'git' found; imported gnulib modules may be outdated." - else - $require_gnulib_path - $require_gnulib_url - - if test -f .gitmodules && test -f "$gnulib_path/gnulib-tool"; then - : All present and correct. - - elif test -n "$opt_gnulib_srcdir"; then - # Older git can't clone into an empty directory. - rmdir "$gnulib_path" 2>/dev/null - func_show_eval "$GIT clone --reference '$opt_gnulib_srcdir' \ - '$gnulib_url' '$gnulib_path'" \ - || func_fatal_error "Unable to fetch gnulib submodule." - - # Without --gnulib-srcdir, and no existing checked out submodule, we - # create a new shallow clone of the remote gnulib repository. - else - trap func_cleanup_gnulib 1 2 13 15 - - shallow= - $GIT clone -h 2>&1 |func_grep_q -- --depth \ - && shallow='--depth 365' - - func_show_eval "$GIT clone $shallow '$gnulib_url' '$gnulib_path'" \ - func_cleanup_gnulib - - # FIXME: Solaris /bin/sh will try to execute '-' if any of - # these signals are caught after this. - trap - 1 2 13 15 - fi - - # Make sure we've checked out the correct revision of gnulib. - func_show_eval "$GIT submodule init" \ - && func_show_eval "$GIT submodule update" \ - || func_fatal_error "Unable to update gnulib submodule." - fi - - require_gnulib_submodule=: -} - - -# require_gnulib_tool -# ------------------- -# Ensure that '$gnulib_tool' is set, and points to an executable file, -# or else fall back to using the binary 'true' if the main gnulib -# files appear to have been imported already. -require_gnulib_tool=func_require_gnulib_tool -func_require_gnulib_tool () -{ - $debug_cmd - - test true = "$gnulib_tool" || { - $require_gnulib_submodule - $require_gnulib_path - - test -n "$gnulib_tool" \ - || gnulib_tool=$gnulib_path/gnulib-tool - - test -x "$gnulib_tool" || { - gnulib_tool=true - func_warning recommend \ - "No 'gnulib-tool' found; gnulib modules may be missing." - } - - test true = "$gnulib_tool" \ - || func_verbose "found '$gnulib_tool'" - } - - require_gnulib_tool=: -} - - -# require_gnulib_tool_base_options -# -------------------------------- -# Ensure that '$gnulib_tool_base_options' contains all the base options -# required according to user configuration from bootstrap.conf. -require_gnulib_tool_base_options=func_require_gnulib_tool_base_options -func_require_gnulib_tool_base_options () -{ - $debug_cmd - - $require_gnulib_tool - - gnulib_tool_base_options= - - test true = "$gnulib_tool" || { - $require_build_aux - $require_macro_dir - - # 'gnulib_modules' and others are maintained in 'bootstrap.conf': - # Use 'gnulib --import' to fetch gnulib modules. - test -n "$build_aux" \ - && func_append_uniq gnulib_tool_base_options " --aux-dir=$build_aux" - test -n "$macro_dir" \ - && func_append_uniq gnulib_tool_base_options " --m4-base=$macro_dir" - test -n "$doc_base" \ - && func_append_uniq gnulib_tool_base_options " --doc-base=$doc_base" - test -n "$gnulib_name" \ - && func_append_uniq gnulib_tool_base_options " --lib=$gnulib_name" - test -n "$local_gl_dir" \ - && func_append_uniq gnulib_tool_base_options " --local-dir=$local_gl_dir" - test -n "$source_base" \ - && func_append_uniq gnulib_tool_base_options " --source-base=$source_base" - } - - require_gnulib_tool_base_options=: -} - - -# require_libtoolize -# ------------------ -# Skip libtoolize if it's not needed. -require_libtoolize=func_require_libtoolize -func_require_libtoolize () -{ - $debug_cmd - - # Unless we're not searching for libtool use by this package, set - # LIBTOOLIZE to true if none of 'LT_INIT', 'AC_PROG_LIBTOOL' and - # 'AM_PROG_LIBTOOL' are used in configure. - test true = "$LIBTOOLIZE" || { - func_extract_trace LT_INIT - test -n "$func_extract_trace_result" || func_extract_trace AC_PROG_LIBTOOL - test -n "$func_extract_trace_result" || func_extract_trace AM_PROG_LIBTOOL - test -n "$func_extract_trace_result" || LIBTOOLIZE=true - } - - test -n "$LIBTOOLIZE" || { - # Find libtoolize, named glibtoolize in Mac Ports, but prefer - # user-installed libtoolize to ancient glibtoolize shipped by - # Apple with Mac OS X when Mac Ports is not installed. - func_find_tool LIBTOOLIZE libtoolize glibtoolize - } - - func_verbose "export LIBTOOLIZE='$LIBTOOLIZE'" - - # Make sure the search result is visible to subshells - export LIBTOOLIZE - - require_libtoolize=: -} - - -# require_macro_dir -# ----------------- -# Ensure that '$macro_dir' is set, and if it doesn't already point to an -# existing directory, create one. -require_macro_dir=func_require_macro_dir -func_require_macro_dir () -{ - $debug_cmd - - # Sometimes this is stored in 'configure.ac'. - test -n "$macro_dir" || { - # AC_CONFIG_MACRO_DIRS takes a space delimited list of directories, - # but we only care about the first one in bootstrap. - func_extract_trace_first AC_CONFIG_MACRO_DIRS - macro_dir=`expr "x$func_extract_trace_first_result" : 'x\([^ ]*\)'` - } - test -n "$macro_dir" || { - func_extract_trace_first AC_CONFIG_MACRO_DIR - macro_dir=$func_extract_trace_first_result - } - - # Otherwise we might find it in 'Makefile.am'. - test -n "$macro_dir" || { - $require_aclocal_amflags - - # Take the argument following the first '-I', if any. - _G_minus_I_seen=false - for _G_arg in $aclocal_amflags; do - case $_G_minus_I_seen,$_G_arg in - :,*) macro_dir=$_G_arg; break ;; - *,-I) _G_minus_I_seen=: ;; - *,-I*) macro_dir=`expr x$_G_arg : 'x-I\(.*\)$'`; break ;; - esac - done - } - - func_verbose "macro_dir='$macro_dir'" - - func_check_configuration macro_dir \ - "AC_CONFIG_MACRO_DIRS([name of a directory for configure m4 files])" - - $require_vc_ignore_files - - # If the macro_dir directory doesn't exist, create it now, and mark it - # as ignored for the VCS. - if test ! -d "$macro_dir"; then - mkdir "$macro_dir" || func_permissions_error "$macro_dir" - - test -n "$vc_ignore_files" \ - || func_insert_if_absent "$macro_dir" $vc_ignore_files - fi - - require_macro_dir=: -} - - -# require_makefile_am -# ------------------- -# Ensure there is a 'Makefile.am' in the current directory. -# names an existing file. -require_makefile_am=func_require_makefile_am -func_require_makefile_am () -{ - $debug_cmd - - test -n "$makefile_am" \ - || makefile_am=Makefile.am - - <"$makefile_am" - - func_verbose "found '$makefile_am'" - - require_makefile_am=: -} - - -# require_package -# --------------- -# Ensure that '$package' contains a sensible default value. -require_package=func_require_package -func_require_package () -{ - $debug_cmd - - test -n "$package" || { - $require_package_name - - package=`echo "$package_name" \ - |$SED -e 's/GNU //' \ - -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'` - } - - func_verbose "package='$package'" - - require_package=: -} - - -# require_package_bugreport -# ------------------------- -# Ensure that this has a sensible value, extracted from 'configure.ac' -# if appropriate (and possible!). -require_package_bugreport=func_require_package_bugreport -func_require_package_bugreport () -{ - $debug_cmd - - func_extract_trace AC_INIT - - save_ifs=$IFS - IFS=: - set dummy $func_extract_trace_result - IFS=$save_ifs - shift - - test -n "$package_bugreport" || package_bugreport=$3 - func_check_configuration package_bugreport \ - "AC_INIT([$package_name], [$package_version], [bug-$package@gnu.org])" - func_verbose "package_bugreport='$package_bugreport'" - - require_package_bugreport=: -} - - -# require_package_name -# -------------------- -# Ensure that this has a sensible value, extracted from 'configure.ac' -# if appropriate (and possible!). -require_package_name=func_require_package_name -func_require_package_name () -{ - $debug_cmd - - func_extract_trace AC_INIT - - save_ifs=$IFS - IFS=: - set dummy $func_extract_trace_result - IFS=$save_ifs - shift - - test -n "$package_name" || package_name=$1 - func_check_configuration package_name \ - "AC_INIT([name of your package], [package version number])" - func_verbose "package_name='$package_name'" - - require_package_name=: -} - - -# require_package_version -# ----------------------- -# Ensure that this has a sensible value, extracted from 'configure.ac' -# if appropriate (and possible!). While we might have set all the -# parameters extracted from AC_INIT at once, 'package_version' in -# particular is not necessarily available as early as the others, since -# 'git-version-gen' is often involved, and until then we can't rely on -# getting a correct version number from an AC_INIT extraction. -require_package_version=func_require_package_version -func_require_package_version () -{ - $debug_cmd - - func_extract_trace AC_INIT - - save_ifs=$IFS - IFS=: - set dummy $func_extract_trace_result - IFS=$save_ifs - shift - - test -n "$package_version" || package_version=$2 - test -n "$package_version" || { - # The embedded echo is to squash whitespace before globbing. - case " "`echo $gnulib_modules`" " in - *" git-version-gen "*) - func_fatal_error "\ -cannot \$require_package_version in bootstrap.conf before -func_gnulib_tool has installed the 'git-version-gen' script." - ;; - *) - func_check_configuration package_version \ - "AC_INIT([name of your package], [package version number])" - ;; - esac - } - func_verbose "package_version='$package_version'" - - require_package_version=: -} - - -# require_patch -# ------------- -# Find patch, according to the PATCH environment variable, or else -# searching the user's PATH. -require_patch=func_require_patch -func_require_patch () -{ - $debug_cmd - - test -n "$PATCH" || { - # Find a patch program, preferring gpatch, which is usually better - # than the vendor patch. - func_find_tool PATCH gpatch patch - } - - func_verbose "export PATCH='$PATCH'" - - # Make sure the search result is visible to subshells - export PATCH - - require_patch=: -} - - -# require_source_base -# ------------------- -# Ensure that source_base has a sensible value, extracted from -# 'gnulib-cache.m4' if possible. -require_source_base=func_require_source_base -func_require_source_base () -{ - $debug_cmd - - $require_gnulib_cache - - test -f "$gnulib_cache" && test -z "$source_base" && { - $require_macro_dir - - func_extract_trace_first "gl_SOURCE_BASE" "$gnulib_cache" - - source_base=$func_extract_trace_first_result - - func_verbose "source_base='$source_base'" - } - - require_source_base=: -} - - -# require_vc_ignore_files -# ----------------------- -# Ensure that '$vc_ignore' has been processed to list VCS ignore files -# in '$vc_ignore_files' -require_vc_ignore_files=func_require_vc_ignore_files -func_require_vc_ignore_files () -{ - $debug_cmd - - test -n "$vc_ignore" || vc_ignore=auto - - if test auto = "$vc_ignore" && test -z "$vc_ignore_files"; then - vc_ignore_files= - test -d .git && vc_ignore_files=.gitignore - test -d CVS && vc_ignore_files="$vc_ignore_files .cvsignore" - else - vc_ignore_files=$vc_ignore - fi - - func_verbose "vc_ignore_files='$vc_ignore_files'" - - require_vc_ignore_files=: -} - - -## ------------------## -## Helper functions. ## -## ------------------## - -# This section contains the helper functions used by the rest of 'bootstrap'. - -# func_len STRING -# --------------- -# STRING may not start with a hyphen. -if (eval 'x=123; test x${#x} = "x3"') 2>/dev/null -then - # This is an XSI compatible shell, allowing a faster implementation... - eval 'func_len () - { - $debug_cmd - - func_len_result=${#1} - }' -else - # ...otherwise fall back to using expr, which is often a shell builtin. - func_len () - { - $debug_cmd - - func_len_result=`expr "$1" : ".*" 2>/dev/null || echo 0` - } -fi - - -# func_unset VAR -# -------------- -# Portably unset VAR. -# In some shells, an 'unset VAR' statement leaves a non-zero return -# status if VAR is already unset, which might be problematic if the -# statement is used at the end of a function (thus poisoning its return -# value) or when 'set -e' is active (causing even a spurious abort of -# the script in this case). -func_unset () -{ - { eval $1=; unset $1; } -} -unset=func_unset - - -# func_cmp_s FILE1 FILE2 -# ---------------------- -# Return non-zero exit status unless FILE1 and FILE2 are identical, without -# any output at all, even error messages. -func_cmp_s () -{ - $debug_cmd - - # This function relies on non-zero exit status, which will cause the - # program to exit when running in 'set -e' mode. - $CMP "$@" >/dev/null 2>&1 -} - - -# func_grep_q EXPRESSION [FILENAME..] -# ----------------------------------- -# Check whether EXPRESSION matches any line of any listed FILENAME, -# without any output at all, even error messages. -func_grep_q () -{ - $debug_cmd - - # This function relies on non-zero exit status, which will cause the - # program to exit when running in 'set -e' mode. - $GREP "$@" >/dev/null 2>&1 -} - - -# func_ifcontains LIST MEMBER YES-CMD [NO-CMD] -# -------------------------------------------- -# If whitespace-separated LIST contains MEMBER then execute YES-CMD, -# otherwise if NO-CMD was give, execute that. -func_ifcontains () -{ - $debug_cmd - - # The embedded echo is to squash whitespace before globbing. - _G_wslist=`$bs_echo " "$1" "` - _G_member=$2 - _G_yes_cmd=$3 - _G_no_cmd=${4-":"} - - case $_G_wslist in - *" $_G_member "*) - eval "$_G_yes_cmd" - _G_status=$? - ;; - *) - eval "$_G_no_cmd" - _G_status=$? - ;; - esac - - test 0 -eq "$_G_status" || exit $_G_status -} - - -# func_strpad STR WIDTH CHAR -# -------------------------- -# Trim STR, or pad with CHAR to force a total length of WIDTH. -func_strpad () -{ - $debug_cmd - - _G_width=`expr "$2" - 1` - func_strpad_result=`$bs_echo "$1" |$SED ' - :a - s|^.\{0,'"$_G_width"'\}$|&'"$3"'| - ta - '` -} - - -# func_strrpad STR WIDTH CHAR -# --------------------------- -# Trim STR, or right-justify-pad with CHAR to force a total length of -# WIDTH. -func_strrpad () -{ - $debug_cmd - - _G_width=`expr "$2" - 1` - func_strrpad_result=`$bs_echo "$1" |$SED ' - :a - s|^.\{0,'"$_G_width"'\}$|'"$3"'&| - ta - '` -} - - -# func_strrow INDENT FIELD WIDTH [FIELDn WIDTHn]... -# ------------------------------------------------- -# Return a string containing each FIELD left justified to WIDTH, with -# the whole thing indented by INDENT spaces. This function is used to -# render one row of aligned columns for a table by func_strtable(). -func_strrow () -{ - $debug_cmd - - func_strrow_linelen=$1; shift - - _G_row= - while test $# -gt 0; do - func_strrow_linelen=`expr $func_strrow_linelen + $2` - func_strpad "$1" $2 " " - func_append _G_row "$func_strpad_result" - shift; shift - done - - func_strrpad "$_G_row" $func_strrow_linelen " " - func_strrow_result=$func_strrpad_result -} - - -# func_strtable INDENT WIDTH1...WIDTHn HEADER1...HEADERn FIELD1...FIELDn -# ---------------------------------------------------------------------- -# Generate a string of newline-separated rows arranged in lined-up -# columns of the given WIDTHs, with the entire table indented by INDENT -# spaces. The number of columns is determined by the number of integer -# valued WIDTH arguments following INDENT. The next set (i.e. a number -# of arguments equal to the number of WIDTH arguments) of fields are -# treated as the table's column HEADERs, and are separated from the -# remainder of the table by an indented row of '-' characters. Remaining -# arguments are each aligned below the next available header, wrapping -# to a new row as necessary. Finally another row of '-' characters is -# added to mark the end of the table. -# -# For example an unindented 3 column table with 2 rows of data would be -# generated by this call: -# -# func_strtable 3 20 10 25 \ -# Header1 Header2 Header3 \ -# Row1Col1 Row1Col2 Row1Col3 \ -# Row2Col1 Row2Col2 Row2Col3 -# -# returning the following string: -# -# " Header1 Header2 Header3 -# ------------------------------------------------------- -# Row1Col1 Row1Col2 Row1Col3 -# Row2Col1 Row2Col2 Row2Col3 -# -------------------------------------------------------" -func_strtable () -{ - $debug_cmd - - # Save the indent value, we'll need it for each row we render. - _G_indent=$1; shift - - # Collect remaining numeric args into a list for reuse between - # members of each row when we call func_strrow later. - _G_widths=$1; shift - while test 0 -lt `expr "$1" : '[1-9][0-9]*$'`; do - func_append _G_widths " $1"; shift - done - - # Extract the same number of positional parameters as there are - # width elements - we'll do the header rows separately so that - # we can insert a divider line. - _G_header=$_G_indent - for _G_width in $_G_widths; do - func_append _G_header " $1 $_G_width"; shift - done - func_strrow $_G_header - - # Strip off the indent, and make a divider with '-' chars, then - # reindent. - _G_divider=`$bs_echo "$func_strrow_result" \ - |$SED 's|[^ ]|-|g - :a - s|- |--|g - ta - '` - - # Append the header and divider to the running result. - func_append func_strtable_result "\ -$func_strrow_result -$_G_divider -" - - # The remaining rows are zipped between the width values we - # unwound earlier just like the header row above. - while test $# -gt 0; do - _G_row=$_G_indent - for _G_width in $_G_widths; do - func_append _G_row " $1 $_G_width"; shift - done - func_strrow $_G_row - func_append func_strtable_result "\ -$func_strrow_result -" - done - - # Mark the end of the table with a final divider line. - func_append func_strtable_result "$_G_divider" -} - - -# func_internal_error ARG... -# -------------------------- -# Echo program name prefixed message to standard error, and exit. -func_internal_error () -{ - func_fatal_error "\ -INTERNAL: " ${1+"$@"} " - Please report this bug to 'bug-gnulib@gnu.org' - in as much detail as possible." -} - - -# func_permissions_error FILE-OR-DIRECTORY -# ---------------------------------------- -# Echo program name prefixed permissions error message to standard -# error, and exit. -func_permissions_error () -{ - $debug_cmd - - func_fatal_error "Failed to create '$1', check permissions." -} - - -# func_show_eval CMD [FAIL_EXP] -# ----------------------------- -# Unless opt_silent is true, then output CMD. Then, if opt_dryrun is -# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP -# is given, then evaluate it. -func_show_eval () -{ - $debug_cmd - - $require_term_colors - - _G_cmd=$1 - _G_fail_exp=${2-':'} - - ${opt_silent-'false'} || { - func_quote_for_eval $_G_cmd - eval func_truncate_cmd $func_quote_for_eval_result - func_echo "running: $tc_bold$func_truncate_cmd_result$tc_reset" - } - - ${opt_dry_run-'false'} || { - eval "$_G_cmd" - _G_status=$? - test 0 -eq "$_G_status" || eval "(exit $_G_status); $_G_fail_exp" - } -} - - -# func_truncate_cmd CMD [ARG]... -# ------------------------------ -# For unreasonably long commands (such as a gnulib-tool invocation with -# the full module list for import), truncate CMD after the second non- -# option ARG. -func_truncate_cmd () -{ - $debug_cmd - - _G_last_arg_opt_p=false - func_truncate_cmd_result= - - set dummy "$@"; shift - - while test $# -gt 0; do - _G_opt=$1; shift - - test -n "$func_truncate_cmd_result" \ - && func_append func_truncate_cmd_result ' ' - func_append func_truncate_cmd_result "$_G_opt" - - func_len "x$func_truncate_cmd_result" - - case $_G_opt in - -*) _G_last_arg_opt_p=: ;; - *) $_G_last_arg_opt_p \ - || test "$min_cmd_len" -gt "$func_len_result" \ - || break - _G_last_arg_opt_p=false - ;; - esac - done - - test $# -gt 0 && func_append func_truncate_cmd_result "..." -} - - -# func_gitignore_entries FILE... -# ------------------------------ -# Strip blank and comment lines to leave significant entries. -func_gitignore_entries () -{ - $debug_cmd - - sed -e '/^#/d' -e '/^$/d' "$@" -} - - -# func_insert_if_absent STR FILE... -# --------------------------------- -# If $STR is not already on a line by itself in $FILE, insert it, at the -# start. Entries are inserted at the start of the ignore list to ensure -# existing entries starting with ! are not overridden. Such entries -# support whilelisting exceptions after a more generic blacklist pattern. -# sorting the new contents of the file and replacing $FILE with the result. -func_insert_if_absent () -{ - $debug_cmd - - str=$1 - shift - - for file - do - test -f "$file" || touch "$file" - - duplicate_entries=`func_gitignore_entries "$file" |sort |uniq -d` - test -n "$duplicate_entries" \ - && func_error "duplicate entries in $file: " $duplicate_entries - - func_grep_q "^$str\$" "$file" \ - || func_verbose "inserting '$str' into '$file'" - - linesold=`func_gitignore_entries "$file" |wc -l` - linesnew=`{ $bs_echo "$str"; cat "$file"; } \ - |func_gitignore_entries |sort -u |wc -l` - test "$linesold" -eq "$linesnew" \ - || { sed "1i\\$nl$str$nl" "$file" >"$file"T && mv "$file"T "$file"; } \ - || func_permissions_error "$file" - done -} - - -# func_sort_ver VER1 VER2 -# ----------------------- -# 'sort -V' is not generally available. -# Note this deviates from the version comparison in automake -# in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a -# but this should suffice as we won't be specifying old -# version formats or redundant trailing .0 in bootstrap.conf. -# If we did want full compatibility then we should probably -# use m4_version_compare from autoconf. -func_sort_ver () -{ - $debug_cmd - - ver1=$1 - ver2=$2 - - # Split on '.' and compare each component. - i=1 - while :; do - p1=`echo "$ver1" |cut -d. -f$i` - p2=`echo "$ver2" |cut -d. -f$i` - if test ! "$p1"; then - echo "$1 $2" - break - elif test ! "$p2"; then - echo "$2 $1" - break - elif test ! "$p1" = "$p2"; then - if test "$p1" -gt "$p2" 2>/dev/null; then # numeric comparison - echo "$2 $1" - elif test "$p2" -gt "$p1" 2>/dev/null; then # numeric comparison - echo "$1 $2" - else # numeric, then lexicographic comparison - lp=`printf "$p1\n$p2\n" |sort -n |tail -n1` - if test "$lp" = "$p2"; then - echo "$1 $2" - else - echo "$2 $1" - fi - fi - break - fi - i=`expr $i + 1` - done -} - - -# func_get_version APP -# -------------------- -# echo the version number (if any) of APP, which is looked up along your -# PATH. -func_get_version () -{ - $debug_cmd - - _G_app=$1 - - # Rather than uncomment the sed script in-situ, strip the comments - # programatically before passing the result to $SED for evaluation. - sed_get_version=`$bs_echo '# extract version within line - s|.*[v ]\{1,\}\([0-9]\{1,\}\.[.a-z0-9-]*\).*|\1| - t done - - # extract version at start of line - s|^\([0-9]\{1,\}\.[.a-z0-9-]*\).*|\1| - t done - - d - - :done - # the following essentially does s|5.005|5.5| - s|\.0*\([1-9]\)|.\1|g - p - q' \ - |$SED '/^[ ]*#.*$/d'` - - func_tool_version_output $_G_app >/dev/null - _G_status=$? - - test 0 -ne "$_G_status" \ - || $_G_app --version 2>&1 |$SED -n "$sed_get_version" - - (exit $_G_status) -} - - -# func_check_tool APP -# ------------------- -# Search PATH for an executable at APP. -func_check_tool () -{ - $debug_cmd - - func_check_tool_result= - - case $1 in - *[\\/]*) - test -x "$1" && func_check_tool_result=$1 - ;; - *) - save_IFS=$IFS - IFS=: - for _G_check_tool_path in $PATH; do - IFS=$save_IFS - if test -x "$_G_check_tool_path/$1"; then - func_check_tool_result=$_G_check_tool_path/$1 - break - fi - done - IFS=$save_IFS - ;; - esac -} - - -# func_check_versions APP1 VER1 URL1 ...[APPN VERN URLN] -# ------------------------------------------------------ -func_check_versions () -{ - $debug_cmd - - func_check_versions_result=: - - while test $# -gt 0; do - _G_app=$1; shift - _G_reqver=$1; shift - _G_url=$1; shift - - # Diagnose bad buildreq formatting. - case $_G_url in - [a-z]*://*) ;; # looks like a url - *) func_fatal_error "\ -'$_G_url' from the buildreq table in -'bootstrap.conf' does not look like the URL for downloading -$_G_app. Please ensure that buildreq is a strict newline -delimited list of triples; 'program min-version url'." - ;; - esac - - # Honor $APP variables ($TAR, $AUTOCONF, etc.) - _G_appvar=`echo $_G_app |tr '[a-z]' '[A-Z]'` - test TAR = "$_G_appvar" && _G_appvar=AMTAR - eval "_G_app=\${$_G_appvar-$_G_app}" - - # Fail if no version specified, but the program can't be found. - if test x- = "x$_G_reqver"; then - func_check_tool $_G_app - if test -z "$func_check_tool_result"; then - func_error "Prerequisite '$_G_app' not not found. Please install it, or -'export $_G_appvar=/path/to/$_G_app'." - func_check_versions_result=false - else - func_verbose "found '$func_check_tool_result' for $_G_appvar." - fi - else - _G_instver=`func_get_version $_G_app` - - test -z "$_G_instver" \ - || func_verbose "found '$_G_app' version $_G_instver." - - # Fail if --version didn't work. - if test -z "$_G_instver"; then - func_error "Prerequisite '$_G_app' not found. Please install it, or -'export $_G_appvar=/path/to/$_G_app'." - func_check_versions_result=false - - # Fail if a newer version than what we have is required. - else - _G_newer=`func_sort_ver $_G_reqver $_G_instver |cut -d' ' -f2` - test "$_G_newer" != "$_G_instver" && { - func_error "\ - '$_G_app' version == $_G_instver is too old - '$_G_app' version >= $_G_reqver is required" - func_check_versions_result=false - } - fi - fi - done -} - - -# func_cleanup_gnulib -# ------------------- -# Recursively delete everything below the path in the global variable -# GNULIB_PATH. -func_cleanup_gnulib () -{ - $debug_cmd - - _G_status=$? - $RM -fr "$gnulib_path" - exit $_G_status -} - - -# func_download_po_files SUBDIR DOMAIN -# ------------------------------------ -func_download_po_files () -{ - $debug_cmd - - func_echo "getting translations into $1 for $2..." - _G_cmd=`printf "$po_download_command_format" "$2" "$1"` - eval "$_G_cmd" -} - - -# func_update_po_files PO_DIR DOMAIN -# ---------------------------------- -# Mirror .po files to $po_dir/.reference and copy only the new -# or modified ones into $po_dir. Also update $po_dir/LINGUAS. -# Note po files that exist locally only are left in $po_dir but will -# not be included in LINGUAS and hence will not be distributed. -func_update_po_files () -{ - $debug_cmd - - # Directory containing primary .po files. - # Overwrite them only when we're sure a .po file is new. - _G_po_dir=$1 - _G_domain=$2 - - # Mirror *.po files into this dir. - # Usually contains *.s1 checksum files. - _G_ref_po_dir=$_G_po_dir/.reference - - test -d "$_G_ref_po_dir" || mkdir $_G_ref_po_dir || return - func_download_po_files $_G_ref_po_dir $_G_domain \ - && ls "$_G_ref_po_dir"/*.po 2>/dev/null \ - |$SED -e 's|.*/||' -e 's|\.po$||' > "$_G_po_dir/LINGUAS" || return - - # Find sha1sum, named gsha1sum on MacPorts, and shasum on MacOS 10.6+. - func_find_tool SHA1SUM sha1sum gsha1sum shasum sha1 - - _G_langs=`cd $_G_ref_po_dir && echo *.po|$SED 's|\.po||g'` - test '*' = "$_G_langs" && _G_langs=x - for _G_po in $_G_langs; do - case $_G_po in x) continue;; esac - _G_new_po=$_G_ref_po_dir/$_G_po.po - _G_cksum_file=$_G_ref_po_dir/$_G_po.s1 - if ! test -f "$_G_cksum_file" || - ! test -f "$_G_po_dir/$_G_po.po" || - ! $SHA1SUM -c "$_G_cksum_file" \ - < "$_G_new_po" > /dev/null; then - echo "updated $_G_po_dir/$_G_po.po..." - cp "$_G_new_po" "$_G_po_dir/$_G_po.po" \ - && $SHA1SUM < "$_G_new_po" > "$_G_cksum_file" || return - fi - done -} - - - -## --------------- ## -## Option parsing. ## -## --------------- ## - -# Hook in the functions to make sure our own options are parsed during -# the option parsing loop. - -usage='$progpath [OPTION]...' - -# Short help message in response to '-h'. Add to this in 'bootstrap.conf' -# if you accept any additional options. -usage_message="Common Bootstrap Options: - -c, --copy copy files instead of creating symbolic links. - --debug enable verbose shell tracing - -n, --dry-run print commands rather than running them - -f, --force attempt to bootstrap even if the sources seem not - to have been checked out. - --gnulib-srcdir=DIRNAME - specify a local directory where gnulib sources - reside. Use this if you already have the gnulib - sources on your machine, and don't want to waste - your bandwidth downloading them again. Defaults to - \$GNULIB_SRCDIR. - --no-warnings equivalent to '-Wnone' - --skip-git do not fetch files from remote repositories - --skip-po do not download po files. - -v, --verbose verbosely report processing - --version print version information and exit - -W, --warnings=CATEGORY - report the warnings falling in CATEGORY [all] - -h, --help print short or long help message and exit -" - -# Additional text appended to 'usage_message' in response to '--help'. -long_help_message=$long_help_message" - 'recommend' show warnings about missing recommended packages - 'settings' show warnings about missing '$progname.conf' settings - 'upgrade' show warnings about out-dated files - -If the file '$progname.conf' exists in the same directory as this -script, its contents are read as shell variables to configure the -bootstrap. - -For build prerequisites, environment variables like \$AUTOCONF and -\$AMTAR are honored. - -Running without arguments will suffice in most cases. -" - -# Warning categories used by 'bootstrap', append others if you use them -# in your 'bootstrap.conf'. -warning_categories='recommend settings upgrade' - - -# bootstrap_options_prep [ARG]... -# ------------------------------- -# Preparation for options parsed by Bootstrap. -bootstrap_options_prep () -{ - $debug_cmd - - # Option defaults: - opt_copy=${copy-'false'} - opt_dry_run=false - opt_force=false - opt_gnulib_srcdir=$GNULIB_SRCDIR - opt_skip_git=false - opt_skip_po=false - - # Pass back the list of options we consumed. - func_quote_for_eval ${1+"$@"} - bootstrap_options_prep_result=$func_quote_for_eval_result -} -func_add_hook func_options_prep bootstrap_options_prep - - -# bootstrap_parse_options [ARG]... -# -------------------------------- -# Provide handling for Bootstrap specific options. -bootstrap_parse_options () -{ - $debug_cmd - - # Perform our own loop to consume as many options as possible in - # each iteration. - while test $# -gt 0; do - _G_opt=$1 - shift - case $_G_opt in - --dry-run|--dryrun|-n) - opt_dry_run=: ;; - --copy|-c) opt_copy=: ;; - --force|-f) opt_force=: ;; - - --gnulib-srcdir) - test $# = 0 && func_missing_arg $_G_opt && break - opt_gnulib_srcdir=$1 - shift - ;; - - --skip-git|--no-git) - opt_skip_git=: - ;; - - --skip-po|--no-po) - opt_skip_po=: - ;; - - # Separate non-argument short options: - -c*|-f*|-n*) - func_split_short_opt "$_G_opt" - set dummy "$func_split_short_opt_name" \ - "-$func_split_short_opt_arg" ${1+"$@"} - shift - ;; - - *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; - esac - done - - # save modified positional parameters for caller - func_quote_for_eval ${1+"$@"} - bootstrap_parse_options_result=$func_quote_for_eval_result -} -func_add_hook func_parse_options bootstrap_parse_options - - -# bootstrap_validate_options [ARG]... -# ----------------------------------- -# Perform any sanity checks on option settings and/or unconsumed -# arguments. -bootstrap_validate_options () -{ - $debug_cmd - - # Validate options. - test $# -gt 0 \ - && func_fatal_help "too many arguments" - - # Pass back the (empty) list of unconsumed options. - func_quote_for_eval ${1+"$@"} - bootstrap_validate_options_result=$func_quote_for_eval_result -} -func_add_hook func_validate_options bootstrap_validate_options - - -## -------------------------------------------------- ## -## Source package customisations in 'bootstrap.conf'. ## -## -------------------------------------------------- ## - -# Override the default configuration, if necessary. -# Make sure that bootstrap.conf is sourced from the current directory -# if we were invoked as "sh bootstrap". -case $0 in - */*) test -r "$0.conf" && . "$0.conf" ;; - *) test -r "$0.conf" && . ./"$0.conf" ;; -esac - - -## ------------------------------- ## -## Actually perform the bootstrap. ## -## ------------------------------- ## - -func_bootstrap ${1+"$@"} - -# The End. -exit ${exit_status-$EXIT_SUCCESS} - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-pattern: "20/scriptversion=%:y-%02m-%02d.%02H; # UTC" -# time-stamp-time-zone: "UTC" -# End: diff --git a/bootstrap.conf b/bootstrap.conf deleted file mode 100644 index 65cfaab..0000000 --- a/bootstrap.conf +++ /dev/null @@ -1,100 +0,0 @@ -# bootstrap.conf (Stdlib) version 2013-05-06 -# -# Copyright (C) 2013 Gary V. Vaughan -# Written by Gary V. Vaughan, 2013 - -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Libtool; see the file COPYING. If not, a copy -# can be downloaded from http://www.gnu.org/licenses/gpl.html, -# or obtained by writing to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -## -------------- ## -## Configuration. ## -## -------------- ## - -# List of programs, minimum versions, and software urls required to -# bootstrap, maintain and release GNU Zile. - -# Build prerequisites -buildreq=' - git 1.5.5 http://git-scm.com -' - -# List of slingshot files to link into stdlib tree before autotooling. -slingshot_files=' - .autom4te.cfg - GNUmakefile - Makefile.am - build-aux/do-release-commit-and-tag - build-aux/gitlog-to-changelog - build-aux/mkrockspecs - build-aux/release.mk - build-aux/rockspecs.mk - build-aux/sanity.mk - build-aux/specl.mk - m4/ax_compare_version.m4 - m4/ax_lua.m4 - m4/slingshot.m4 - travis.yml.in -' - -# No need to do any gnulib-tooling here. -gnulib_tool=true - -# The not-synced with gnulib warning is bogus until upstream adopts -# the saner bootstrap script. -require_bootstrap_uptodate=: - - -## -------------------------------- ## -## Source Slingshot customisations. ## -## -------------------------------- ## - -# Integrate the Slingshot submodule bootstrap. -# Make sure that bootstrap.slingshot is sourced from the current -# directory if we were invoked with "sh bootstrap". -case $0 in - */*) . "$0.slingshot" ;; - *) . ./"$0.slingshot" ;; -esac - - -## --------------- ## -## Hook functions. ## -## --------------- ## - -# stdlib_force_changelog -# ---------------------- -# Automake requires that ChangeLog exist. -stdlib_force_changelog () -{ - $debug_cmd - - echo "Autogenerated by 'make dist'" > ChangeLog || exit 1 -} -func_add_hook func_gnulib_tool stdlib_force_changelog - - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "# bootstrap.conf (Stdlib) version " -# time-stamp-format: "%:y-%02m-%02d" -# time-stamp-end: "$" -# End: diff --git a/bootstrap.slingshot b/bootstrap.slingshot deleted file mode 100644 index 20be0e7..0000000 --- a/bootstrap.slingshot +++ /dev/null @@ -1,282 +0,0 @@ -# bootstrap.slingshot (Slingshot) version 2013-05-06 -# -# Copyright (C) 2013 Gary V. Vaughan -# Written by Gary V. Vaughan, 2013 - -# This is free software; see the source for copying conditions. There is NO -# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 3 of -# the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with GNU Libtool; see the file COPYING. If not, a copy -# can be downloaded from http://www.gnu.org/licenses/gpl.html, -# or obtained by writing to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -# For your project to work with subproject slingshot out of the box, you'll -# need to commit this file to your project's repository and source it from -# bootstrap.conf. -# -# case $0 in -# */*) . "$0.slingshot" ;; -# *) . ./"$0.slingshot" ;; -# esac - - -## -------------- ## -## Configuration. ## -## -------------- ## - -# List of slingshot files to link into stdlib tree before autotooling. -slingshot_files=$slingshot_files - -# Relative path to the local slingshot submodule, and url to the upsream -# git repository. If you have a slingshot entry in your .gitmodules file, -# these values are ignored. -slingshot_path=$slingshot_path -slingshot_url=$slingshot_url - - -## ------------------ ## -## Utility functions. ## -## ------------------ ## - -# slingshot_copy FILENAME SRCDIR DESTDIR -# -------------------------------------- -# If option '--copy' was specified, or soft-linking SRCFILE to DESTFILE -# fails, then try to copy SRCFILE to DESTFILE (making sure to update the -# timestamp so that a series of files with dependencies can be copied -# in the right order that their timestamps won't trigger rebuilds). -slingshot_copy () -{ - $debug_cmd - - slingshot_srcfile=`echo "$2/$1" |sed -e 's|/\./|/|g'` - slingshot_destfile=`echo "$3/$1" |sed -e 's|/\./|/|g'` - - $opt_force || { - # Nothing to do if the files are already identical. - if func_cmp_s "$slingshot_srcfile" "$slingshot_destfile"; then - func_verbose "'$slingshot_destfile' is up to date." - return 0 - fi - } - - # Require --force to remove existing $slingshot_destfile. - $opt_force && $RM "$slingshot_destfile" - test -f "$slingshot_destfile" && { - func_warn_and_continue "'$slingshot_destfile' exists: use '--force' to overwrite" - return 0 - } - - # Be careful to support 'func_copy dir/target srcbase destbase'. - func_dirname "$slingshot_destfile" - func_mkdir_p "$func_dirname_result" - - # Copy or link according to '--copy' option. - if $opt_copy; then - slingshot_copycmd=$CP - slingshot_copy_type=copying - else - slingshot_copycmd=$LN_S - slingshot_copy_type=linking - - func_relative_path "$3" "$2" - slingshot_srcfile=$func_relative_path_result/$1 - fi - slingshot_copy_msg="$slingshot_copy_type file '$slingshot_destfile'" - $opt_verbose && \ - slingshot_copy_msg="$slingshot_copy_type $slingshot_srcfile $3" - - if $opt_dry_run || { - ( umask 0 - $slingshot_copycmd "$slingshot_srcfile" "$slingshot_destfile" - ) >/dev/null 2>&1 - } - then - echo "$slingshot_copy_msg" - else - func_error "$slingshot_copy_type '$2/$1' to '$3/' failed" - return 1 - fi -} - - -## --------------- ## -## Hook functions. ## -## --------------- ## - -# slingshot_copy_files -# -------------------- -# Update files from slingshot subproject. -slingshot_copy_files () -{ - $debug_cmd - - func_check_configuration slingshot_files - - $require_slingshot_submodule - - # Make sure we have the latest mkrockspecs - make -C slingshot build-aux/mkrockspecs - - # Update in-tree links. - for file in $slingshot_files; do - func_dirname_and_basename "./$file" - slingshot_copy "$func_basename_result" \ - "slingshot/$func_dirname_result" "$func_dirname_result" - done -} -func_add_hook func_prep slingshot_copy_files - - -## -------------------- ## -## Resource management. ## -## -------------------- ## - -# require_slingshot_dotgitmodules -# ------------------------------- -# Ensure we have a '.gitmodules' file, with appropriate 'slingshot' settings. -require_slingshot_dotgitmodules=slingshot_require_slingshot_dotgitmodules -slingshot_require_slingshot_dotgitmodules () -{ - $debug_cmd - - $require_git - - test true = "$GIT" || { - # A slingshot entry in .gitmodules always takes precedence. - _G_path=`$GIT config --file .gitmodules submodule.slingshot.path 2>/dev/null` - - test -n "$_G_path" || { - $require_vc_ignore_files - - func_verbose "adding slingshot entries to '.gitmodules'" - - test -n "$slingshot_path" || slingshot_path=slingshot - test -n "$slingshot_url" || slingshot_url=git://github.com/gvvaughan/slingshot.git - - { - echo '[submodule "slingshot"]' - echo " path=$slingshot_path" - echo " url=$slingshot_url" - } >> .gitmodules - - test -n "$vc_ignore_files" \ - || func_insert_if_absent ".gitmodules" $vc_ignore_files - } - } - - require_slingshot_dotgitmodules=: -} - - -# require_slingshot_path -# require_slingshot_url -# ---------------------- -# Ensure 'slingshot_path' and 'slingshot_url' are set. -require_slingshot_path=slingshot_require_slingshot_dotgitmodules_parameters -require_slingshot_url=slingshot_require_slingshot_dotgitmodules_parameters -slingshot_require_slingshot_dotgitmodules_parameters () -{ - $debug_cmd - - $require_git - $require_slingshot_dotgitmodules - - test -f .gitmodules \ - || func_fatal_error "Unable to update '.gitmodules' with slingshot submodule" - - test true = "$GIT" || { - slingshot_path=`$GIT config --file=.gitmodules --get submodule.slingshot.path` - slingshot_url=`$GIT config --file=.gitmodules --get submodule.slingshot.url` - - func_verbose "slingshot_path='$slingshot_path'" - func_verbose "slingshot_url='$slingshot_url'" - } - - require_slingshot_path=: - require_slingshot_url=: -} - - -# require_slingshot_submodule -# --------------------------- -# Ensure that there is a current slingshot submodule. -require_slingshot_submodule=slingshot_require_slingshot_submodule -slingshot_require_slingshot_submodule () -{ - $debug_cmd - - $require_git - - if test true = "$GIT"; then - func_warning recommend \ - "No 'git' found; imported slingshot modules may be missing." - else - $require_slingshot_dotgitmodules - - if test -f .gitmodules && test -f "slingshot/src/mkrockspecs.in" - then - : All present and correct. - - else - $require_slingshot_path - $require_slingshot_url - - trap slingshot_cleanup 1 2 13 15 - - shallow= - $GIT clone -h 2>&1 |func_grep_q -- --depth \ - && shallow='--depth 365' - - func_show_eval "$GIT clone $shallow '$slingshot_url' '$slingshot_path'" \ - slingshot_cleanup - - # FIXME: Solaris /bin/sh will try to execute '-' if any of - # these signals are caught after this. - trap - 1 2 13 15 - - # Make sure we've checked out the correct revision of slingshot. - func_show_eval "$GIT submodule init" \ - && func_show_eval "$GIT submodule update" \ - || func_fatal_error "Unable to update slingshot submodule." - fi - fi - - require_slingshot_submodule=: -} - - -# slingshot_cleanup -# ----------------- -# Recursively delete everything at $slingshot_path. -slingshot_cleanup () -{ - $debug_cmd - - $require_slingshot_path - - _G_status=$? - $RM -fr $slingshot_path - exit $_G_status -} - -# Local variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "# bootstrap.slingshot (Slingshot) version " -# time-stamp-format: "%:y-%02m-%02d" -# time-stamp-end: "$" -# End: diff --git a/build-aux/config.ld.in b/build-aux/config.ld.in new file mode 100644 index 0000000..20ee48b --- /dev/null +++ b/build-aux/config.ld.in @@ -0,0 +1,53 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] + +title = 'stdlib @PACKAGE_VERSION@ Reference' +project = 'stdlib @PACKAGE_VERSION@' +description = [[ +# Standard Lua Libraries + +This is a collection of light-weight libraries for Lua 5.1 (including +LuaJIT), 5.2 and 5.3 written in pure Lua, comprising: + +1. Enhanced and expanded versions of some core Lua functions in the + @{std} module itself; + +2. Enhanced versions of some core Lua libraries: @{std.debug}, @{std.io}, + @{std.math}, @{std.package}, @{std.string} and @{std.table}; + +## LICENSE + +The code is copyright by its respective authors, and released under the +MIT license (the same license as Lua itself). There is no warranty. +]] + +dir = '../doc' + +file = { + '../lib/std/init.lua', + '../lib/std/debug.lua', + '../lib/std/io.lua', + '../lib/std/math.lua', + '../lib/std/package.lua', + '../lib/std/string.lua', + '../lib/std/table.lua', +} + +new_type ('corefunction', 'Core_Functions', true) +new_type ('corelibrary', 'Core_Libraries', true) + +function postprocess_html(s) + s = s:gsub('

%s*Corefunction (.-)

', '

Module %1

') + s = s:gsub('

%s*Corelibrary (.-)

', '

Module %1

') + s = s:gsub('

Core_Functions

', '

Core Functions

') + s = s:gsub('

Core_Libraries

', '

Core Libraries

') + return s +end + +new_type ('init', 'Initialisation', false, 'Parameters') + +format = 'markdown' +backtick_references = false +sort = false diff --git a/configure.ac b/configure.ac deleted file mode 100644 index f2ab2d4..0000000 --- a/configure.ac +++ /dev/null @@ -1,25 +0,0 @@ -dnl Process this file with autoconf to produce a configure script - -dnl Initialise autoconf and automake -AC_INIT([lua-stdlib], [36], [http://github.com/rrthomas/lua-stdlib/issues]) -AC_CONFIG_AUX_DIR([build-aux]) -AC_CONFIG_MACRO_DIR([m4]) - -AS_BOX([Configuring AC_PACKAGE_TARNAME AC_PACKAGE_VERSION]) -echo - -AM_INIT_AUTOMAKE([-Wall foreign]) -m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) - -dnl Check for programs -AX_PROG_LUA([5.1], [5.3]) -AC_PATH_PROG([LUADOC], [luadoc], [:]) -AC_PATH_PROG([SPECL], [specl], [:]) -AC_PROG_EGREP -AC_PROG_SED - -dnl Generate output files -SPECL_MIN=8 -SS_CONFIG_TRAVIS([luadoc specl]) -AC_CONFIG_FILES([Makefile]) -AC_OUTPUT diff --git a/lib/std.lua.in b/lib/std.lua.in deleted file mode 100644 index 4821af2..0000000 --- a/lib/std.lua.in +++ /dev/null @@ -1,70 +0,0 @@ ---- Lua standard library ---
    ---
  • 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: Add tests for each function immediately after the function; --- this also helps to check module dependencies.
  • ---
  • TODO: pre-compile.
  • ---
-local version = "General Lua libraries / @VERSION@" - -for m, globally in pairs (require "std.modules") do - if globally == true then - -- Inject stdlib extensions directly into global package namespaces. - for k, v in pairs (require ("std." .. m)) do - _G[m][k] = v - end - else - _G[m] = require ("std." .. m) - end -end - --- Add io functions to the file handle metatable. -local file_metatable = getmetatable (io.stdin) -file_metatable.readlines = io.readlines -file_metatable.writelines = io.writelines - --- Maintain old global interface access points. -for _, api in ipairs { - "functional.bind", - "functional.collect", - "functional.compose", - "functional.curry", - "functional.eval", - "functional.filter", - "functional.fold", - "functional.id", - "functional.map", - "functional.memoize", - "functional.metamethod", - "functional.op", - - "io.die", - "io.warn", - - "string.assert", - "string.pickle", - "string.prettytostring", - "string.render", - "string.require_version", - "string.tostring", - - "table.pack", - "table.ripairs", - "table.totable", - - "tree.ileaves", - "tree.inodes", - "tree.leaves", - "tree.nodes", -} do - local module, method = api:match "^(.*)%.(.-)$" - _G[method] = _G[module][method] -end - -local M = { - version = version, -} - -return M diff --git a/lib/std/_base.lua b/lib/std/_base.lua new file mode 100644 index 0000000..0c623d4 --- /dev/null +++ b/lib/std/_base.lua @@ -0,0 +1,204 @@ +--[[ + General Lua Libraries for Lua 5.1, 5.2 & 5.3 + Copyright (C) 2002-2018 stdlib authors +]] +--[[-- + Prevent dependency loops with key function implementations. + + A few key functions are used in several stdlib modules; we implement those + functions in this internal module to prevent dependency loops in the first + instance, and to minimise coupling between modules where the use of one of + these functions might otherwise load a whole selection of other supporting + modules unnecessarily. + + Although the implementations are here for logistical reasons, we re-export + them from their respective logical modules so that the api is not affected + as far as client code is concerned. The functions in this file do not make + use of `argcheck` or similar, because we know that they are only called by + other stdlib functions which have already performed the necessary checking + and neither do we want to slow everything down by recheckng those argument + types here. + + This implies that when re-exporting from another module when argument type + checking is in force, we must export a wrapper function that can check the + user's arguments fully at the API boundary. +]] + + +local _ENV = require 'std.normalize' { + concat = 'table.concat', + dirsep = 'package.dirsep', + find = 'string.find', + gsub = 'string.gsub', + insert = 'table.insert', + min = 'math.min', + shallow_copy = 'table.merge', + sort = 'table.sort', + sub = 'string.sub', + table_maxn = table.maxn, + wrap = 'coroutine.wrap', + yield = 'coroutine.yield', +} + + + +--[[ ============================ ]]-- +--[[ Enhanced Core Lua functions. ]]-- +--[[ ============================ ]]-- + + +-- These come as early as possible, because we want the rest of the code +-- in this file to use these versions over the core Lua implementation +-- (which have slightly varying semantics between releases). + + +local 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 + + + +--[[ ============================ ]]-- +--[[ Shared Stdlib API functions. ]]-- +--[[ ============================ ]]-- + + +-- No need to recurse because functables are second class citizens in +-- Lua: +-- func = function() print 'called' end +-- func() --> 'called' +-- functable=setmetatable({}, {__call=func}) +-- functable() --> 'called' +-- nested=setmetatable({}, {__call=functable}) +-- nested() +-- --> stdin:1: attempt to call a table value(global 'd') +-- --> stack traceback: +-- --> stdin:1: in main chunk +-- --> [C]: in ? +local function callable(x) + if type(x) == 'function' then + return x + end + return (getmetatable(x) or {}).__call +end + + +local function catfile(...) + return concat({...}, dirsep) +end + + +local function compare(l, m) + local lenl, lenm = len(l), len(m) + for i = 1, min(lenl, lenm) do + local li, mi = tonumber(l[i]), tonumber(m[i]) + if li == nil or mi == nil then + li, mi = l[i], m[i] + end + if li < mi then + return -1 + elseif li > mi then + return 1 + end + end + if lenl < lenm then + return -1 + elseif lenl > lenm then + return 1 + end + return 0 +end + + +local function escape_pattern(s) + return (gsub(s, '[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0')) +end + + +local function invert(t) + local i = {} + for k, v in pairs(t) do + i[v] = k + end + return i +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 + yield(n) + end + end + return wrap(visit), tr +end + + +local function split(s, sep) + local r, patt = {} + if sep == '' then + patt = '(.)' + insert(r, '') + else + patt = '(.-)' ..(sep or '%s+') + end + local b, slen = 0, len(s) + while b <= slen do + local e, n, m = find(s, patt, b + 1) + insert(r, m or sub(s, b + 1, slen)) + b = n or slen + 1 + end + return r +end + + +--[[ ============= ]]-- +--[[ Internal API. ]]-- +--[[ ============= ]]-- + + +-- For efficient use within stdlib, these functions have no type-checking. +-- In debug mode, type-checking wrappers are re-exported from the public- +-- facing modules as necessary. +-- +-- Also, to provide some sanity, we mirror the subtable layout of stdlib +-- public API here too, which means everything looks relatively normal +-- when importing the functions into stdlib implementation modules. +return { + io = { + catfile = catfile, + }, + + list = { + compare = compare, + }, + + object = { + Module = Module, + mapfields = mapfields, + }, + + string = { + escape_pattern = escape_pattern, + split = split, + }, + + table = { + invert = invert, + maxn = maxn, + }, + + tree = { + leaves = leaves, + }, +} diff --git a/lib/std/base.lua b/lib/std/base.lua deleted file mode 100644 index aba7226..0000000 --- a/lib/std/base.lua +++ /dev/null @@ -1,179 +0,0 @@ ---- Shared functions. - ---- 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 = {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.
--- Usage: ---
    ---
  • prog = {< --- name = , --- [usage = ,] --- [options = { --- {{[, ...]}, , [ [, ]]}, --- ... --- },] --- [banner = ,] --- [purpose = ,] --- [notes = ] --- }
  • ---
  • The type of option argument is one of Req(uired), --- Opt(ional)
  • ---
  • The varis a descriptive name for the option argument.
  • ---
  • getopt.processargs (prog)
  • ---
  • Options take a single dash, but may have a double dash.
  • ---
  • Arguments may be given as -opt=arg or -opt arg.
  • ---
  • If an option taking an argument is given multiple times, only the --- last value is returned; missing arguments are returned as 1.
  • ---
--- getOpt, usageinfo and usage can be called directly (see --- below, and the example at the end). Set _DEBUG.std to a non-nil --- value to run the example. ---
    ---
  • TODO: Wrap all messages; do all wrapping in processargs, not --- usageinfo; use sdoc-like library (see string.format todos).
  • ---
  • TODO: Don't require name to be repeated in banner.
  • ---
  • TODO: Store version separately (construct banner?).
  • ---
- -local io = require "std.io" -local List = require "std.list" -local Object = require "std.object" -local string = require "std.string" -local table = require "std.table" - -local M = { - opt = {}, -} - -local argtype = { Opt = true, Req = true } - - ---- Perform argument processing --- @param argIn list of command-line args --- @param options options table --- @param stop_at_nonopt if true, stop option processing at first non-option --- @return table of remaining non-options --- @return table of option key-value list pairs --- @return table of error messages -local function getopt (argIn, options, stop_at_nonopt) - local noProcess = nil - local argOut, optOut, errors = {[0] = argIn[0]}, {}, {} - -- get an argument for option opt - local function getArg (o, opt, arg, oldarg) - if not argtype[o.type] then - if arg ~= nil then - table.insert (errors, "option `" .. opt .. "' doesn't take an argument") - end - else - if arg == nil and argIn[1] and - string.sub (argIn[1], 1, 1) ~= "-" then - arg = argIn[1] - table.remove (argIn, 1) - end - if arg == nil and o.type == "Req" then - table.insert (errors, "option `" .. opt .. - "' requires an argument `" .. o.var .. "'") - return nil - end - end - return arg or 1 -- make sure arg has a value - end - - local function parseOpt (opt, arg) - local o = options.name[opt] - if o ~= nil then - o = o or {name = {opt}} - optOut[o.name[1]] = optOut[o.name[1]] or {} - table.insert (optOut[o.name[1]], getArg (o, opt, arg, optOut[o.name[1]])) - else - table.insert (errors, "unrecognized option `-" .. opt .. "'") - end - end - while argIn[1] do - local v = argIn[1] - table.remove (argIn, 1) - local _, _, dash, opt = string.find (v, "^(%-%-?)([^=-][^=]*)") - local _, _, arg = string.find (v, "=(.*)$") - if not dash and stop_at_nonopt then - noProcess = true - end - if v == "--" then - noProcess = true - elseif not dash or noProcess then -- non-option - table.insert (argOut, v) - else -- option - parseOpt (opt, arg) - end - end - return argOut, optOut, errors -end - - --- Object that defines a single Option entry. -local Option = Object {_init = {"name", "desc", "type", "var"}} - ---- Options table constructor: adds lookup tables for the option names -local function makeOptions (t) - local options, name = {}, {} - local function appendOpt (v, nodupes) - local dupe = false - v = Option (v) - for s in List.elems (v.name) do - if name[s] then - dupe = true - end - name[s] = v - end - if not dupe or nodupes ~= true then - if dupe then io.warn ("duplicate option '%s'", s) end - for s in List.elems (v.name) do name[s] = v end - options = List.concat (options, {v}) - end - end - for v in List.elems (t or {}) do - appendOpt (v) - end - -- Unless they were supplied already, add version and help options - appendOpt ({{"version", "V"}, "print version information, then exit"}, - true) - appendOpt ({{"help", "h"}, "print this help, then exit"}, true) - options.name = name - return options -end - - ---- Produce usage info for the given options --- @param header header string --- @param optDesc option descriptors --- @param pageWidth width to format to [78] --- @return formatted string -local function usageinfo (header, optDesc, pageWidth) - pageWidth = pageWidth or 78 - -- Format the usage info for a single option - -- @param opt the option table - -- @return options - -- @return description - local function fmtOpt (opt) - local function fmtName (o) - return (#o > 1 and "--" or "-") .. o - end - local function fmtArg () - if opt.type == "Req" then - return "=" .. opt.var - elseif opt.type == "Opt" then - return "[=" .. opt.var .. "]" - else - return "" - end - end - local textName = List.reverse (List.map (opt.name, fmtName)) - textName[#textName] = textName[#textName] .. fmtArg () - local indent = "" - if #opt.name == 1 and #opt.name[1] > 1 then - indent = " " - end - return {indent .. table.concat ({table.concat (textName, ", ")}, ", "), - opt.desc} - end - local function sameLen (xs) - local n = math.max (unpack (List.map (xs, string.len))) - for i, v in pairs (xs) do - xs[i] = string.sub (v .. string.rep (" ", n), 1, n) - end - return xs, n - end - local function paste (x, y) - return " " .. x .. " " .. y - end - local function wrapper (w, i) - return function (s) - return string.wrap (s, w, i, 0) - end - end - local optText = "" - if #optDesc > 0 then - local cols = List.transpose (List.map (optDesc, fmtOpt)) - local width - cols[1], width = sameLen (cols[1]) - cols[2] = List.map (cols[2], wrapper (pageWidth, width + 4)) - optText = "\n\n" .. - table.concat (List.map_with (List.transpose ({sameLen (cols[1]), - cols[2]}), - paste), - "\n") - end - return header .. optText -end - ---- Emit a usage message. --- @param prog table of named parameters -local function usage (prog) - local usage = "[OPTION]... [FILE]..." - local purpose, description, notes = "", "", "" - if prog.usage then - usage = prog.usage - end - usage = "Usage: " .. prog.name .. " " .. usage - if prog.purpose then - purpose = "\n\n" .. prog.purpose - end - if prog.description then - for para in List.elems (string.split (prog.description, "\n")) do - description = description .. "\n\n" .. string.wrap (para) - end - end - if prog.notes then - notes = "\n\n" - if not string.find (prog.notes, "\n") then - notes = notes .. string.wrap (prog.notes) - else - notes = notes .. prog.notes - end - end - local header = usage .. purpose .. description - io.writelines (usageinfo (header, prog.options) .. notes) -end - - -local function version (prog) - local version = prog.version or prog.name or "unknown version!" - if prog.copyright then - version = version .. "\n\n" .. prog.copyright - end - io.writelines (version) -end - - - ---- Simple getopt wrapper. --- If the caller didn't supply their own already, --- adds --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. ---
FIXME: Make the file list an argument to the function. --- @param f function to process files with, which is passed --- (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. ---
FIXME: Find a better name. --- @param t table {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. ---
FIXME: Find a better name. --- @param ls list {{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. ---
--- Algorithm: turn shape into all positive numbers, calculating --- the zero if necessary and making sure there is at most one; --- recursively walk the shape, adding empty tables until the bottom --- level is reached at which point add table items instead, using a --- counter to walk the flattened original list. ---
--- @param s {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 ---
    ---
  • Create an object/class:
  • ---
      ---
    • Either, if the _init field is a list: ---
        ---
      • object/Class = prototype {value, ...; field = value, ...}
      • ---
      • Named values are assigned to the corresponding fields, and unnamed values --- to the fields given by _init.
      • ---
      ---
    • Or, if the _init field is a function: ---
        ---
      • object/Class = prototype (value, ...)
      • ---
      • The given values are passed as arguments to the _init function.
      • ---
      ---
    • An object's metatable is itself.
    • ---
    • Private fields and methods start with "_".
    • ---
    ---
  • Access an object field: object.field
  • ---
  • Call an object method: object:method (...)
  • ---
  • Call a class method: Class.method (object, ...)
  • ---
  • Add a field: object.field = x
  • ---
  • Add a method: function object:method (...) ... end
  • --- - -local base = require "std.base" - - --- Return the named entry from x's metatable, if any, else nil. -local function metaentry (x, n) - local ok, f = pcall (function (x) - return getmetatable (x)[n] - end, - x) - if not ok then f = nil end - return f -end - - --- Return the extended object type, if any, else primitive type. -local function object_type (self) - local _type = metaentry (self, "_type") - if type (self) == "table" and _type ~= nil then - return _type - end - return type (self) -end - - --- Return a new object, cloned from prototype. -local function clone (prototype, ...) - local mt = getmetatable (prototype) - - -- Make a shallow copy of prototype. - local object = {} - for k, v in pairs (prototype) do - object[k] = v - end - - -- Map arguments according to _init metamethod. - local _init = metaentry (prototype, "_init") - if type (_init) == "table" then - base.merge (object, base.clone_rename (_init, ...)) - else - object = _init (object, ...) - end - - -- Extract any new fields beginning with "_". - local object_mt = {} - for k, v in pairs (object) do - if type (k) == "string" and k:sub (1, 1) == "_" then - object_mt[k], object[k] = v, nil - end - end - - if next (object_mt) == nil then - -- Reuse metatable if possible - object_mt = getmetatable (prototype) - else - - -- Otherwise copy the prototype metatable... - local t = {} - for k, v in pairs (mt) do - t[k] = v - end - -- ...but give preference to "_" prefixed keys from init table - object_mt = base.merge (t, object_mt) - - -- ...and merge object methods from prototype too. - if mt then - if type (object_mt.__index) == "table" and type (mt.__index) == "table" then - local methods = base.clone (object_mt.__index) - for k, v in pairs (mt.__index) do - methods[k] = methods[k] or v - end - object_mt.__index = methods - end - end - end - - return setmetatable (object, object_mt) -end - - --- Return a stringified version of the contents of object. --- First the object type, and then between { and } a list of the array --- part of the object table (without numeric keys) followed by the --- remaining key-value pairs. --- This function doesn't recurse explicity, but relies upon suitable --- __tostring metamethods in contained objects. -local function stringify (object) - local totable = getmetatable (object).__totable - local array = base.clone (totable (object), "nometa") - local other = base.clone (array, "nometa") - local s = "" - if #other > 0 then - for i in ipairs (other) do other[i] = nil end - end - for k in pairs (other) do array[k] = nil end - for i, v in ipairs (array) do array[i] = tostring (v) end - - local keys, dict = {}, {} - for k in pairs (other) do table.insert (keys, k) end - table.sort (keys, function (a, b) return tostring (a) < tostring (b) end) - for _, k in ipairs (keys) do - table.insert (dict, tostring (k) .. "=" .. tostring (other[k])) - end - - if #array > 0 then - s = s .. table.concat (array, ", ") - if next (dict) ~= nil then s = s .. "; " end - end - if #dict > 0 then - s = s .. table.concat (dict, ", ") - end - - return metaentry (object, "_type") .. " {" .. s .. "}" -end - - --- Return a new table with a shallow copy of all non-private fields --- in object (private fields have keys prefixed with "_"). -local function totable (object) - local t = {} - for k, v in pairs (object) do - if type (k) ~= "string" or k:sub (1, 1) ~= "_" then - t[k] = v - end - end - return t -end - - --- Metatable for objects --- Normally a cloned object will share its metatable with its prototype, --- unless some new fields for the cloned object begin with '_', in which --- case they are merged into a copy of the prototype metatable to form --- a new metatable for the cloned object (and its clones). -local metatable = { - _type = "Object", - _init = {}, - - __totable = totable, - __tostring = stringify, - - -- object:method () - __index = { - clone = clone, - tostring = stringify, - totable = totable, - type = object_type, - }, - - -- Sugar instance creation - __call = function (self, ...) - return self:clone (...) - end, -} - ---- Root object --- @class functable --- @name Object --- @field _init constructor method or list of fields to be initialised by the --- constructor -return setmetatable ({}, metatable) diff --git a/lib/std/package.lua b/lib/std/package.lua index 7f32eaa..e3e8243 100644 --- a/lib/std/package.lua +++ b/lib/std/package.lua @@ -1,21 +1,263 @@ --- Additions to the package module. - -local M = {} - ---- Make named constants for package.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. ---
    TODO: Make it work for recursive tables. --- @param x object to pickle --- @return string such that eval (s) is the same value as x -local function pickle (x) - if type (x) == "string" then - return format ("%q", x) - elseif type (x) == "number" or type (x) == "boolean" or - type (x) == "nil" then - return tostring (x) - else - x = totable (x) or x - if type (x) == "table" then - local s, sep = "{", "" - for i, v in pairs (x) do - s = s .. sep .. "[" .. pickle (i) .. "]=" .. pickle (v) - sep = "," - end - s = s .. "}" - return s - else - die ("cannot pickle " .. tostring (x)) - end + +local function toqstring(x, xstr) + if type(x) ~= 'string' then + return xstr end + return format('%q', x) end ---- Give strings a subscription operator. --- @param s string --- @param i index --- @return 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', + }, +}