Third-party libraries updated.

This commit is contained in:
Scott Duensing 2026-09-08 14:29:27 -05:00
parent eae3e66694
commit 1aede80d34
42555 changed files with 948574 additions and 5092609 deletions

View file

@ -152,6 +152,16 @@ API Changes
layers, filter passes, mask writes, layer bytes, passes). Test scenes layers, filter passes, mask writes, layer bytes, passes). Test scenes
scene40 to scene43 cover clip masks, layers, filters and gradients. scene40 to scene43 cover clip masks, layers, filters and gradients.
- Third-party libraries brought up to their current releases: ffmpeg
9.0.1 (from a 2023 development snapshot; libpostproc is gone with it),
OpenSSL 3.5.8 LTS (from a 2023 development snapshot), Lua 5.4.9, zlib
1.3.2, zstd 1.5.7, uthash 2.4.0, lsqlite3 0.9.7, luafilesystem 1.9.0,
copas 4.12.0 (its new copas.future module is embedded) and arg_parser
1.21. sqlite3.lversion() reports the real lsqlite3 version instead
of "unknown", and os.tmpname() returns a usable name again (the local
mkstemp edit never filled in its template). LICENSES now records the
version of every vendored library.
- The bundled menu is an RmlUi document (Singe/Menu.rml styled by - The bundled menu is an RmlUi document (Singe/Menu.rml styled by
Singe/menu.rcss, filled and driven by Singe/Menu.singe): every game in Singe/menu.rcss, filled and driven by Singe/Menu.singe): every game in
a scrolling list on the left, up and down to select, left and right to a scrolling list on the left, up and down to select, left and right to

View file

@ -246,6 +246,7 @@ singeEmbedLua(thirdparty/luasec/src/ssl.lua "")
singeEmbedLua(thirdparty/librs232/bindings/lua/rs232.lua "") singeEmbedLua(thirdparty/librs232/bindings/lua/rs232.lua "")
singeEmbedLua(thirdparty/copas/src/copas.lua "") singeEmbedLua(thirdparty/copas/src/copas.lua "")
singeEmbedLua(thirdparty/copas/src/copas/ftp.lua "copas_") singeEmbedLua(thirdparty/copas/src/copas/ftp.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/future.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/http.lua "copas_") singeEmbedLua(thirdparty/copas/src/copas/http.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/smtp.lua "copas_") singeEmbedLua(thirdparty/copas/src/copas/smtp.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/lock.lua "copas_") singeEmbedLua(thirdparty/copas/src/copas/lock.lua "copas_")
@ -310,11 +311,14 @@ set(SINGE_SOURCE
src/vfs.h src/vfs.h
) )
# The binding reports this from sqlite3.lversion(); it is the version of thirdparty/lsqlite3/lsqlite3.c.
set(LSQLITE_VERSION "0.9.7")
set(SQLITE_SOURCE set(SQLITE_SOURCE
thirdparty/sqlite/sqlite3.c thirdparty/sqlite/sqlite3.c
thirdparty/sqlite/sqlite3.h thirdparty/sqlite/sqlite3.h
thirdparty/lsqlite3/lsqlite3.c thirdparty/lsqlite3/lsqlite3.c
) )
set_source_files_properties(thirdparty/lsqlite3/lsqlite3.c PROPERTIES COMPILE_DEFINITIONS "LSQLITE_VERSION=\"${LSQLITE_VERSION}\"")
set(ARG_PARSER_SOURCE set(ARG_PARSER_SOURCE
thirdparty/arg_parser/carg_parser.c thirdparty/arg_parser/carg_parser.c
@ -627,7 +631,6 @@ set(STATIC_LIBS
${BUILD_DIR}/lib/libopus.a ${BUILD_DIR}/lib/libopus.a
${BUILD_DIR}/lib/libopusfile.a ${BUILD_DIR}/lib/libopusfile.a
${BUILD_DIR}/lib/libogg.a ${BUILD_DIR}/lib/libogg.a
${BUILD_DIR}/lib/libpostproc.a
${BUILD_DIR}/lib/libSDL3.a ${BUILD_DIR}/lib/libSDL3.a
${BUILD_DIR}/lib/libSDL3_image.a ${BUILD_DIR}/lib/libSDL3_image.a
${BUILD_DIR}/lib/libSDL3_mixer.a ${BUILD_DIR}/lib/libSDL3_mixer.a

View file

@ -2,47 +2,51 @@ Singe was made possible by a number of third-party software libraries
generously provided as open source by their respective authors. Without generously provided as open source by their respective authors. Without
these tools, Singe would not exist. these tools, Singe would not exist.
The version column is the release vendored under thirdparty/ (or, for the
libraries bundled inside the SDL trees and the host tools, the copy those
trees carry). Projects without releases are listed by snapshot date.
arg_parser BSD-2-Clause http://savannah.nongnu.org/projects/arg-parser
basis_universal Apache-2.0 https://github.com/BinomialLLC/basis_universal arg_parser 1.21 BSD-2-Clause http://savannah.nongnu.org/projects/arg-parser
binaryheap.lua MIT http://tieske.github.io/binaryheap.lua basis_universal 2.50 Apache-2.0 https://github.com/BinomialLLC/basis_universal
cgltf MIT https://github.com/jkuhlmann/cgltf binaryheap.lua 0.4 MIT http://tieske.github.io/binaryheap.lua
DirectXShaderCompiler NCSA https://github.com/microsoft/DirectXShaderCompiler (prebuilt, downloaded at build time; host tool only) cgltf 1.15 MIT https://github.com/jkuhlmann/cgltf
copas MIT https://lunarmodules.github.io/copas copas 4.12.0 MIT https://lunarmodules.github.io/copas
ffmpeg LGPL-2.1 https://ffmpeg.org DirectXShaderCompiler 1.10.2605.37 NCSA https://github.com/microsoft/DirectXShaderCompiler (prebuilt, downloaded at build time; host tool only)
freetype FTL https://freetype.org ffmpeg 9.0.1 LGPL-2.1 https://ffmpeg.org
JoltPhysics MIT https://github.com/jrouwe/JoltPhysics freetype 2.13.2 FTL https://freetype.org (bundled with SDL3_ttf)
json.lua MIT https://github.com/rxi/json.lua JoltPhysics 5.6.0 MIT https://github.com/jrouwe/JoltPhysics
libjpeg-turbo IJG https://libjpeg-turbo.org json.lua 0.1.2 MIT https://github.com/rxi/json.lua
libogg BSD-3-Clause https://xiph.org/ogg libjpeg 9f IJG https://ijg.org (bundled with SDL3_image)
libpng libpng-2.0 http://www.libpng.org libogg 1.3.5 BSD-3-Clause https://xiph.org/ogg (bundled with SDL3_mixer)
librs232 MIT https://github.com/srdgame/librs232 libpng 1.6.58 libpng-2.0 http://www.libpng.org (bundled with SDL3_image)
libwebp BSD-3-Clause https://developers.google.com/speed/webp librs232 1.0.4 MIT https://github.com/srdgame/librs232
libxmp MIT https://github.com/libxmp/libxmp libwebp 1.3.2 BSD-3-Clause https://developers.google.com/speed/webp (bundled with SDL3_image)
lsqlite3 MIT http://lua.sqlite.org libxmp 4.7.0 MIT https://github.com/libxmp/libxmp (bundled with SDL3_mixer)
lua MIT https://www.lua.org lsqlite3 0.9.7 MIT http://lua.sqlite.org
luafilesystem MIT https://lunarmodules.github.io/luafilesystem lua 5.4.9 MIT https://www.lua.org
luasec MIT https://github.com/lunarmodules/luasec luafilesystem 1.9.0 MIT https://lunarmodules.github.io/luafilesystem
luasocket MIT https://lunarmodules.github.io/luasocket luasec 1.3.2 MIT https://github.com/lunarmodules/luasec
manymouse Zlib https://icculus.org/manymouse luasocket 3.1.0 MIT https://lunarmodules.github.io/luasocket
openssl Apache-2.0 https://www.openssl.org manymouse 0.0.3 Zlib https://icculus.org/manymouse
opus BSD-3-Clause https://opus-codec.org openssl 3.5.8 Apache-2.0 https://www.openssl.org
opusfile BSD-3-Clause https://opus-codec.org opus 1.4 BSD-3-Clause https://opus-codec.org (bundled with SDL3_mixer)
recastnavigation Zlib https://github.com/recastnavigation/recastnavigation opusfile 0.12 BSD-3-Clause https://opus-codec.org (bundled with SDL3_mixer)
RmlUi MIT https://github.com/mikke89/RmlUi recastnavigation 1.6.0 Zlib https://github.com/recastnavigation/recastnavigation
SDL3 Zlib https://www.libsdl.org RmlUi 6.3 MIT https://github.com/mikke89/RmlUi
SDL_shadercross Zlib https://github.com/libsdl-org/SDL_shadercross (host tool only) SDL3 3.4.16 Zlib https://www.libsdl.org
SDL3_image Zlib https://www.libsdl.org SDL_shadercross main 2026-09 Zlib https://github.com/libsdl-org/SDL_shadercross (host tool only)
SDL3_mixer Zlib https://www.libsdl.org SDL3_image 3.4.6 Zlib https://www.libsdl.org
SDL3_ttf Zlib https://www.libsdl.org SDL3_mixer 3.2.4 Zlib https://www.libsdl.org
SPIRV-Cross Apache-2.0 https://github.com/KhronosGroup/SPIRV-Cross (host tool only) SDL3_ttf 3.2.2 Zlib https://www.libsdl.org
sqlite Public-Domain https://sqlite.org SPIRV-Cross main 2026-09 Apache-2.0 https://github.com/KhronosGroup/SPIRV-Cross (host tool only)
timerwheel.lua MIT https://tieske.github.io/timerwheel.lua sqlite 3.53.4 Public-Domain https://sqlite.org
uthash BSD-1-Clause https://troydhanson.github.io/uthash timerwheel.lua 1.0.2 MIT https://tieske.github.io/timerwheel.lua
vlc GPL-2.0 https://www.videolan.org/vlc uthash 2.4.0 BSD-1-Clause https://troydhanson.github.io/uthash
wavpack BSD-3-Clause https://www.wavpack.com vlc 3.0 headers LGPL-2.1 https://www.videolan.org/vlc (two language-code headers only)
zlib Zlib https://zlib.net wavpack 5.9.0 BSD-3-Clause https://www.wavpack.com (bundled with SDL3_mixer)
zstd BSD-3-Clause https://facebook.github.io/zstd zlib 1.3.2 Zlib https://zlib.net
zstd 1.5.7 BSD-3-Clause https://facebook.github.io/zstd
Fonts Fonts

View file

@ -134,12 +134,12 @@ function(singeCmakeProject name source deps args env)
singeRebuildTarget(${name}) singeRebuildTarget(${name})
endfunction() endfunction()
# rebuild-<name> forgets the stamps and builds the library again, then whatever depends on it # rebuild-<name> forgets the stamps and the build directory and builds the library again, then whatever depends on it
# is rebuilt by the normal build. # is rebuilt by the normal build.
function(singeRebuildTarget name) function(singeRebuildTarget name)
ExternalProject_Get_Property(${name} STAMP_DIR) ExternalProject_Get_Property(${name} STAMP_DIR BINARY_DIR)
add_custom_target(rebuild-${name} add_custom_target(rebuild-${name}
COMMAND ${CMAKE_COMMAND} -E rm -rf ${STAMP_DIR} COMMAND ${CMAKE_COMMAND} -E rm -rf ${STAMP_DIR} ${BINARY_DIR}
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${name} COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target ${name}
COMMENT "Rebuilding ${name}" COMMENT "Rebuilding ${name}"
) )
@ -240,7 +240,7 @@ elseif(KANGAROO_OS STREQUAL "pi")
if(KANGAROO_ARCH STREQUAL "aarch64") if(KANGAROO_ARCH STREQUAL "aarch64")
set(opensslTarget linux-aarch64) set(opensslTarget linux-aarch64)
else() else()
set(opensslTarget linux-arm4) set(opensslTarget linux-armv4)
endif() endif()
elseif(KANGAROO_OS STREQUAL "macos") elseif(KANGAROO_OS STREQUAL "macos")
if(KANGAROO_ARCH STREQUAL "aarch64") if(KANGAROO_ARCH STREQUAL "aarch64")
@ -316,7 +316,7 @@ set(ffmpegBinary ${SB_PREFIX}/build/ffmpeg)
ExternalProject_Add(ffmpeg ExternalProject_Add(ffmpeg
SOURCE_DIR ${SB_THIRDPARTY}/ffmpeg SOURCE_DIR ${SB_THIRDPARTY}/ffmpeg
BINARY_DIR ${ffmpegBinary} BINARY_DIR ${ffmpegBinary}
CONFIGURE_COMMAND ${SB_ENV} ${SB_THIRDPARTY}/ffmpeg/configure --enable-static --disable-shared --disable-debug --disable-muxers ${hwaccel} --disable-encoders --disable-filters --disable-network --disable-devices --disable-bzlib --disable-lzma --disable-doc --disable-programs --enable-gpl --enable-version3 --extra-ldflags=-L${SB_PREFIX}/lib --prefix=${SB_PREFIX} --arch=${KANGAROO_ARCH} --target-os=${SINGE_CROSS_OS} ${ffmpegTools} CONFIGURE_COMMAND ${SB_ENV} ${SB_THIRDPARTY}/ffmpeg/configure --enable-static --disable-shared --disable-debug --disable-muxers ${hwaccel} --disable-encoders --disable-filters --disable-network --disable-devices --disable-vulkan --disable-d3d12va --disable-bzlib --disable-lzma --disable-doc --disable-programs --enable-gpl --enable-version3 --extra-ldflags=-L${SB_PREFIX}/lib --prefix=${SB_PREFIX} --arch=${KANGAROO_ARCH} --target-os=${SINGE_CROSS_OS} ${ffmpegTools}
BUILD_COMMAND ${SB_ENV} make BUILD_COMMAND ${SB_ENV} make
INSTALL_COMMAND ${SB_ENV} make install INSTALL_COMMAND ${SB_ENV} make install
LOG_CONFIGURE ON LOG_BUILD ON LOG_INSTALL ON LOG_OUTPUT_ON_FAILURE ON LOG_CONFIGURE ON LOG_BUILD ON LOG_INSTALL ON LOG_OUTPUT_ON_FAILURE ON

View file

@ -72,6 +72,7 @@
// Copas // Copas
#include "generated/copas_lua.h" #include "generated/copas_lua.h"
#include "generated/copas_ftp_lua.h" #include "generated/copas_ftp_lua.h"
#include "generated/copas_future_lua.h"
#include "generated/copas_http_lua.h" #include "generated/copas_http_lua.h"
#include "generated/copas_lock_lua.h" #include "generated/copas_lock_lua.h"
#include "generated/copas_queue_lua.h" #include "generated/copas_queue_lua.h"

View file

@ -1067,6 +1067,7 @@ static const LuaModuleT _luaModules[] = {
// Copas // Copas
MODL("copas", copas_lua), MODL("copas", copas_lua),
MODL("copas.ftp", copas_ftp_lua), MODL("copas.ftp", copas_ftp_lua),
MODL("copas.future", copas_future_lua),
MODL("copas.http", copas_http_lua), MODL("copas.http", copas_http_lua),
MODL("copas.lock", copas_lock_lua), MODL("copas.lock", copas_lock_lua),
MODL("copas.queue", copas_queue_lua), MODL("copas.queue", copas_queue_lua),

View file

@ -1,4 +1,4 @@
Arg_parser - POSIX/GNU command line argument parser. Arg_parser - POSIX/GNU command-line argument parser.
Copyright (C) Antonio Diaz Diaz. Copyright (C) Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and This library is free software. Redistribution and use in source and

View file

@ -1,3 +1,37 @@
2026-03-10 Antonio Diaz Diaz <antonio@gnu.org>
* Version 1.21 released.
* Replace 'bool in_order' with 'int flags'.
* New flags 'in_order_stop' and 'in_order_skip'.
* New flag 'neg_non_opt'. (Suggested by Mohammad Akhlaghi).
* Rename Has_arg value 'yme' to 'yesme'.
* carg_parser.h (struct Arg_parser): Remove 'error_size'.
* carg_parser.c: Replace 'add_error' with 'set_error'.
Add 'push_back_option', 'push_back_argument', and 'set_argument'.
* arg_parser.texi: New section 'Differences with getopt_long'.
* New files testsuite/test[1-4].txt.
2025-06-29 Antonio Diaz Diaz <antonio@gnu.org>
* Version 1.20 released.
* main.cc, cmain.c: Refactor code to match examples in manual.
* arg_parser.texi: New chapters 'Tutorial for the C++ version' and
'Tutorial for the C version'. (Suggested by Mark Galassi).
2025-01-11 Antonio Diaz Diaz <antonio@gnu.org>
* Version 1.19 released.
* New value in Has_arg; yme = 'yes but maybe empty'.
* carg_parser.h: Use 'typedef' (again) in C version.
* arg_parser.texi (Argument syntax): Document opts with empty argument.
* check.sh: Use 'cp' instead of 'cat'.
2024-01-20 Antonio Diaz Diaz <antonio@gnu.org>
* Version 1.18 released.
* arg_parser.texi (Argument syntax): Reorder, improve descriptions.
* configure, Makefile.in: New variable 'MAKEINFO'.
2022-02-05 Antonio Diaz Diaz <antonio@gnu.org> 2022-02-05 Antonio Diaz Diaz <antonio@gnu.org>
* Version 1.17 released. * Version 1.17 released.
@ -75,14 +109,13 @@
* Version 1.5 released. * Version 1.5 released.
* Minor fixes. * Minor fixes.
* C version: C99 style comments '//' have been converted to C89 * C version: Convert C99 style comments '//' to C89 style '/* */'.
style comments '/* */'.
* New files testsuite/check.sh, testsuite/test.txt. * New files testsuite/check.sh, testsuite/test.txt.
2010-03-11 Antonio Diaz Diaz <ant_diaz@teleline.es> 2010-03-11 Antonio Diaz Diaz <ant_diaz@teleline.es>
* Version 1.4 released. * Version 1.4 released.
* carg_parser.h: Remove 'typedefs' from C version. * carg_parser.h: Remove 'typedef' from C version.
* Add link exception to license. * Add link exception to license.
* The manual has been improved. * The manual has been improved.
* Makefile.in: Add option '--name' to help2man invocation. * Makefile.in: Add option '--name' to help2man invocation.
@ -112,15 +145,14 @@
2006-11-04 Antonio Diaz Diaz <ant_diaz@teleline.es> 2006-11-04 Antonio Diaz Diaz <ant_diaz@teleline.es>
* Version 0.2 released. * Version 0.2 released.
* Some fixes made to 'configure' script. * configure: Some fixes.
2006-03-29 Antonio Diaz Diaz <ant_diaz@teleline.es> 2006-03-29 Antonio Diaz Diaz <ant_diaz@teleline.es>
* Version 0.1 released. * Version 0.1 released.
Copyright (C) 2006-2022 Antonio Diaz Diaz. Copyright (C) 2006-2026 Antonio Diaz Diaz.
This file is a collection of facts, and thus it is not copyrightable, This file is a collection of facts, and thus it is not copyrightable, but just
but just in case, you have unlimited permission to copy, distribute, and in case, you have unlimited permission to copy, distribute, and modify it.
modify it.

View file

@ -4,7 +4,8 @@ You will need a C++98 compiler and a C89 compiler.
(gcc 3.3.6 or newer is recommended). (gcc 3.3.6 or newer is recommended).
I use gcc 6.1.0 and 3.3.6, but the code should compile with any standards I use gcc 6.1.0 and 3.3.6, but the code should compile with any standards
compliant compiler. compliant compiler.
Gcc is available at http://gcc.gnu.org. Gcc is available at http://gcc.gnu.org
Lzip is available at http://www.nongnu.org/lzip/lzip.html
Procedure Procedure
@ -15,8 +16,8 @@ Procedure
or or
lzip -cd arg_parser[version].tar.lz | tar -xf - lzip -cd arg_parser[version].tar.lz | tar -xf -
This creates the directory ./arg_parser[version] containing the source from This creates the directory ./arg_parser[version] containing the source code
the main archive. extracted from the archive.
2. Change to arg_parser directory and run configure. 2. Change to arg_parser directory and run configure.
(Try 'configure --help' for usage instructions). (Try 'configure --help' for usage instructions).
@ -31,7 +32,8 @@ the main archive.
4. Optionally, type 'make check' to run the tests that come with arg_parser. 4. Optionally, type 'make check' to run the tests that come with arg_parser.
5. Type 'make install' to install the program and any data files and 5. Type 'make install' to install the program and any data files and
documentation. documentation. You need root privileges to install into a prefix owned
by root.
Or type 'make install-compress', which additionally compresses the Or type 'make install-compress', which additionally compresses the
info manual and the man page after installation. info manual and the man page after installation.
@ -51,15 +53,15 @@ object files and executables to go and run the 'configure' script.
'configure' automatically checks for the source code in '.', in '..', and 'configure' automatically checks for the source code in '.', in '..', and
in the directory that 'configure' is in. in the directory that 'configure' is in.
'configure' recognizes the option '--srcdir=DIR' to control where to 'configure' recognizes the option '--srcdir=DIR' to control where to look
look for the sources. Usually 'configure' can determine that directory for the source code. Usually 'configure' can determine that directory
automatically. automatically.
After running 'configure', you can run 'make' and 'make install' as After running 'configure', you can run 'make' and 'make install' as
explained above. explained above.
Copyright (C) 2006-2022 Antonio Diaz Diaz. Copyright (C) 2006-2026 Antonio Diaz Diaz.
This file is free documentation: you have unlimited permission to copy, This file is free documentation: you have unlimited permission to copy,
distribute, and modify it. distribute, and modify it.

View file

@ -2,8 +2,8 @@
DISTNAME = $(pkgname)-$(pkgversion) DISTNAME = $(pkgname)-$(pkgversion)
INSTALL = install INSTALL = install
INSTALL_PROGRAM = $(INSTALL) -m 755 INSTALL_PROGRAM = $(INSTALL) -m 755
INSTALL_DATA = $(INSTALL) -m 644
INSTALL_DIR = $(INSTALL) -d -m 755 INSTALL_DIR = $(INSTALL) -d -m 755
INSTALL_DATA = $(INSTALL) -m 644
SHELL = /bin/sh SHELL = /bin/sh
CAN_RUN_INSTALLINFO = $(SHELL) -c "install-info --version" > /dev/null 2>&1 CAN_RUN_INSTALLINFO = $(SHELL) -c "install-info --version" > /dev/null 2>&1
@ -37,16 +37,20 @@ arg_parser.o : arg_parser.cc
carg_parser.o : carg_parser.c carg_parser.o : carg_parser.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
# prevent 'make' from trying to remake source files
$(VPATH)/configure $(VPATH)/Makefile.in $(VPATH)/doc/$(pkgname).texi : ;
MAKEFLAGS += -r
.SUFFIXES :
$(objs) : Makefile arg_parser.h $(objs) : Makefile arg_parser.h
$(cobjs) : Makefile carg_parser.h $(cobjs) : Makefile carg_parser.h
doc : info man doc : info man
info : $(VPATH)/doc/$(pkgname).info info : $(VPATH)/doc/$(pkgname).info
$(VPATH)/doc/$(pkgname).info : $(VPATH)/doc/$(pkgname).texi $(VPATH)/doc/$(pkgname).info : $(VPATH)/doc/$(pkgname).texi
cd $(VPATH)/doc && makeinfo $(pkgname).texi cd $(VPATH)/doc && $(MAKEINFO) $(pkgname).texi
man : $(VPATH)/doc/$(progname).1 $(VPATH)/doc/c$(progname).1 man : $(VPATH)/doc/$(progname).1 $(VPATH)/doc/c$(progname).1
@ -54,7 +58,8 @@ $(VPATH)/doc/$(progname).1 : $(progname)
help2man -n 'test program for Arg_parser' -o $@ ./$(progname) help2man -n 'test program for Arg_parser' -o $@ ./$(progname)
$(VPATH)/doc/c$(progname).1 : c$(progname) $(VPATH)/doc/c$(progname).1 : c$(progname)
help2man -n 'test program for Arg_parser' -o $@ --info-page=$(pkgname) ./c$(progname) help2man -n 'test program for Arg_parser' \
-o $@ --info-page=$(pkgname) ./c$(progname)
Makefile : $(VPATH)/configure $(VPATH)/Makefile.in Makefile : $(VPATH)/configure $(VPATH)/Makefile.in
./config.status ./config.status
@ -132,7 +137,7 @@ dist : doc
$(DISTNAME)/*.c \ $(DISTNAME)/*.c \
$(DISTNAME)/*.cc \ $(DISTNAME)/*.cc \
$(DISTNAME)/testsuite/check.sh \ $(DISTNAME)/testsuite/check.sh \
$(DISTNAME)/testsuite/test.txt $(DISTNAME)/testsuite/test[1-4].txt
rm -f $(DISTNAME) rm -f $(DISTNAME)
lzip -v -9 $(DISTNAME).tar lzip -v -9 $(DISTNAME).tar

View file

@ -1,5 +1,41 @@
Changes in version 1.17: Changes in version 1.21:
The new functions 'parsed_name' (C++) and 'ap_parsed_name' (C) have been The last argument of the C++ constructor and of the C function 'ap_init' has
added. They return the full name of the option parsed (short or long) to been changed from 'bool in_order' to 'int flags'. This change is
help produce more accurate diagnostic messages. backwards-compatible after recompilation because the default value of both
arguments is 0, and the new flag 'in_order' has a value of 1. So the old
'in_order = true' is equivalent to the new 'flags = in_order'.
The C++ constructor and the C function 'ap_init' have been extended with the
flag 'in_order_stop' to parse the first non-option argument and all the
arguments following it as non-option arguments, even if they begin with a
hyphen. This is similar to the POSIX function 'getopt', which stops option
processing after finding the first non-option argument.
The C++ constructor and the C function 'ap_init' have been extended with the
flag 'in_order_skip' to parse only the heading options, skipping the first
non-option argument and all the arguments following it. This mode is useful
for parsing the command line of programs that invoke other programs, like
'timeout', 'xargs', or 'valgrind'.
The C++ constructor and the C function 'ap_init' have been extended with the
flag 'neg_non_opt' to parse negative numbers as non-option arguments without
reordering them. This mode is useful for parsing the command line of tools
like 'seq' which take negative numbers as arguments. (Suggested by Mohammad
Akhlaghi):
$ arg_parser +15 -Inf 0 -20 -a -.5 -v
option '-a'
option '-v'
non-option argument '+15'
non-option argument '-Inf'
non-option argument '0'
non-option argument '-20'
non-option argument '-.5'
The 'enum Has_arg' value 'yme' has been renamed to 'yesme'.
The code of the C version has been reorganized.
The new section 'Differences with getopt and getopt_long' has been added to
the manual.

View file

@ -1,25 +1,31 @@
Description Description
Arg_parser is an argument parser that follows POSIX and GNU conventions for Arg_parser is an argument parser that follows POSIX and GNU conventions for
command line arguments. There exist C++ and C versions of Arg_parser. The command-line arguments. There exist C++ and C versions of Arg_parser. The
C++ version is implemented as a C++ class, while the C version is C++ version is implemented as a C++ class, while the C version is
implemented as a single struct plus associated functions. Both are simpler, implemented as a single struct plus associated functions. Both are simpler,
easier to use, and safer than 'getopt_long'. easier to use, and safer than 'getopt_long'.
For maximum stability, Arg_parser is self-contained. It extracts all the For maximum stability, Arg_parser is self-contained. It extracts all the
information it needs from its arguments to avoid refering to them later. information it needs from its arguments to avoid referring to them later.
This avoids index-out-of-bounds errors. This avoids index-out-of-bounds errors and allows the parser object to be
passed as argument to other functions for further analysis.
Arg_parser does not modify its arguments, nor uses any global variables. So Arg_parser does not modify its arguments (argc, argv), nor uses any global
you may create more than one parser in your program if you need or want to. variables.
The C++ version of Arg_parser can also parse options from configuration The C++ version of Arg_parser can also parse options from configuration
files. files.
To use Arg_parser in your own programs simply copy the files 'arg_parser.h' The C++ version of Arg_parser is provided in the files 'arg_parser.h' and
and 'arg_parser.cc' (or 'carg_parser.h' and 'carg_parser.c' for the 'arg_parser.cc'. To learn how to use Arg_parser in your C++ programs, see
C version) in your source tree. See the file 'main.cc' ('cmain.c') for an the C++ example in the manual and the example file 'main.cc' in the source
example of use. tarball.
The C version of Arg_parser is provided in the files 'carg_parser.h' and
'carg_parser.c'. To learn how to use Arg_parser in your C programs, see the
C example in the manual and the example file 'cmain.c' in the source
tarball.
Arg_parser was developed as the argument parser for GNU moe, because moe's Arg_parser was developed as the argument parser for GNU moe, because moe's
argument parsing is rather complex. Then I used it in my other projects, argument parsing is rather complex. Then I used it in my other projects,
@ -27,11 +33,10 @@ including GNU ddrescue, GNU ed, lzip, GNU ocrad, tarlz, and zutils, with
excellent results. excellent results.
Copyright (C) 2006-2022 Antonio Diaz Diaz. Copyright (C) 2006-2026 Antonio Diaz Diaz.
This file is free documentation: you have unlimited permission to copy, This file is free documentation: you have unlimited permission to copy,
distribute, and modify it. distribute, and modify it.
The file Makefile.in is a data file used by configure to produce the The file Makefile.in is a data file used by configure to produce the Makefile.
Makefile. It has the same copyright owner and permissions that configure It has the same copyright owner and permissions that configure itself.
itself.

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C++ version) /* Arg_parser - POSIX/GNU command-line argument parser. (C++ version)
Copyright (C) 2006-2022 Antonio Diaz Diaz. Copyright (C) 2006-2026 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and This library is free software. Redistribution and use in source and
binary forms, with or without modification, are permitted provided binary forms, with or without modification, are permitted provided
@ -17,12 +17,24 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/ */
#include <cctype>
#include <cstring> #include <cstring>
#include <string> #include <string>
#include <vector> #include <vector>
#include "arg_parser.h" #include "arg_parser.h"
namespace {
bool is_number( const char * const p )
{
return std::isdigit( (unsigned char)(p[*p=='.']) ) ||
std::strcmp( p, "inf" ) == 0 || std::strcmp( p, "Inf" ) == 0 ||
std::strcmp( p, "INF" ) == 0;
}
} // end namespace
bool Arg_parser::parse_long_option( const char * const opt, const char * const arg, bool Arg_parser::parse_long_option( const char * const opt, const char * const arg,
const Option options[], int & argind ) const Option options[], int & argind )
@ -75,19 +87,19 @@ bool Arg_parser::parse_long_option( const char * const opt, const char * const a
error_ += "' requires an argument"; error_ += "' requires an argument";
return false; return false;
} }
data.back().argument = &opt[len+3]; data.back().argument = &opt[len+3]; // argument may be empty
return true; return true;
} }
if( options[index].has_arg == yes ) if( options[index].has_arg == yes || options[index].has_arg == yesme )
{ {
if( !arg || !arg[0] ) if( !arg || ( options[index].has_arg == yes && !arg[0] ) )
{ {
error_ = "option '--"; error_ += options[index].long_name; error_ = "option '--"; error_ += options[index].long_name;
error_ += "' requires an argument"; error_ += "' requires an argument";
return false; return false;
} }
++argind; data.back().argument = arg; ++argind; data.back().argument = arg; // argument may be empty
return true; return true;
} }
@ -123,15 +135,16 @@ bool Arg_parser::parse_short_option( const char * const opt, const char * const
{ {
data.back().argument = &opt[cind]; ++argind; cind = 0; data.back().argument = &opt[cind]; ++argind; cind = 0;
} }
else if( options[index].has_arg == yes ) else if( options[index].has_arg == yes || options[index].has_arg == yesme )
{ {
if( !arg || !arg[0] ) if( !arg || ( options[index].has_arg == yes && !arg[0] ) )
{ {
error_ = "option requires an argument -- '"; error_ += c; error_ = "option requires an argument -- '"; error_ += c;
error_ += '\''; error_ += '\'';
return false; return false;
} }
data.back().argument = arg; ++argind; cind = 0; ++argind; cind = 0;
data.back().argument = arg; // argument may be empty
} }
} }
return true; return true;
@ -139,7 +152,8 @@ bool Arg_parser::parse_short_option( const char * const opt, const char * const
Arg_parser::Arg_parser( const int argc, const char * const argv[], Arg_parser::Arg_parser( const int argc, const char * const argv[],
const Option options[], const bool in_order ) const Option options[], const int flags )
: argv_index_( argc )
{ {
if( argc < 2 || !argv || !options ) return; if( argc < 2 || !argv || !options ) return;
@ -151,9 +165,10 @@ Arg_parser::Arg_parser( const int argc, const char * const argv[],
const unsigned char ch1 = argv[argind][0]; const unsigned char ch1 = argv[argind][0];
const unsigned char ch2 = ch1 ? argv[argind][1] : 0; const unsigned char ch2 = ch1 ? argv[argind][1] : 0;
if( ch1 == '-' && ch2 ) // we found an option if( ch1 == '-' && ch2 && ( ch2 == '-' || (flags & neg_non_opt) == 0 ||
!is_number( argv[argind] + 1 ) ) )
{ {
const char * const opt = argv[argind]; const char * const opt = argv[argind]; // we found an option
const char * const arg = ( argind + 1 < argc ) ? argv[argind+1] : 0; const char * const arg = ( argind + 1 < argc ) ? argv[argind+1] : 0;
if( ch2 == '-' ) if( ch2 == '-' )
{ {
@ -162,14 +177,13 @@ Arg_parser::Arg_parser( const int argc, const char * const argv[],
} }
else if( !parse_short_option( opt, arg, options, argind ) ) break; else if( !parse_short_option( opt, arg, options, argind ) ) break;
} }
else else if( flags & (in_order_stop | in_order_skip) ) break;
{ else if( flags & in_order ) data.push_back( Record( argv[argind++] ) );
if( in_order ) data.push_back( Record( argv[argind++] ) ); else non_options.push_back( argv[argind++] );
else non_options.push_back( argv[argind++] );
}
} }
if( !error_.empty() ) data.clear(); if( !error_.empty() ) data.clear();
else else if( flags & in_order_skip ) argv_index_ = argind;
else // copy non-option arguments
{ {
for( unsigned i = 0; i < non_options.size(); ++i ) for( unsigned i = 0; i < non_options.size(); ++i )
data.push_back( Record( non_options[i] ) ); data.push_back( Record( non_options[i] ) );
@ -181,6 +195,7 @@ Arg_parser::Arg_parser( const int argc, const char * const argv[],
Arg_parser::Arg_parser( const char * const opt, const char * const arg, Arg_parser::Arg_parser( const char * const opt, const char * const arg,
const Option options[] ) const Option options[] )
: argv_index_( 0 )
{ {
if( !opt || !opt[0] || !options ) return; if( !opt || !opt[0] || !options ) return;

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C++ version) /* Arg_parser - POSIX/GNU command-line argument parser. (C++ version)
Copyright (C) 2006-2022 Antonio Diaz Diaz. Copyright (C) 2006-2026 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and This library is free software. Redistribution and use in source and
binary forms, with or without modification, are permitted provided binary forms, with or without modification, are permitted provided
@ -27,23 +27,29 @@
option. A code value outside the unsigned char range means a long-only option. A code value outside the unsigned char range means a long-only
option. option.
Arg_parser normally makes it appear as if all the option arguments Arg_parser normally makes it appear as if all the options were specified
were specified before all the non-option arguments for the purposes before all the non-option arguments for the purposes of parsing, even if
of parsing, even if the user of your program intermixed option and the user of your program intermixed options and non-option arguments. If
non-option arguments. If you want the arguments in the exact order you want the arguments in the exact order the user typed them, call
the user typed them, call 'Arg_parser' with 'in_order' = true. 'Arg_parser' with 'flags' = 'in_order'.
The argument '--' terminates all options; any following arguments are The argument '--' terminates all options; any following arguments are
treated as non-option arguments, even if they begin with a hyphen. treated as non-option arguments, even if they begin with a hyphen.
The syntax for optional option arguments is '-<short_option><argument>' The syntax of options with an optional argument is
(without whitespace), or '--<long_option>=<argument>'. '-<short_option><argument>' (without whitespace), or
'--<long_option>=<argument>'.
The syntax of options with an empty argument is '-<short_option> ""',
'--<long_option> ""', or '--<long_option>=""'.
*/ */
class Arg_parser class Arg_parser
{ {
public: public:
enum Has_arg { no, yes, maybe }; enum Flags { in_order = 1, in_order_stop = 2, in_order_skip = 4,
neg_non_opt = 8 }; // negative is non-option
enum Has_arg { no, yes, maybe, yesme }; // yesme = yes but maybe empty
struct Option struct Option
{ {
@ -68,6 +74,7 @@ private:
const std::string empty_arg; const std::string empty_arg;
std::string error_; std::string error_;
std::vector< Record > data; std::vector< Record > data;
int argv_index_;
bool parse_long_option( const char * const opt, const char * const arg, bool parse_long_option( const char * const opt, const char * const arg,
const Option options[], int & argind ); const Option options[], int & argind );
@ -76,13 +83,14 @@ private:
public: public:
Arg_parser( const int argc, const char * const argv[], Arg_parser( const int argc, const char * const argv[],
const Option options[], const bool in_order = false ); const Option options[], const int flags = 0 );
// Restricted constructor. Parses a single token and argument (if any). // Restricted constructor. Parses a single token and argument (if any).
Arg_parser( const char * const opt, const char * const arg, Arg_parser( const char * const opt, const char * const arg,
const Option options[] ); const Option options[] );
const std::string & error() const { return error_; } const std::string & error() const { return error_; }
int argv_index() const { return argv_index_; }
// The number of arguments parsed. May be different from argc. // The number of arguments parsed. May be different from argc.
int arguments() const { return data.size(); } int arguments() const { return data.size(); }

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C version) /* Arg_parser - POSIX/GNU command-line argument parser. (C version)
Copyright (C) 2006-2022 Antonio Diaz Diaz. Copyright (C) 2006-2026 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and This library is free software. Redistribution and use in source and
binary forms, with or without modification, are permitted provided binary forms, with or without modification, are permitted provided
@ -17,12 +17,21 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/ */
#include <ctype.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "carg_parser.h" #include "carg_parser.h"
static char is_number( const char * const p )
{
return isdigit( (unsigned char)(p[*p=='.']) ) ||
strcmp( p, "inf" ) == 0 || strcmp( p, "Inf" ) == 0 ||
strcmp( p, "INF" ) == 0;
}
/* assure at least a minimum size for buffer 'buf' */ /* assure at least a minimum size for buffer 'buf' */
static void * ap_resize_buffer( void * buf, const int min_size ) static void * ap_resize_buffer( void * buf, const int min_size )
{ {
@ -32,58 +41,78 @@ static void * ap_resize_buffer( void * buf, const int min_size )
} }
static char push_back_record( struct Arg_parser * const ap, const int code, static char set_argument( Arg_parser * const ap, const char * const argument )
const char * const long_name,
const char * const argument )
{ {
struct ap_Record * p; ap_Record * const p = &(ap->data[ap->data_size-1]);
void * tmp = ap_resize_buffer( ap->data, const int len = strlen( argument );
( ap->data_size + 1 ) * sizeof (struct ap_Record) ); p->argument = (char *)malloc( len + 1 );
if( !p->argument ) return 0;
memcpy( p->argument, argument, len + 1 );
return 1;
}
static ap_Record * push_back_record( Arg_parser * const ap )
{
const int min_size = ( ap->data_size + 1 ) * sizeof (ap_Record);
void * tmp = ap_resize_buffer( ap->data, min_size );
if( !tmp ) return 0; if( !tmp ) return 0;
ap->data = (struct ap_Record *)tmp; ap->data = (ap_Record *)tmp;
p = &(ap->data[ap->data_size]); return &(ap->data[ap->data_size++]);
}
static char push_back_option( Arg_parser * const ap, const int code,
const char * const long_name )
{
ap_Record * const p = push_back_record( ap );
if( !p ) return 0;
p->code = code; p->code = code;
p->argument = 0;
if( long_name ) if( long_name )
{ {
const int len = strlen( long_name ); const int len = strlen( long_name );
p->parsed_name = (char *)malloc( len + 2 + 1 ); p->parsed_name = (char *)malloc( len + 2 + 1 );
if( !p->parsed_name ) return 0; if( !p->parsed_name ) return 0;
p->parsed_name[0] = p->parsed_name[1] = '-'; p->parsed_name[0] = p->parsed_name[1] = '-';
strncpy( p->parsed_name + 2, long_name, len + 1 ); memcpy( p->parsed_name + 2, long_name, len + 1 );
} }
else if( code > 0 && code < 256 ) else
{ {
p->parsed_name = (char *)malloc( 2 + 1 ); p->parsed_name = (char *)malloc( 2 + 1 );
if( !p->parsed_name ) return 0; if( !p->parsed_name ) return 0;
p->parsed_name[0] = '-'; p->parsed_name[1] = code; p->parsed_name[2] = 0; p->parsed_name[0] = '-'; p->parsed_name[1] = code; p->parsed_name[2] = 0;
} }
else p->parsed_name = 0;
if( argument )
{
const int len = strlen( argument );
p->argument = (char *)malloc( len + 1 );
if( !p->argument ) { free( p->parsed_name ); return 0; }
strncpy( p->argument, argument, len + 1 );
}
else p->argument = 0;
++ap->data_size;
return 1; return 1;
} }
static char add_error( struct Arg_parser * const ap, const char * const msg ) static char push_back_argument( Arg_parser * const ap,
const char * const argument )
{ {
const int len = strlen( msg ); ap_Record * const p = push_back_record( ap );
void * tmp = ap_resize_buffer( ap->error, ap->error_size + len + 1 ); if( !p ) return 0;
p->code = 0;
p->parsed_name = 0;
return set_argument( ap, argument );
}
static char set_error( Arg_parser * const ap, const char * const s1,
const char * const s2, const char * const s3 )
{
const int l1 = strlen( s1 ), l2 = strlen( s2 ), l3 = strlen( s3 );
void * tmp = ap_resize_buffer( ap->error, l1 + l2 + l3 + 1 );
if( !tmp ) return 0; if( !tmp ) return 0;
ap->error = (char *)tmp; ap->error = (char *)tmp;
strncpy( ap->error + ap->error_size, msg, len + 1 ); memcpy( ap->error, s1, l1 );
ap->error_size += len; memcpy( ap->error + l1, s2, l2 );
memcpy( ap->error + l1 + l2, s3, l3 + 1 );
return 1; return 1;
} }
static void free_data( struct Arg_parser * const ap ) static void free_data( Arg_parser * const ap )
{ {
int i; int i;
for( i = 0; i < ap->data_size; ++i ) for( i = 0; i < ap->data_size; ++i )
@ -94,10 +123,9 @@ static void free_data( struct Arg_parser * const ap )
/* Return 0 only if out of memory. */ /* Return 0 only if out of memory. */
static char parse_long_option( struct Arg_parser * const ap, static char parse_long_option( Arg_parser * const ap,
const char * const opt, const char * const arg, const char * const opt, const char * const arg,
const struct ap_Option options[], const ap_Option options[], int * const argindp )
int * const argindp )
{ {
unsigned len; unsigned len;
int index = -1, i; int index = -1, i;
@ -119,62 +147,43 @@ static char parse_long_option( struct Arg_parser * const ap,
} }
if( ambig && !exact ) if( ambig && !exact )
{ return set_error( ap, "option '", opt, "' is ambiguous" );
add_error( ap, "option '" ); add_error( ap, opt );
add_error( ap, "' is ambiguous" );
return 1;
}
if( index < 0 ) /* nothing found */ if( index < 0 ) /* nothing found */
{ return set_error( ap, "unrecognized option '", opt, "'" );
add_error( ap, "unrecognized option '" ); add_error( ap, opt );
add_error( ap, "'" );
return 1;
}
++*argindp; ++*argindp;
if( !push_back_option( ap, options[index].code, options[index].long_name ) )
return 0;
if( opt[len+2] ) /* '--<long_option>=<argument>' syntax */ if( opt[len+2] ) /* '--<long_option>=<argument>' syntax */
{ {
if( options[index].has_arg == ap_no ) if( options[index].has_arg == ap_no )
{ return set_error( ap, "option '--", options[index].long_name,
add_error( ap, "option '--" ); add_error( ap, options[index].long_name ); "' doesn't allow an argument" );
add_error( ap, "' doesn't allow an argument" );
return 1;
}
if( options[index].has_arg == ap_yes && !opt[len+3] ) if( options[index].has_arg == ap_yes && !opt[len+3] )
{ return set_error( ap, "option '--", options[index].long_name,
add_error( ap, "option '--" ); add_error( ap, options[index].long_name ); "' requires an argument" );
add_error( ap, "' requires an argument" ); return set_argument( ap, &opt[len+3] ); /* argument may be empty */
return 1;
}
return push_back_record( ap, options[index].code,
options[index].long_name, &opt[len+3] );
} }
if( options[index].has_arg == ap_yes ) if( options[index].has_arg == ap_yes || options[index].has_arg == ap_yesme )
{ {
if( !arg || !arg[0] ) if( !arg || ( options[index].has_arg == ap_yes && !arg[0] ) )
{ return set_error( ap, "option '--", options[index].long_name,
add_error( ap, "option '--" ); add_error( ap, options[index].long_name ); "' requires an argument" );
add_error( ap, "' requires an argument" );
return 1;
}
++*argindp; ++*argindp;
return push_back_record( ap, options[index].code, return set_argument( ap, arg ); /* argument may be empty */
options[index].long_name, arg );
} }
return push_back_record( ap, options[index].code, return 1;
options[index].long_name, 0 );
} }
/* Return 0 only if out of memory. */ /* Return 0 only if out of memory. */
static char parse_short_option( struct Arg_parser * const ap, static char parse_short_option( Arg_parser * const ap,
const char * const opt, const char * const arg, const char * const opt, const char * const arg,
const struct ap_Option options[], const ap_Option options[], int * const argindp )
int * const argindp )
{ {
int cind = 1; /* character index in opt */ int cind = 1; /* character index in opt */
@ -191,49 +200,42 @@ static char parse_short_option( struct Arg_parser * const ap,
{ index = i; break; } { index = i; break; }
if( index < 0 ) if( index < 0 )
{ return set_error( ap, "invalid option -- '", code_str, "'" );
add_error( ap, "invalid option -- '" ); add_error( ap, code_str );
add_error( ap, "'" );
return 1;
}
if( !push_back_option( ap, c, 0 ) ) return 0;
if( opt[++cind] == 0 ) { ++*argindp; cind = 0; } /* opt finished */ if( opt[++cind] == 0 ) { ++*argindp; cind = 0; } /* opt finished */
if( options[index].has_arg != ap_no && cind > 0 && opt[cind] ) if( options[index].has_arg != ap_no && cind > 0 && opt[cind] )
{ {
if( !push_back_record( ap, c, 0, &opt[cind] ) ) return 0; if( !set_argument( ap, &opt[cind] ) ) return 0;
++*argindp; cind = 0; ++*argindp; cind = 0;
} }
else if( options[index].has_arg == ap_yes ) else if( options[index].has_arg == ap_yes || options[index].has_arg == ap_yesme )
{ {
if( !arg || !arg[0] ) if( !arg || ( options[index].has_arg == ap_yes && !arg[0] ) )
{ return set_error( ap, "option requires an argument -- '", code_str, "'" );
add_error( ap, "option requires an argument -- '" );
add_error( ap, code_str ); add_error( ap, "'" );
return 1;
}
++*argindp; cind = 0; ++*argindp; cind = 0;
if( !push_back_record( ap, c, 0, arg ) ) return 0; if( !set_argument( ap, arg ) ) return 0; /* argument may be empty */
} }
else if( !push_back_record( ap, c, 0, 0 ) ) return 0;
} }
return 1; return 1;
} }
char ap_init( struct Arg_parser * const ap, /* Return 0 only if out of memory. */
char ap_init( Arg_parser * const ap,
const int argc, const char * const argv[], const int argc, const char * const argv[],
const struct ap_Option options[], const char in_order ) const ap_Option options[], const int flags )
{ {
const char ** non_options = 0; /* skipped non-options */ const char ** non_options = 0; /* skipped non-options */
int non_options_size = 0; /* number of skipped non-options */ int non_options_size = 0; /* number of skipped non-options */
int argind = 1; /* index in argv */ int argind = 1; /* index in argv */
char done = 0; /* false until success */ char done = 0; /* false until success or error */
ap->data = 0; ap->data = 0;
ap->error = 0; ap->error = 0;
ap->data_size = 0; ap->data_size = 0;
ap->error_size = 0; ap->argv_index = argc;
if( argc < 2 || !argv || !options ) return 1; if( argc < 2 || !argv || !options ) return 1;
while( argind < argc ) while( argind < argc )
@ -241,78 +243,75 @@ char ap_init( struct Arg_parser * const ap,
const unsigned char ch1 = argv[argind][0]; const unsigned char ch1 = argv[argind][0];
const unsigned char ch2 = ch1 ? argv[argind][1] : 0; const unsigned char ch2 = ch1 ? argv[argind][1] : 0;
if( ch1 == '-' && ch2 ) /* we found an option */ if( ch1 == '-' && ch2 && ( ch2 == '-' || (flags & ap_neg_non_opt) == 0 ||
!is_number( argv[argind] + 1 ) ) )
{ {
const char * const opt = argv[argind]; const char * const opt = argv[argind]; /* we found an option */
const char * const arg = ( argind + 1 < argc ) ? argv[argind+1] : 0; const char * const arg = ( argind + 1 < argc ) ? argv[argind+1] : 0;
if( ch2 == '-' ) if( ch2 == '-' )
{ {
if( !argv[argind][2] ) { ++argind; break; } /* we found "--" */ if( !argv[argind][2] ) { ++argind; break; } /* we found "--" */
else if( !parse_long_option( ap, opt, arg, options, &argind ) ) goto out; else if( !parse_long_option( ap, opt, arg, options, &argind ) ) goto oom;
} }
else if( !parse_short_option( ap, opt, arg, options, &argind ) ) goto out; else if( !parse_short_option( ap, opt, arg, options, &argind ) ) goto oom;
if( ap->error ) break; if( ap->error ) break;
} }
else if( flags & (ap_in_order_stop | ap_in_order_skip) ) break;
else if( flags & ap_in_order )
{ if( !push_back_argument( ap, argv[argind++] ) ) goto oom; }
else else
{ {
if( in_order ) const int min_size = ( non_options_size + 1 ) * sizeof non_options[0];
{ if( !push_back_record( ap, 0, 0, argv[argind++] ) ) goto out; } void * tmp = ap_resize_buffer( non_options, min_size );
else if( !tmp ) goto oom;
{ non_options = (const char **)tmp;
void * tmp = ap_resize_buffer( non_options, non_options[non_options_size++] = argv[argind++];
( non_options_size + 1 ) * sizeof *non_options );
if( !tmp ) goto out;
non_options = (const char **)tmp;
non_options[non_options_size++] = argv[argind++];
}
} }
} }
if( ap->error ) free_data( ap ); if( ap->error ) free_data( ap );
else else if( flags & ap_in_order_skip ) ap->argv_index = argind;
else /* copy non-option arguments */
{ {
int i; int i;
for( i = 0; i < non_options_size; ++i ) for( i = 0; i < non_options_size; ++i )
if( !push_back_record( ap, 0, 0, non_options[i] ) ) goto out; if( !push_back_argument( ap, non_options[i] ) ) goto oom;
while( argind < argc ) while( argind < argc )
if( !push_back_record( ap, 0, 0, argv[argind++] ) ) goto out; if( !push_back_argument( ap, argv[argind++] ) ) goto oom;
} }
done = 1; done = 1;
out: if( non_options ) free( non_options ); oom: if( non_options ) free( non_options );
return done; return done;
} }
void ap_free( struct Arg_parser * const ap ) void ap_free( Arg_parser * const ap )
{ {
free_data( ap ); free_data( ap );
if( ap->error ) { free( ap->error ); ap->error = 0; } if( ap->error ) { free( ap->error ); ap->error = 0; }
ap->error_size = 0;
} }
const char * ap_error( const struct Arg_parser * const ap ) const char * ap_error( const Arg_parser * const ap ) { return ap->error; }
{ return ap->error; }
int ap_argv_index( const Arg_parser * const ap ) { return ap->argv_index; }
int ap_arguments( const struct Arg_parser * const ap ) int ap_arguments( const Arg_parser * const ap ) { return ap->data_size; }
{ return ap->data_size; }
int ap_code( const Arg_parser * const ap, const int i )
int ap_code( const struct Arg_parser * const ap, const int i )
{ {
if( i < 0 || i >= ap_arguments( ap ) ) return 0; if( i < 0 || i >= ap_arguments( ap ) ) return 0;
return ap->data[i].code; return ap->data[i].code;
} }
const char * ap_parsed_name( const struct Arg_parser * const ap, const int i ) const char * ap_parsed_name( const Arg_parser * const ap, const int i )
{ {
if( i < 0 || i >= ap_arguments( ap ) || !ap->data[i].parsed_name ) return ""; if( i < 0 || i >= ap_arguments( ap ) || !ap->data[i].parsed_name ) return "";
return ap->data[i].parsed_name; return ap->data[i].parsed_name;
} }
const char * ap_argument( const struct Arg_parser * const ap, const int i ) const char * ap_argument( const Arg_parser * const ap, const int i )
{ {
if( i < 0 || i >= ap_arguments( ap ) || !ap->data[i].argument ) return ""; if( i < 0 || i >= ap_arguments( ap ) || !ap->data[i].argument ) return "";
return ap->data[i].argument; return ap->data[i].argument;

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C version) /* Arg_parser - POSIX/GNU command-line argument parser. (C version)
Copyright (C) 2006-2022 Antonio Diaz Diaz. Copyright (C) 2006-2026 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and This library is free software. Redistribution and use in source and
binary forms, with or without modification, are permitted provided binary forms, with or without modification, are permitted provided
@ -20,77 +20,85 @@
/* Arg_parser reads the arguments in 'argv' and creates a number of /* Arg_parser reads the arguments in 'argv' and creates a number of
option codes, option arguments, and non-option arguments. option codes, option arguments, and non-option arguments.
In case of error, 'ap_error' returns a non-null pointer to an error In case of error, 'ap_error' returns a pointer to an error message.
message.
'options' is an array of 'struct ap_Option' terminated by an element 'options' is an array of 'struct ap_Option' terminated by an element
containing a code which is zero. A null long_name means a short-only containing a code which is zero. A null long_name means a short-only
option. A code value outside the unsigned char range means a long-only option. A code value outside the unsigned char range means a long-only
option. option.
Arg_parser normally makes it appear as if all the option arguments Arg_parser normally makes it appear as if all the options were specified
were specified before all the non-option arguments for the purposes before all the non-option arguments for the purposes of parsing, even if
of parsing, even if the user of your program intermixed option and the user of your program intermixed options and non-option arguments. If
non-option arguments. If you want the arguments in the exact order you want the arguments in the exact order the user typed them, call
the user typed them, call 'ap_init' with 'in_order' = true. 'ap_init' with 'flags' = 'ap_in_order'.
The argument '--' terminates all options; any following arguments are The argument '--' terminates all options; any following arguments are
treated as non-option arguments, even if they begin with a hyphen. treated as non-option arguments, even if they begin with a hyphen.
The syntax for optional option arguments is '-<short_option><argument>' The syntax of options with an optional argument is
(without whitespace), or '--<long_option>=<argument>'. '-<short_option><argument>' (without whitespace), or
'--<long_option>=<argument>'.
The syntax of options with an empty argument is '-<short_option> ""',
'--<long_option> ""', or '--<long_option>=""'.
*/ */
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
enum ap_Has_arg { ap_no, ap_yes, ap_maybe }; enum ap_Flags { ap_in_order = 1, ap_in_order_stop = 2, ap_in_order_skip = 4,
ap_neg_non_opt = 8 }; /* negative is non-option */
/* ap_yesme = yes but maybe empty */
typedef enum ap_Has_arg { ap_no, ap_yes, ap_maybe, ap_yesme } ap_Has_arg;
struct ap_Option typedef struct ap_Option
{ {
int code; /* Short option letter or code ( code != 0 ) */ int code; /* Short option letter or code ( code != 0 ) */
const char * long_name; /* Long option name (maybe null) */ const char * long_name; /* Long option name (maybe null) */
enum ap_Has_arg has_arg; ap_Has_arg has_arg;
}; } ap_Option;
struct ap_Record typedef struct ap_Record
{ {
int code; int code;
char * parsed_name; char * parsed_name;
char * argument; char * argument;
}; } ap_Record;
struct Arg_parser typedef struct Arg_parser
{ {
struct ap_Record * data; ap_Record * data;
char * error; char * error;
int data_size; int data_size;
int error_size; int argv_index;
}; } Arg_parser;
char ap_init( struct Arg_parser * const ap, /* Return 0 only if out of memory. */
char ap_init( Arg_parser * const ap,
const int argc, const char * const argv[], const int argc, const char * const argv[],
const struct ap_Option options[], const char in_order ); const ap_Option options[], const int flags );
void ap_free( struct Arg_parser * const ap ); void ap_free( Arg_parser * const ap );
const char * ap_error( const struct Arg_parser * const ap ); const char * ap_error( const Arg_parser * const ap );
int ap_argv_index( const Arg_parser * const ap );
/* The number of arguments parsed. May be different from argc. */ /* The number of arguments parsed. May be different from argc. */
int ap_arguments( const struct Arg_parser * const ap ); int ap_arguments( const Arg_parser * const ap );
/* If ap_code( i ) is 0, ap_argument( i ) is a non-option. /* If ap_code( i ) is 0, ap_argument( i ) is a non-option.
Else ap_argument( i ) is the option's argument (or empty). */ Else ap_argument( i ) is the option's argument (or empty). */
int ap_code( const struct Arg_parser * const ap, const int i ); int ap_code( const Arg_parser * const ap, const int i );
/* Full name of the option parsed (short or long). */ /* Full name of the option parsed (short or long). */
const char * ap_parsed_name( const struct Arg_parser * const ap, const int i ); const char * ap_parsed_name( const Arg_parser * const ap, const int i );
const char * ap_argument( const struct Arg_parser * const ap, const int i ); const char * ap_argument( const Arg_parser * const ap, const int i );
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C version) /* Arg_parser - POSIX/GNU command-line argument parser. (C version)
Copyright (C) 2006-2022 Antonio Diaz Diaz. Copyright (C) 2006-2026 Antonio Diaz Diaz.
This program is free software: you have unlimited permission to This program is free software: you have unlimited permission to
copy, distribute, and modify it. copy, distribute, and modify it.
@ -10,9 +10,9 @@
*/ */
/* /*
Exit status: 0 for a normal exit, 1 for environmental problems Exit status: 0 for a normal exit, 1 for environmental problems
(file not found, invalid flags, I/O errors, etc), 2 to indicate a (file not found, invalid command-line options, I/O errors, etc), 2 to
corrupt or invalid input file, 3 for an internal consistency error indicate a corrupt or invalid input file, 3 for an internal consistency
(e.g., bug) which caused carg_parser to panic. error (e.g., bug) which caused carg_parser to panic.
*/ */
#include <limits.h> #include <limits.h>
@ -24,54 +24,61 @@
static const char * const program_name = "carg_parser"; static const char * const program_name = "carg_parser";
static const char * const program_year = "2022"; static const char * const program_year = "2026";
static const char * invocation_name = "carg_parser"; /* default value */ static const char * invocation_name = "carg_parser"; /* default value */
static unsigned verbosity = 0;
enum { opt_lo = 256 };
static void show_help( const char verbose ) static void show_help( void )
{ {
printf( "Arg_parser - POSIX/GNU command line argument parser. (C version)\n" fputs(
"\nArg_parser is an argument parser that follows POSIX and GNU conventions for\n" "Arg_parser - POSIX/GNU command-line argument parser. (C version)\n"
"command line arguments. There exist C++ and C versions of Arg_parser. The\n" "\nArg_parser is an argument parser that follows POSIX and GNU conventions for\n"
"C++ version is implemented as a C++ class, while the C version is\n" "command-line arguments. There exist C++ and C versions of Arg_parser. The\n"
"implemented as a single struct plus associated functions. Both are simpler,\n" "C++ version is implemented as a C++ class, while the C version is\n"
"easier to use, and safer than 'getopt_long'.\n" "implemented as a single struct plus associated functions. Both are simpler,\n"
"\nFor maximum stability, Arg_parser is self-contained. It extracts all the\n" "easier to use, and safer than 'getopt_long'.\n"
"information it needs from its arguments to avoid refering to them later.\n" "\nFor maximum stability, Arg_parser is self-contained. It extracts all the\n"
"This avoids index-out-of-bounds errors.\n" "information it needs from its arguments to avoid referring to them later.\n"
"\nArg_parser does not modify its arguments, nor uses any global variables. So\n" "This avoids index-out-of-bounds errors and allows the parser object to be\n"
"you may create more than one parser in your program if you need or want to.\n" "passed as argument to other functions for further analysis.\n"
"\nThe C++ version of Arg_parser can also parse options from configuration\n" "\nArg_parser does not modify its arguments (argc, argv), nor uses any global\n"
"files.\n" "variables.\n"
"\nTo use Arg_parser in your own programs simply copy the files 'carg_parser.h'\n" "\nThe C++ version of Arg_parser can also parse options from configuration\n"
"and 'carg_parser.c' in your source tree. See the file 'cmain.c' for an\n" "files.\n"
"example of use.\n" "\nThe C version of Arg_parser is provided in the files 'carg_parser.h' and\n"
"\nUsage: %s [options]\n", invocation_name ); "'carg_parser.c'. To learn how to use Arg_parser in your C programs, see the\n"
printf( "\nOptions:\n" "C example in the manual and the example file 'cmain.c' in the source\n"
" -h, --help display this help and exit\n" "tarball.\n", stdout );
" -V, --version output version information and exit\n" printf( "\nUsage: %s [options]\n", invocation_name );
" -a, --append example of option with no argument\n" fputs( "\nOptions:\n"
" -b, --block=<arg> example of option with required argument\n" " -h, --help display this help and exit\n"
" -c, --casual[=<arg>] example of option with optional argument\n" " -V, --version output version information and exit\n"
" -o <arg> example of short only option\n" " -a, --append example of option with no argument\n"
" --orphan example of long only option\n" " -b, --block=<arg> example of option with required argument\n"
" -q, --quiet quiet operation\n" " -c, --casual[=<arg>] example of option with optional argument\n"
" -u, --uncaught example of intentional bug\n" " -e, --empty=<arg> example of option with maybe empty argument\n"
" -v, --verbose verbose operation\n" ); " -o <arg> example of short only option\n"
if( verbose ) " --long-only example of long only option\n"
printf( " -H, --hidden example of hidden option (shown with -v -h)\n" ); " -q, --quiet quiet operation\n"
printf( "\nReport bugs to arg-parser-bug@nongnu.org\n" " -u, --uncaught example of intentional bug\n"
"Arg_parser home page: http://www.nongnu.org/arg-parser/arg_parser.html\n" ); " -v, --verbose verbose operation\n", stdout );
if( verbosity ) fputs(
" -H, --hidden example of hidden option (shown with -v -h)\n", stdout );
fputs(
"\nReport bugs to arg-parser-bug@nongnu.org\n"
"Arg_parser home page: http://www.nongnu.org/arg-parser/arg_parser.html\n", stdout );
} }
static void show_version() static void show_version( void )
{ {
printf( "%s %s\n", program_name, PROGVERSION ); printf( "%s %s\n", program_name, PROGVERSION );
printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year ); printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year );
printf( "License 2-clause BSD.\n" fputs( "License 2-clause BSD.\n"
"This is free software: you are free to change and redistribute it.\n" "This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n" ); "There is NO WARRANTY, to the extent permitted by law.\n", stdout );
} }
@ -95,75 +102,121 @@ static void internal_error( const char * const msg )
} }
static void print_opt( const char * const arg, const char * const option_name,
const int code )
{
printf( "option '%s'", option_name );
if( arg[0] || code == 'e' ) printf( " with argument '%s'", arg );
if( code == INT_MIN || code == INT_MAX ) printf( " (code 0x%X)", code );
fputc( '\n', stdout );
}
static char process_options( const Arg_parser * const parserp )
{
int argind;
verbosity = 0;
for( argind = 0; argind < ap_arguments( parserp ); ++argind )
{
const int code = ap_code( parserp, argind );
const char * const pn = ap_parsed_name( parserp, argind );
const char * const arg = ap_argument( parserp, argind );
switch( code )
{
case 0: printf( "non-option argument '%s'\n", arg ); break;
case '.': case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c':
case 'e': print_opt( arg, pn, code ); break;
case 'h': show_help(); return 0;
case 'H': case 'i': case 'I':
case 'o': print_opt( arg, pn, code ); break;
case 'q': print_opt( arg, pn, code ); verbosity = 0; break;
/* case 'u': break; */ /* intentionally not caught */
case 'v': print_opt( arg, pn, code ); ++verbosity; break;
case 'V': show_version(); return 0;
case opt_lo:
case INT_MIN: case INT_MAX: print_opt( arg, pn, code ); break;
default: internal_error( "uncaught option." );
}
} /* end process arguments */
return 1;
}
int main( const int argc, const char * const argv[] ) int main( const int argc, const char * const argv[] )
{ {
char verbose = 0; const ap_Option options[] =
const struct ap_Option options[] =
{ {
{ 'a', "append", ap_no }, /* code, long_name, has_arg (no/yes/maybe/yesme) */
{ 'b', "block", ap_yes }, { '.', 0, ap_maybe },
{ 'c', "casual", ap_maybe }, { '0', 0, ap_maybe },
{ 'h', "help", ap_no }, { '1', 0, ap_maybe },
{ 'H', "hidden", ap_no }, { '2', 0, ap_maybe },
{ 'o', 0, ap_yes }, { '3', 0, ap_maybe },
{ 'q', "quiet", ap_no }, { '4', 0, ap_maybe },
{ 'u', "uncaught", ap_no }, { '5', 0, ap_maybe },
{ 'v', "verbose", ap_no }, { '6', 0, ap_maybe },
{ 'V', "version", ap_no }, { '7', 0, ap_maybe },
{ 256, "orphan", ap_no }, { '8', 0, ap_maybe },
{ INT_MIN, "int-min", ap_no }, { '9', 0, ap_maybe },
{ INT_MAX, "int-max", ap_no }, { 'a', "append", ap_no },
{ 0, 0, ap_no } }; { 'b', "block", ap_yes },
{ 'c', "casual", ap_maybe },
{ 'e', "empty", ap_yesme },
{ 'h', "help", ap_no },
{ 'H', "hidden", ap_no },
{ 'i', 0, ap_maybe },
{ 'I', 0, ap_maybe },
{ 'o', 0, ap_yes },
{ 'q', "quiet", ap_no },
{ 'u', "uncaught", ap_no },
{ 'v', "verbose", ap_no },
{ 'V', "version", ap_no },
{ opt_lo, "long-only", ap_no },
{ INT_MIN, "int-min", ap_no },
{ INT_MAX, "int-max", ap_no },
{ 0, 0, ap_no } };
struct Arg_parser parser; Arg_parser parser;
int argind; int flags;
if( argc > 0 ) invocation_name = argv[0]; if( argc > 0 ) invocation_name = argv[0];
if( !ap_init( &parser, argc, argv, options, 0 ) ) if( !ap_init( &parser, argc, argv, options, 0 ) )
{ show_error( "Not enough memory.", 0, 0 ); return 1; } { show_error( "Not enough memory.", 0, 0 ); return 1; }
if( ap_error( &parser ) ) /* bad option */ if( ap_error( &parser ) ) /* bad option */
{ show_error( ap_error( &parser ), 0, 1 ); return 1; } { show_error( ap_error( &parser ), 0, 1 ); return 1; }
if( !process_options( &parser ) ) return 0;
fputs( !ap_arguments( &parser ) ?
"No arguments!\n" : "(options reordered)\n", stdout );
if( verbosity ) fputc( '\n', stdout ); else return 0;
for( argind = 0; argind < ap_arguments( &parser ); ++argind ) if( !ap_init( &parser, argc, argv, options, ap_neg_non_opt ) )
{ { show_error( "Not enough memory.", 0, 0 ); return 1; }
const int code = ap_code( &parser, argind ); if( ap_error( &parser ) ) /* bad option */
if( !code ) break; /* no more options */ { show_error( ap_error( &parser ), 0, 1 ); return 1; }
switch( code ) process_options( &parser );
{ fputs( "(options reordered"
case 'a': break; /* example, do nothing */ " and negative numbers treated as non-options)\n", stdout );
case 'b': break; /* example, do nothing */ if( verbosity == 1 ) return 0;
case 'c': break; /* example, do nothing */
case 'h': show_help( verbose ); return 0;
case 'H': break; /* example, do nothing */
case 'o': break; /* example, do nothing */
case 'q': verbose = 0; break;
/* case 'u': break; */ /* intentionally not caught */
case 'v': verbose = 1; break;
case 'V': show_version(); return 0;
case 256: break; /* example, do nothing */
case INT_MIN: break; /* example, do nothing */
case INT_MAX: break; /* example, do nothing */
default : internal_error( "uncaught option." );
}
} /* end process options */
for( argind = 0; argind < ap_arguments( &parser ); ++argind ) for( flags = 1; flags < 16; ++flags )
{ {
const int code = ap_code( &parser, argind ); int i;
const char * const arg = ap_argument( &parser, argind ); if( flags == ap_neg_non_opt ) continue; /* done above */
if( code ) /* option */
{
printf( "option '%s'", ap_parsed_name( &parser, argind ) );
if( arg[0] ) printf( " with argument '%s'", arg );
if( code == INT_MIN || code == INT_MAX ) printf( " (code 0x%X)", code );
}
else /* non-option */
printf( "non-option argument '%s'", arg );
fputc( '\n', stdout ); fputc( '\n', stdout );
if( !ap_init( &parser, argc, argv, options, flags ) )
{ show_error( "Not enough memory.", 0, 0 ); return 1; }
if( ap_error( &parser ) ) /* bad option */
{ show_error( ap_error( &parser ), 0, 1 ); return 1; }
process_options( &parser );
for( i = ap_argv_index( &parser ); i < argc; ++i )
printf( "skipped non-option argument '%s'\n", argv[i] );
printf( "(0%s%s%s%s)\n",
(flags & ap_in_order) ? " | in_order" : "",
(flags & ap_in_order_stop) ? " | in_order_stop" : "",
(flags & ap_in_order_skip) ? " | in_order_skip" : "",
(flags & ap_neg_non_opt) ? " | neg_non_opt" : "" );
} }
if( !ap_arguments( &parser ) ) fputs( "Hello, world!\n", stdout );
ap_free( &parser ); ap_free( &parser );
return 0; return 0;
} }

View file

@ -1,12 +1,12 @@
#! /bin/sh #! /bin/sh
# configure script for Arg_parser - POSIX/GNU command line argument parser. # configure script for Arg_parser - POSIX/GNU command-line argument parser.
# Copyright (C) 2006-2022 Antonio Diaz Diaz. # Copyright (C) 2006-2026 Antonio Diaz Diaz.
# #
# This configure script is free software: you have unlimited permission # This configure script is free software: you have unlimited permission
# to copy, distribute, and modify it. # to copy, distribute, and modify it.
pkgname=arg_parser pkgname=arg_parser
pkgversion=1.17 pkgversion=1.21
progname=arg_parser progname=arg_parser
srctrigger=doc/${pkgname}.texi srctrigger=doc/${pkgname}.texi
@ -26,6 +26,7 @@ CPPFLAGS=
CFLAGS='-Wall -W -O2' CFLAGS='-Wall -W -O2'
CXXFLAGS='-Wall -W -O2' CXXFLAGS='-Wall -W -O2'
LDFLAGS= LDFLAGS=
MAKEINFO=makeinfo
# checking whether we are using GNU C. # checking whether we are using GNU C.
/bin/sh -c "${CC} --version" > /dev/null 2>&1 || { CC=cc ; CFLAGS=-O2 ; } /bin/sh -c "${CC} --version" > /dev/null 2>&1 || { CC=cc ; CFLAGS=-O2 ; }
@ -62,7 +63,7 @@ while [ $# != 0 ] ; do
echo "Options and variables: [defaults in brackets]" echo "Options and variables: [defaults in brackets]"
echo " -h, --help display this help and exit" echo " -h, --help display this help and exit"
echo " -V, --version output version information and exit" echo " -V, --version output version information and exit"
echo " --srcdir=DIR find the sources in DIR [. or ..]" echo " --srcdir=DIR find the source code in DIR [. or ..]"
echo " --prefix=DIR install into DIR [${prefix}]" echo " --prefix=DIR install into DIR [${prefix}]"
echo " --exec-prefix=DIR base directory for arch-dependent files [${exec_prefix}]" echo " --exec-prefix=DIR base directory for arch-dependent files [${exec_prefix}]"
echo " --bindir=DIR user executables directory [${bindir}]" echo " --bindir=DIR user executables directory [${bindir}]"
@ -71,12 +72,13 @@ while [ $# != 0 ] ; do
echo " --mandir=DIR man pages directory [${mandir}]" echo " --mandir=DIR man pages directory [${mandir}]"
echo " CC=COMPILER C compiler to use [${CC}]" echo " CC=COMPILER C compiler to use [${CC}]"
echo " CXX=COMPILER C++ compiler to use [${CXX}]" echo " CXX=COMPILER C++ compiler to use [${CXX}]"
echo " CPPFLAGS=OPTIONS command line options for the preprocessor [${CPPFLAGS}]" echo " CPPFLAGS=OPTIONS command-line options for the preprocessor [${CPPFLAGS}]"
echo " CFLAGS=OPTIONS command line options for the C compiler [${CFLAGS}]" echo " CFLAGS=OPTIONS command-line options for the C compiler [${CFLAGS}]"
echo " CFLAGS+=OPTIONS append options to the current value of CFLAGS" echo " CFLAGS+=OPTIONS append options to the current value of CFLAGS"
echo " CXXFLAGS=OPTIONS command line options for the C++ compiler [${CXXFLAGS}]" echo " CXXFLAGS=OPTIONS command-line options for the C++ compiler [${CXXFLAGS}]"
echo " CXXFLAGS+=OPTIONS append options to the current value of CXXFLAGS" echo " CXXFLAGS+=OPTIONS append options to the current value of CXXFLAGS"
echo " LDFLAGS=OPTIONS command line options for the linker [${LDFLAGS}]" echo " LDFLAGS=OPTIONS command-line options for the linker [${LDFLAGS}]"
echo " MAKEINFO=NAME makeinfo program to use [${MAKEINFO}]"
echo echo
exit 0 ;; exit 0 ;;
--version | -V) --version | -V)
@ -107,17 +109,16 @@ while [ $# != 0 ] ; do
CXXFLAGS=*) CXXFLAGS=${optarg} ;; CXXFLAGS=*) CXXFLAGS=${optarg} ;;
CXXFLAGS+=*) CXXFLAGS="${CXXFLAGS} ${optarg}" ;; CXXFLAGS+=*) CXXFLAGS="${CXXFLAGS} ${optarg}" ;;
LDFLAGS=*) LDFLAGS=${optarg} ;; LDFLAGS=*) LDFLAGS=${optarg} ;;
MAKEINFO=*) MAKEINFO=${optarg} ;;
--*) --*) echo "configure: WARNING: unrecognized option: '${option}'" 1>&2 ;;
echo "configure: WARNING: unrecognized option: '${option}'" 1>&2 ;;
*=* | *-*-*) ;; *=* | *-*-*) ;;
*) *) echo "configure: unrecognized option: '${option}'" 1>&2
echo "configure: unrecognized option: '${option}'" 1>&2
echo "Try 'configure --help' for more information." 1>&2 echo "Try 'configure --help' for more information." 1>&2
exit 1 ;; exit 1 ;;
esac esac
# Check if the option took a separate argument # Check whether the option took a separate argument
if [ "${arg2}" = yes ] ; then if [ "${arg2}" = yes ] ; then
if [ $# != 0 ] ; then args="${args} \"$1\"" ; shift if [ $# != 0 ] ; then args="${args} \"$1\"" ; shift
else echo "configure: Missing argument to '${option}'" 1>&2 else echo "configure: Missing argument to '${option}'" 1>&2
@ -126,7 +127,7 @@ while [ $# != 0 ] ; do
fi fi
done done
# Find the source files, if location was not specified. # Find the source code, if location was not specified.
srcdirtext= srcdirtext=
if [ -z "${srcdir}" ] ; then if [ -z "${srcdir}" ] ; then
srcdirtext="or . or .." ; srcdir=. srcdirtext="or . or .." ; srcdir=.
@ -138,7 +139,7 @@ if [ -z "${srcdir}" ] ; then
fi fi
if [ ! -r "${srcdir}/${srctrigger}" ] ; then if [ ! -r "${srcdir}/${srctrigger}" ] ; then
echo "configure: Can't find sources in ${srcdir} ${srcdirtext}" 1>&2 echo "configure: Can't find source code in ${srcdir} ${srcdirtext}" 1>&2
echo "configure: (At least ${srctrigger} is missing)." 1>&2 echo "configure: (At least ${srctrigger} is missing)." 1>&2
exit 1 exit 1
fi fi
@ -158,7 +159,7 @@ if [ -z "${no_create}" ] ; then
# This script is free software: you have unlimited permission # This script is free software: you have unlimited permission
# to copy, distribute, and modify it. # to copy, distribute, and modify it.
exec /bin/sh $0 ${args} --no-create exec /bin/sh "$0" ${args} --no-create
EOF EOF
chmod +x config.status chmod +x config.status
fi fi
@ -177,10 +178,11 @@ echo "CPPFLAGS = ${CPPFLAGS}"
echo "CFLAGS = ${CFLAGS}" echo "CFLAGS = ${CFLAGS}"
echo "CXXFLAGS = ${CXXFLAGS}" echo "CXXFLAGS = ${CXXFLAGS}"
echo "LDFLAGS = ${LDFLAGS}" echo "LDFLAGS = ${LDFLAGS}"
echo "MAKEINFO = ${MAKEINFO}"
rm -f Makefile rm -f Makefile
cat > Makefile << EOF cat > Makefile << EOF
# Makefile for Arg_parser - POSIX/GNU command line argument parser. # Makefile for Arg_parser - POSIX/GNU command-line argument parser.
# Copyright (C) 2006-2022 Antonio Diaz Diaz. # Copyright (C) 2006-2026 Antonio Diaz Diaz.
# This file was generated automatically by configure. Don't edit. # This file was generated automatically by configure. Don't edit.
# #
# This Makefile is free software: you have unlimited permission # This Makefile is free software: you have unlimited permission
@ -202,6 +204,7 @@ CPPFLAGS = ${CPPFLAGS}
CFLAGS = ${CFLAGS} CFLAGS = ${CFLAGS}
CXXFLAGS = ${CXXFLAGS} CXXFLAGS = ${CXXFLAGS}
LDFLAGS = ${LDFLAGS} LDFLAGS = ${LDFLAGS}
MAKEINFO = ${MAKEINFO}
EOF EOF
cat "${srcdir}/Makefile.in" >> Makefile cat "${srcdir}/Makefile.in" >> Makefile

View file

@ -1,32 +1,34 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.16. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2.
.TH ARG_PARSER "1" "February 2022" "arg_parser 1.17" "User Commands" .TH ARG_PARSER "1" "March 2026" "arg_parser 1.21" "User Commands"
.SH NAME .SH NAME
arg_parser \- test program for Arg_parser arg_parser \- test program for Arg_parser
.SH SYNOPSIS .SH SYNOPSIS
.B arg_parser .B arg_parser
[\fI\,options\/\fR] [\fI\,options\/\fR]
.SH DESCRIPTION .SH DESCRIPTION
Arg_parser \- POSIX/GNU command line argument parser. (C++ version) Arg_parser \- POSIX/GNU command\-line argument parser. (C++ version)
.PP .PP
Arg_parser is an argument parser that follows POSIX and GNU conventions for Arg_parser is an argument parser that follows POSIX and GNU conventions for
command line arguments. There exist C++ and C versions of Arg_parser. The command\-line arguments. There exist C++ and C versions of Arg_parser. The
C++ version is implemented as a C++ class, while the C version is C++ version is implemented as a C++ class, while the C version is
implemented as a single struct plus associated functions. Both are simpler, implemented as a single struct plus associated functions. Both are simpler,
easier to use, and safer than 'getopt_long'. easier to use, and safer than 'getopt_long'.
.PP .PP
For maximum stability, Arg_parser is self\-contained. It extracts all the For maximum stability, Arg_parser is self\-contained. It extracts all the
information it needs from its arguments to avoid refering to them later. information it needs from its arguments to avoid referring to them later.
This avoids index\-out\-of\-bounds errors. This avoids index\-out\-of\-bounds errors and allows the parser object to be
passed as argument to other functions for further analysis.
.PP .PP
Arg_parser does not modify its arguments, nor uses any global variables. So Arg_parser does not modify its arguments (argc, argv), nor uses any global
you may create more than one parser in your program if you need or want to. variables.
.PP .PP
The C++ version of Arg_parser can also parse options from configuration The C++ version of Arg_parser can also parse options from configuration
files. files.
.PP .PP
To use Arg_parser in your own programs simply copy the files 'arg_parser.h' The C++ version of Arg_parser is provided in the files 'arg_parser.h' and
and 'arg_parser.cc' in your source tree. See the file 'main.cc' for an \&'arg_parser.cc'. To learn how to use Arg_parser in your C++ programs, see
example of use. the C++ example in the manual and the example file 'main.cc' in the source
tarball.
.SH OPTIONS .SH OPTIONS
.TP .TP
\fB\-h\fR, \fB\-\-help\fR \fB\-h\fR, \fB\-\-help\fR
@ -44,10 +46,13 @@ example of option with required argument
\fB\-c\fR, \fB\-\-casual[=\fR<arg>] \fB\-c\fR, \fB\-\-casual[=\fR<arg>]
example of option with optional argument example of option with optional argument
.TP .TP
\fB\-e\fR, \fB\-\-empty=\fR<arg>
example of option with maybe empty argument
.TP
\fB\-o\fR <arg> \fB\-o\fR <arg>
example of short only option example of short only option
.TP .TP
\fB\-\-orphan\fR \fB\-\-long\-only\fR
example of long only option example of long only option
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
@ -63,7 +68,7 @@ Report bugs to arg\-parser\-bug@nongnu.org
.br .br
Arg_parser home page: http://www.nongnu.org/arg\-parser/arg_parser.html Arg_parser home page: http://www.nongnu.org/arg\-parser/arg_parser.html
.SH COPYRIGHT .SH COPYRIGHT
Copyright \(co 2022 Antonio Diaz Diaz. Copyright \(co 2026 Antonio Diaz Diaz.
License 2\-clause BSD. License 2\-clause BSD.
.br .br
This is free software: you are free to change and redistribute it. This is free software: you are free to change and redistribute it.

View file

@ -3,7 +3,7 @@ arg_parser.texi.
INFO-DIR-SECTION Libraries INFO-DIR-SECTION Libraries
START-INFO-DIR-ENTRY START-INFO-DIR-ENTRY
* Arg_parser: (arg_parser). A POSIX/GNU command line argument parser * Arg_parser: (arg_parser). A POSIX/GNU command-line argument parser
END-INFO-DIR-ENTRY END-INFO-DIR-ENTRY
 
@ -12,22 +12,24 @@ File: arg_parser.info, Node: Top, Next: Introduction, Up: (dir)
Arg_parser Manual Arg_parser Manual
***************** *****************
This manual is for Arg_parser (version 1.17, 5 February 2022). This manual is for Arg_parser (version 1.21, 10 March 2026).
* Menu: * Menu:
* Introduction:: Purpose and features of Arg_parser * Introduction:: Purpose and features of Arg_parser
* Argument syntax:: By convention, options start with a hyphen * Argument syntax:: By convention, options start with a hyphen
* Initialization:: Parsing arguments and reporting errors * C++ version:: Using the C++ version of Arg_parser
* Using Arg_parser:: Reading the options and arguments parsed
* C version:: Using the C version of Arg_parser * C version:: Using the C version of Arg_parser
* C++ example:: Tutorial for the C++ version
* C example:: Tutorial for the C version
* Problems:: Reporting bugs * Problems:: Reporting bugs
* Concept index:: Index of concepts * Concept index:: Index of concepts
* Function index:: Index of functions, constants, and variables
Copyright (C) 2006-2022 Antonio Diaz Diaz. Copyright (C) 2006-2026 Antonio Diaz Diaz.
This manual is free documentation: you have unlimited permission to copy, This manual is free documentation: you have unlimited permission to copy,
distribute, and modify it. distribute, and modify it.
 
@ -37,94 +39,135 @@ File: arg_parser.info, Node: Introduction, Next: Argument syntax, Prev: Top,
************** **************
Arg_parser is an argument parser that follows POSIX and GNU conventions for Arg_parser is an argument parser that follows POSIX and GNU conventions for
command line arguments. There exist C++ and C versions of Arg_parser. The command-line arguments. There exist C++ and C versions of Arg_parser. The
C++ version is implemented as a C++ class, while the C version is C++ version is implemented as a C++ class, while the C version is
implemented as a single struct plus associated functions. Both are simpler, implemented as a single struct plus associated functions. Both are simpler,
easier to use, and safer than 'getopt_long'. easier to use, and safer than 'getopt_long'.
For maximum stability, Arg_parser is self-contained. It extracts all the For maximum stability, Arg_parser is self-contained. It extracts all the
information it needs from its arguments to avoid refering to them later. information it needs from its arguments to avoid referring to them later.
This avoids index-out-of-bounds errors. This avoids index-out-of-bounds errors and allows the parser object to be
passed as argument to other functions for further analysis.
Arg_parser does not modify its arguments, nor uses any global variables. Arg_parser does not modify its arguments (argc, argv), nor uses any global
So you may create more than one parser in your program if you need or want variables.
to.
The C++ version of Arg_parser can also parse options from configuration The C++ version of Arg_parser can also parse options from configuration
files. files.
Arg_parser was developed as the argument parser for GNU moe, because Arg_parser was developed as the argument parser for GNU moe, because moe's
moe's argument parsing is rather complex. Then I used it in my other argument parsing is rather complex. Then I used it in my other projects,
projects, including GNU ddrescue, GNU ed, lzip, GNU ocrad, tarlz, and including GNU ddrescue, GNU ed, lzip, GNU ocrad, tarlz, and zutils, with
zutils, with excellent results. excellent results.
1.1 Differences with 'getopt' and 'getopt_long'.
================================================
'getopt' parses the options one by one, which requires internal state. Part
of this state is stored in several global variables, and part of it is
hidden. For example, the POSIX specification of 'getopt' states that:
When an element of argv[] contains multiple option characters, it is
unspecified how getopt() determines which options have already been
processed.
Parsing the options one by one is also a lot of work for the application,
which must check for errors (invalid option, missing option argument, etc)
for each option. 'getopt' is the 1970s way of parsing command-line
arguments.
'getopt_long' is a not-very-well-designed extension on top of 'getopt'. In
addition to parsing the options one by one as 'getopt' does, 'getopt_long'
accepts configuration data from two overlapping sources, which makes it
easy for errors in the configuration to remain undetected. For example, a
certain long option can be declared to be equivalent to a given short
option, but both forms of the option may contain contradictory requirements
for the option's argument.
OTOH, Arg_parser parses the whole command line at once and fills a struct
with the results, like 'gmtime_r' and 'stat' do. This way, errors are
checked once, there is no need to permute the elements of argv, and the
struct returned can be used undisturbed by later changes in argv.
Finally, using Arg_parser instead of 'getopt_long' reduces the size of your
source code because, as 'getopt_long' is not available on all systems, a
portable program using it needs to ship its (larger) source anyway.
 
File: arg_parser.info, Node: Argument syntax, Next: Initialization, Prev: Introduction, Up: Top File: arg_parser.info, Node: Argument syntax, Next: C++ version, Prev: Introduction, Up: Top
2 Syntax of command line arguments 2 Syntax of command-line arguments
********************************** **********************************
POSIX recommends these conventions for command line arguments. Arg_parser POSIX recommends these conventions for command-line arguments. Arg_parser
makes it easy to implement them. makes it easy to implement them.
* A command line argument is an option if it begins with a hyphen * A command-line argument is an option if it begins with a hyphen ('-').
delimiter ('-').
* Multiple options may follow a hyphen delimiter in a single token if
the options don't take arguments. Thus, '-abc' is equivalent to
'-a -b -c'.
* Option names are single alphanumeric characters. * Option names are single alphanumeric characters.
* Certain options require an argument. * Certain options require an argument.
* An option and its argument may or may not appear as separate tokens. * An option and its argument may or may not appear as separate tokens.
(In other words, the whitespace separating them is optional). Thus, (In other words, the whitespace separating them is optional, unless the
'-o foo' and '-ofoo' are equivalent. argument is the empty string). Thus, '-o foo' and '-ofoo' are
equivalent.
* One or more options without arguments, followed by at most one option
that takes an argument, may follow a hyphen in a single token. Thus,
'-abc' is equivalent to '-a -b -c'.
* Options typically precede other non-option arguments. * Options typically precede other non-option arguments.
Arg_parser normally makes it appear as if all the option arguments were Arg_parser normally makes it appear as if all the options were
specified before all the non-option arguments for the purposes of specified before all the non-option arguments for the purposes of
parsing, even if the user of your program intermixed option and parsing, even if the user of your program intermixed options and
non-option arguments. If you want the arguments in the exact order the non-option arguments. If you want the arguments in the exact order the
user typed them, call 'Arg_parser' with IN_ORDER = true. user typed them, call 'Arg_parser' with FLAGS = 'in_order'.
* The argument '--' terminates all options; any following arguments are * The argument '--' terminates all options; any following arguments are
treated as non-option arguments, even if they begin with a hyphen. treated as non-option arguments, even if they begin with a hyphen.
* A token consisting of a single hyphen character is interpreted as an * A token consisting of a single hyphen character is interpreted as an
ordinary non-option argument. By convention, it is used to specify ordinary non-option argument. By convention, it is used to specify
input from or output to the standard input and output streams. standard input, standard output, or a file named '-'.
* Options may be supplied in any order, or appear multiple times. The * Options may be supplied in any order, or appear multiple times. The
interpretation is left up to the particular application program. interpretation is left up to the particular application program.
* GNU adds "long options" to these conventions. Long options consist of GNU adds "long options" to these conventions:
'--' followed by a name made of alphanumeric characters and hyphens.
Option names are typically one to three words long, with hyphens to * A long option consists of two hyphens ('--') followed by a name made
separate words. Users can abbreviate the option names as long as the of alphanumeric characters and hyphens. Option names are typically one
abbreviations are unique. to three words long, with hyphens to separate words. Abbreviations can
be used for the long option names as long as the abbreviations are
unique.
* A long option and its argument may or may not appear as separate * A long option and its argument may or may not appear as separate
tokens. In the latter case they must be separated by an equal sign '='. tokens. In the latter case they must be separated by an equal sign '='.
Thus, '--foo bar' and '--foo=bar' are equivalent. Thus, '--foo bar' and '--foo=bar' are equivalent.
* The syntax for optional option arguments is The syntax of options with an optional argument is
'-<short_option><argument>' (without whitespace), or '-<short_option><argument>' (without whitespace), or
'--<long_option>=<argument>'. '--<long_option>=<argument>'.
The syntax of options with an empty argument is '-<short_option> ""',
'--<long_option> ""', or '--<long_option>=""'.
 
File: arg_parser.info, Node: Initialization, Next: Using Arg_parser, Prev: Argument syntax, Up: Top File: arg_parser.info, Node: C++ version, Next: C version, Prev: Argument syntax, Up: Top
3 Parsing arguments and reporting errors 3 Using the C++ version of Arg_parser
**************************************** *************************************
To use Arg_parser in your own programs first copy the files 'arg_parser.h' The C++ version of Arg_parser is provided in the files 'arg_parser.h' and
and 'arg_parser.cc' in your source tree. See the file 'main.cc' for an 'arg_parser.cc'. To learn how to use Arg_parser in your C++ programs, *note
example of use. C++ example::, and the file 'main.cc' in the source tarball.
The class 'Arg_parser' has two constructors; one to parse command line 3.1 Parsing arguments and reporting errors
==========================================
The class 'Arg_parser' has two constructors; one to parse command-line
arguments from ARGV, and the other to parse a single token from a arguments from ARGV, and the other to parse a single token from a
configuration file or other source. configuration file or other source.
@ -132,28 +175,69 @@ configuration file or other source.
This structure describes a single option for the sake of 'Arg_parser'. This structure describes a single option for the sake of 'Arg_parser'.
The argument OPTIONS must be an array of these structures, one for The argument OPTIONS must be an array of these structures, one for
each option. Terminate the array with an element containing a code each option. Terminate the array with an element containing a code
which is zero. which is zero. 'struct Option' has the following members:
'struct Option' has the following members: -- Member: 'int' code
'int code'
This member is the code that identifies the option, normally the This member is the code that identifies the option, normally the
short-option character. Must be different from 0. A code value short-option character. Must be different from 0. A code value
outside the unsigned char range means a long-only option. outside the unsigned char range means a long-only option.
'const char * long_name' -- Member: 'const char *' long_name
This member is the long option name. It is a zero-terminated This member is the long option name. It is a zero-terminated
string. A null or empty long_name means a short-only option. string. A null or empty long_name means a short-only option.
'enum Has_arg has_arg' -- Member: 'enum Has_arg' has_arg
This member says whether the option takes an argument. It has This member says whether the option takes an argument. It has
three valid values: 'no', 'yes', and 'maybe'. four valid values: 'no', 'yes', 'maybe', and 'yesme', meaning
respectively 'no argument', 'non-empty argument required',
'optional argument', and 'argument required, but it may be the
empty string'. 'yesme' is not recommended.
If an empty argument is passed to an option specified as 'yesme',
it must be in a separate command-line argument or specified as a
long option with the empty string after the '=' character. It
can't be specified as the empty string after the short option.
-- Data Type: enum Flags
The argument FLAGS is a bit mask. You can bitwise-OR the following
constants and assign them to FLAGS to modify the way in which the
arguments are parsed. FLAGS = 0 chooses the default behavior (reorder
the options to put them before the non-option arguments).
-- Constant: 'Flags' in_order
Store the arguments in the exact order the user typed them,
without reordering the options to put them before the non-option
arguments.
-- Constant: 'Flags' in_order_stop
Stop option processing when finding the first non-option argument
as if the argument '--' had been found before it. Store the first
non-option argument and all the arguments following it as
non-option arguments, even if they begin with a hyphen. This is
similar to the POSIX function 'getopt', which stops option
processing after finding the first non-option argument.
-- Constant: 'Flags' in_order_skip
Parse only the heading options. Skip the first non-option
argument and all the arguments following it; do not parse nor
store them. The function 'argv_index' returns the index in ARGV
of the first argument skipped (the first non-option argument), or
ARGC if no arguments were skipped. This mode is useful for
parsing the command line of programs that invoke other programs,
like 'timeout', 'xargs', or 'valgrind'.
-- Constant: 'Flags' neg_non_opt
Parse the negative numbers, including '-inf', '-Inf', and '-INF',
as non-option arguments without reordering them. Numbers start
with a digit or a period and a digit. This mode is useful for
parsing the command line of tools like 'seq' which take negative
numbers as arguments.
-- Function: Arg_parser ( const int ARGC, const char * const ARGV[], const -- Function: Arg_parser ( const int ARGC, const char * const ARGV[], const
Option OPTIONS[], const bool IN_ORDER = false ) Option OPTIONS[], const int FLAGS = 0 )
Constructor. Reads the arguments in ARGV and parses all options, Constructor. Reads the arguments in ARGV and parses all options,
option arguments, and non-option arguments contained in them. In case option arguments, and non-option arguments contained in them. In case
of error, 'error().size()' will be non-zero. of error, 'error().size()' returns nonzero.
-- Function: Arg_parser ( const char * const OPT, const char * const ARG, -- Function: Arg_parser ( const char * const OPT, const char * const ARG,
const Option OPTIONS[] ) const Option OPTIONS[] )
@ -161,22 +245,25 @@ configuration file or other source.
token in case an argument is needed for an option parsed). Can be used token in case an argument is needed for an option parsed). Can be used
to parse options from a configuration file one at a time. Be warned to parse options from a configuration file one at a time. Be warned
that a single token may produce an undefined number of short options. that a single token may produce an undefined number of short options.
In case of error, 'error().size()' will be non-zero. In case of error, 'error().size()' returns nonzero.
-- Function: const std::string & error () const -- Function: const std::string & error () const
Use this funtion to verify that the arguments have been correctly Use this funtion to check that the arguments have been correctly
parsed by the constructor. If there was an error parsing the arguments, parsed by the constructor. If there was an error parsing the
'error' returns a non-empty error message explaining the cause. arguments, 'error' returns an error message explaining the cause, else
it returns an empty string.
 -- Function: int argv_index () const
File: arg_parser.info, Node: Using Arg_parser, Next: C version, Prev: Initialization, Up: Top Return the index in ARGV of the first argument skipped (the first
non-option argument) when FLAGS was set to 'in_order_skip'. If no
arguments were skipped, ARGC is returned instead.
4 Using the class 'Arg_parser' 3.2 Reading the options and arguments parsed
****************************** ============================================
After a successful call to the constructor, which must be verified by After a successful call to the constructor, which must be checked by calling
calling 'error', the options and arguments parsed can be accessed by means 'error', the options and arguments parsed can be accessed by means of the
of the following functions: following functions:
-- Function: int arguments () const -- Function: int arguments () const
This function returns the number of options and non-option arguments This function returns the number of options and non-option arguments
@ -185,8 +272,8 @@ of the following functions:
-- Function: int code ( const int I ) const -- Function: int code ( const int I ) const
This function returns the code of the option at position I. Valid This function returns the code of the option at position I. Valid
values for I range from 0 to 'arguments() - 1'. If the code returned values for I range from 0 to 'arguments() - 1'. If the code returned
is non-zero, 'argument(I)' is the option's argument (or is empty if is nonzero, 'argument(I)' is the option's argument (or is empty if the
the option does not have an argument). If the code returned is zero, option does not have an argument). If the code returned is zero,
'argument(I)' is a non-option argument. 'argument(I)' is a non-option argument.
-- Function: const std::string & parsed_name ( const int I ) const -- Function: const std::string & parsed_name ( const int I ) const
@ -199,80 +286,291 @@ of the following functions:
This function returns the argument at position I. It may be the This function returns the argument at position I. It may be the
argument of an option or a non-option argument, depending on the value argument of an option or a non-option argument, depending on the value
returned by 'code(I)'. Valid values for I range from 0 to returned by 'code(I)'. Valid values for I range from 0 to
'arguments() - 1'. 'arguments() - 1'. If the argument does not exist, the empty string is
returned.
 
File: arg_parser.info, Node: C version, Next: Problems, Prev: Using Arg_parser, Up: Top File: arg_parser.info, Node: C version, Next: C++ example, Prev: C++ version, Up: Top
5 Using the C version of Arg_parser 4 Using the C version of Arg_parser
*********************************** ***********************************
To use the C version of Arg_parser in your own programs first copy the files The C version of Arg_parser is provided in the files 'carg_parser.h' and
'carg_parser.h' and 'carg_parser.c' in your source tree. See the file 'carg_parser.c'. To learn how to use Arg_parser in your C programs, *note C
'cmain.c' for an example of use. example::, and the file 'cmain.c' in the source tarball.
Then you need to declare a variable of type 'struct Arg_parser', pass 4.1 Parsing arguments and reporting errors
its address to 'ap_init' to initialize it, and verify that 'ap_error' ==========================================
returns 0.
'struct ap_Option' is identical to 'struct Option', except that You need to declare a variable of type 'Arg_parser', pass its address to
'Has_arg' becomes 'ap_Has_arg', and the names of its three values are also 'ap_init' to initialize it, and check that 'ap_error' returns 0.
prefixed with 'ap_'. *Note struct Option::, for details about the members.
-- Function: char ap_init ( struct Arg_parser * const AP, const int ARGC, 'struct ap_Option' is identical to 'struct Option', except that 'Has_arg'
const char * const ARGV[], const struct ap_Option OPTIONS[], becomes 'ap_Has_arg', and the names of its four values are also prefixed
const char IN_ORDER ) with 'ap_'. *Note struct Option::, for details about the members.
'enum ap_Flags' is identical to 'enum Flags', except that its constants are
also prefixed with 'ap_'. *Note enum Flags::, for a description of the
constants.
-- Function: char ap_init ( Arg_parser * const AP, const int ARGC, const
char * const ARGV[], const ap_Option OPTIONS[], const int FLAGS )
Reads the arguments in ARGV and parses all options, option arguments, Reads the arguments in ARGV and parses all options, option arguments,
and non-option arguments contained in them. Returns 0 if there is not and non-option arguments contained in them. Returns 0 if there is not
enough memory, else 1. In case of error, 'ap_error' will return a enough memory, else 1 (even if errors are found). In case of error,
non-null pointer. 'ap_error' returns a non-null pointer.
-- Function: void ap_free ( struct Arg_parser * const AP ) -- Function: void ap_free ( Arg_parser * const AP )
Frees all dynamically allocated data structures. Frees all dynamically allocated data structures.
-- Function: const char * ap_error ( const struct Arg_parser * const AP ) -- Function: const char * ap_error ( const Arg_parser * const AP )
Use this funtion to verify that the arguments have been correctly Use this funtion to check that the arguments have been correctly
parsed by 'ap_init'. If there was an error parsing the arguments, parsed by 'ap_init'. If there was an error parsing the arguments,
'ap_error' returns a pointer to an error message explaining the cause, 'ap_error' returns a pointer to an error message explaining the cause,
else it returns a null pointer. else it returns a null pointer.
After a successful call to 'ap_init', which must be verified by calling -- Function: int ap_argv_index ( const Arg_parser * const AP )
Return the index in ARGV of the first argument skipped (the first
non-option argument) when FLAGS was set to 'ap_in_order_skip'. If no
arguments were skipped, ARGC is returned instead.
4.2 Reading the options and arguments parsed
============================================
After a successful call to 'ap_init', which must be checked by calling
'ap_error', the options and arguments parsed can be accessed by means of 'ap_error', the options and arguments parsed can be accessed by means of
the following functions: the following functions:
-- Function: int ap_arguments ( const struct Arg_parser * const AP ) -- Function: int ap_arguments ( const Arg_parser * const AP )
This function returns the number of options and non-option arguments This function returns the number of options and non-option arguments
parsed. This number is usually different from argc. parsed. This number is usually different from argc.
-- Function: int ap_code ( const struct Arg_parser * const AP, const int I -- Function: int ap_code ( const Arg_parser * const AP, const int I )
)
This function returns the code of the option at position I. Valid This function returns the code of the option at position I. Valid
values for I range from 0 to 'ap_arguments() - 1'. If the code values for I range from 0 to 'ap_arguments() - 1'. If the code
returned is non-zero, 'ap_argument(I)' is the option's argument (or is returned is nonzero, 'ap_argument(I)' is the option's argument (or is
empty if the option does not have an argument). If the code returned empty if the option does not have an argument). If the code returned
is zero, 'ap_argument(I)' is a non-option argument. is zero, 'ap_argument(I)' is a non-option argument.
-- Function: const char * ap_parsed_name ( const struct Arg_parser * const -- Function: const char * ap_parsed_name ( const Arg_parser * const AP,
AP, const int I ) const int I )
This function returns the full name of the option parsed (short or This function returns the full name of the option parsed (short or
long) at position I. It may be useful to produce more accurate long) at position I. It may be useful to produce more accurate
diagnostic messages. For non-option arguments it returns the empty diagnostic messages. For non-option arguments it returns the empty
string. string.
-- Function: const char * ap_argument ( const struct Arg_parser * const -- Function: const char * ap_argument ( const Arg_parser * const AP, const
AP, const int I ) int I )
This function returns the argument at position I. It may be the This function returns the argument at position I. It may be the
argument of an option or a non-option argument, depending on the value argument of an option or a non-option argument, depending on the value
returned by 'ap_code(I)'. Valid values for I range from 0 to returned by 'ap_code(I)'. Valid values for I range from 0 to
'ap_arguments() - 1'. 'ap_arguments() - 1'. If the argument does not exist, the empty string
is returned.
When you are finished, you should free all dynamically allocated data When you are finished, you should free all dynamically allocated data
structures by calling 'ap_free'. structures by calling 'ap_free'.
 
File: arg_parser.info, Node: Problems, Next: Concept index, Prev: C version, Up: Top File: arg_parser.info, Node: C++ example, Next: C example, Prev: C version, Up: Top
6 Reporting bugs 5 Tutorial for the C++ version
******************************
This tutorial uses lzip as an example of how to use Arg_parser in a C++
program. You need to follow these 6 steps:
First copy the files 'arg_parser.h' and 'arg_parser.cc' in your source
tree, in the same directory as the file containing the function 'main' of
your program. In lzip, 'main' is in 'main.cc'.
Second, include these header files near the top of 'main.cc':
#include <string>
#include <vector>
#include "arg_parser.h"
Third, define inside 'main' the option names and argument requirements.
Lzip defines the following options:
const Arg_parser::Option options[] = {
{ '0', "fast", Arg_parser::no },
{ '1', 0, Arg_parser::no },
{ '2', 0, Arg_parser::no },
{ '3', 0, Arg_parser::no },
{ '4', 0, Arg_parser::no },
{ '5', 0, Arg_parser::no },
{ '6', 0, Arg_parser::no },
{ '7', 0, Arg_parser::no },
{ '8', 0, Arg_parser::no },
{ '9', "best", Arg_parser::no },
{ 'a', "trailing-error", Arg_parser::no },
{ 'b', "member-size", Arg_parser::yes },
{ 'c', "stdout", Arg_parser::no },
{ 'd', "decompress", Arg_parser::no },
{ 'f', "force", Arg_parser::no },
{ 'F', "recompress", Arg_parser::no },
{ 'h', "help", Arg_parser::no },
{ 'k', "keep", Arg_parser::no },
{ 'l', "list", Arg_parser::no },
{ 'm', "match-length", Arg_parser::yes },
{ 'n', "threads", Arg_parser::yes },
{ 'o', "output", Arg_parser::yes },
{ 'q', "quiet", Arg_parser::no },
{ 's', "dictionary-size", Arg_parser::yes },
{ 'S', "volume-size", Arg_parser::yes },
{ 't', "test", Arg_parser::no },
{ 'v', "verbose", Arg_parser::no },
{ 'V', "version", Arg_parser::no },
{ opt_lt, "loose-trailing", Arg_parser::no },
{ 0, 0, Arg_parser::no } };
Fourth, declare and initialize the parser:
const Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option
{ show_error( parser.error().c_str(), 0, true ); return 1; }
Fifth, perform the actions corresponding to each option parsed. Lzip
performs the following actions:
int argind = 0;
for( ; argind < parser.arguments(); ++argind )
{
const int code = parser.code( argind );
if( !code ) break; // no more options
const char * const pn = parser.parsed_name( argind ).c_str();
const std::string & sarg = parser.argument( argind );
const char * const arg = sarg.c_str();
switch( code )
{
case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9': zero = code == '0';
encoder_options = option_mapping[code-'0']; break;
case 'a': cl_opts.ignore_trailing = false; break;
case 'b': member_size = getnum(arg, pn, 100000, max_member_size); break;
case 'c': to_stdout = true; break;
case 'd': set_mode( program_mode, m_decompress ); break;
case 'f': force = true; break;
case 'F': recompress = true; break;
case 'h': show_help(); return 0;
case 'k': keep_input_files = true; break;
case 'l': set_mode( program_mode, m_list ); break;
case 'm': encoder_options.match_len_limit =
getnum( arg, pn, min_match_len_limit, max_match_len );
zero = false; break;
case 'n': break; // ignored
case 'o': if( sarg == "-" ) to_stdout = true;
else { default_output_filename = sarg; } break;
case 'q': verbosity = -1; break;
case 's': encoder_options.dictionary_size = get_dict_size( arg, pn );
zero = false; break;
case 'S': volume_size = getnum(arg, pn, 100000, max_volume_size); break;
case 't': set_mode( program_mode, m_test ); break;
case 'v': if( verbosity < 4 ) ++verbosity; break;
case 'V': show_version(); return 0;
case opt_lt: cl_opts.loose_trailing = true; break;
default: internal_error( "uncaught option." );
}
} // end process options
Sixth, process any remaining non-option arguments (file names in the case of
lzip):
std::vector< std::string > filenames;
bool filenames_given = false;
for( ; argind < parser.arguments(); ++argind )
{
filenames.push_back( parser.argument( argind ) );
if( filenames.back() != "-" ) filenames_given = true;
}
if( filenames.empty() ) filenames.push_back("-");
// do something with 'filenames'

File: arg_parser.info, Node: C example, Next: Problems, Prev: C++ example, Up: Top
6 Tutorial for the C version
****************************
This tutorial uses GNU ed as an example of how to use Arg_parser in a C
program. You need to follow these 6 steps:
First copy the files 'carg_parser.h' and 'carg_parser.c' in your source
tree, in the same directory as the file containing the function 'main' of
your program. In GNU ed, 'main' is in 'main.c'.
Second, include the header 'carg_parser.h' near the top of 'main.c':
#include "carg_parser.h"
Third, define inside 'main' the option names and argument requirements. Ed
defines the following options:
const ap_Option options[] = {
{ 'E', "extended-regexp", ap_no },
{ 'G', "traditional", ap_no },
{ 'h', "help", ap_no },
{ 'l', "loose-exit-status", ap_no },
{ 'p', "prompt", ap_yes },
{ 'q', "quiet", ap_no },
{ 'q', "silent", ap_no },
{ 'r', "restricted", ap_no },
{ 's', "script", ap_no },
{ 'v', "verbose", ap_no },
{ 'V', "version", ap_no },
{ opt_cr, "strip-trailing-cr", ap_no },
{ opt_un, "unsafe-names", ap_no },
{ 0, 0, ap_no } };
Fourth, declare and initialize the parser:
Arg_parser parser;
if( !ap_init( &parser, argc, argv, options, 0 ) )
{ show_error( "Memory exhausted.", 0, false ); return 1; }
if( ap_error( &parser ) ) /* bad option */
{ show_error( ap_error( &parser ), 0, true ); return 1; }
Fifth, perform the actions corresponding to each option parsed. Ed performs
the following actions:
int argind = 0;
for( ; argind < ap_arguments( &parser ); ++argind )
{
const int code = ap_code( &parser, argind );
if( !code ) break; /* no more options */
const char * const arg = ap_argument( &parser, argind );
switch( code )
{
case 'E': extended_regexp_ = true; break;
case 'G': traditional_ = true; break; /* backward compatibility */
case 'h': show_help(); return 0;
case 'l': loose = true; break;
case 'p': if( set_prompt( arg ) ) break; else return 1;
case 'q': quiet = true; break;
case 'r': restricted_ = true; break;
case 's': scripted_ = true; break;
case 'v': set_verbose(); break;
case 'V': show_version(); return 0;
case opt_cr: strip_cr_ = true; break;
case opt_un: safe_names = false; break;
default: show_error( "internal error: uncaught option.", 0, false );
return 3;
}
} /* end process options */
Sixth, process any remaining non-option arguments (line number and file
name in the case of ed):
for( ; argind < ap_arguments( &parser ); ++argind )
{
const char * const arg = ap_argument( &parser, argind );
/* do something with 'arg' */
}

File: arg_parser.info, Node: Problems, Next: Concept index, Prev: C example, Up: Top
7 Reporting bugs
**************** ****************
There are probably bugs in Arg_parser. There are certainly errors and There are probably bugs in Arg_parser. There are certainly errors and
@ -280,12 +578,12 @@ omissions in this manual. If you report them, they will get fixed. If you
don't, no one will ever know about them and they will remain unfixed for don't, no one will ever know about them and they will remain unfixed for
all eternity, if not longer. all eternity, if not longer.
If you find a bug in Arg_parser, please send electronic mail to If you find a bug in Arg_parser, please send electronic mail to
<arg-parser-bug@nongnu.org>. Include the version number, which you can find <arg-parser-bug@nongnu.org>. Include the version number, which you can find
by running 'arg_parser --version'. by running 'arg_parser --version'.
 
File: arg_parser.info, Node: Concept index, Prev: Problems, Up: Top File: arg_parser.info, Node: Concept index, Next: Function index, Prev: Problems, Up: Top
Concept index Concept index
************* *************
@ -295,24 +593,68 @@ Concept index
* argument syntax: Argument syntax. (line 6) * argument syntax: Argument syntax. (line 6)
* bugs: Problems. (line 6) * bugs: Problems. (line 6)
* C example: C example. (line 6)
* C version: C version. (line 6) * C version: C version. (line 6)
* C++ example: C++ example. (line 6)
* C++ version: C++ version. (line 6)
* getting help: Problems. (line 6) * getting help: Problems. (line 6)
* initialization: Initialization. (line 6)
* introduction: Introduction. (line 6) * introduction: Introduction. (line 6)
* using Arg_parser: Using Arg_parser. (line 6)

File: arg_parser.info, Node: Function index, Prev: Concept index, Up: Top
Index of constants and variables
********************************
[index]
* Menu:
* code: C++ version. (line 24)
* has_arg: C++ version. (line 33)
* in_order: C++ version. (line 51)
* in_order_skip: C++ version. (line 64)
* in_order_stop: C++ version. (line 56)
* long_name: C++ version. (line 29)
* neg_non_opt: C++ version. (line 73)
Index of functions
******************
[index]
* Menu:
* ap_argument: C version. (line 72)
* ap_arguments: C version. (line 53)
* ap_argv_index: C version. (line 41)
* ap_code: C version. (line 57)
* ap_error: C version. (line 35)
* ap_free: C version. (line 32)
* ap_init: C version. (line 26)
* ap_parsed_name: C version. (line 65)
* Arg_parser: C++ version. (line 81)
* argument: C++ version. (line 129)
* arguments: C++ version. (line 112)
* argv_index: C++ version. (line 100)
* code: C++ version. (line 116)
* error: C++ version. (line 94)
* parsed_name: C++ version. (line 123)
 
Tag Table: Tag Table:
Node: Top224 Node: Top224
Node: Introduction951 Node: Introduction1048
Node: Argument syntax2085 Node: Argument syntax3879
Node: Initialization4504 Node: C++ version6484
Ref: struct Option5034 Ref: struct Option7131
Node: Using Arg_parser6979 Ref: enum Flags8551
Node: C version8461 Node: C version13221
Node: Problems11640 Node: C++ example17007
Node: Concept index12193 Node: C example21870
Node: Problems24803
Node: Concept index25353
Node: Function index26117
 
End Tag Table End Tag Table

View file

@ -6,19 +6,19 @@
@finalout @finalout
@c %**end of header @c %**end of header
@set UPDATED 5 February 2022 @set UPDATED 10 March 2026
@set VERSION 1.17 @set VERSION 1.21
@dircategory Libraries @dircategory Libraries
@direntry @direntry
* Arg_parser: (arg_parser). A POSIX/GNU command line argument parser * Arg_parser: (arg_parser). A POSIX/GNU command-line argument parser
@end direntry @end direntry
@ifnothtml @ifnothtml
@titlepage @titlepage
@title Arg_parser @title Arg_parser
@subtitle A POSIX/GNU command line argument parser @subtitle A POSIX/GNU command-line argument parser
@subtitle for Arg_parser version @value{VERSION}, @value{UPDATED} @subtitle for Arg_parser version @value{VERSION}, @value{UPDATED}
@author by Antonio Diaz Diaz @author by Antonio Diaz Diaz
@ -38,15 +38,17 @@ This manual is for Arg_parser (version @value{VERSION}, @value{UPDATED}).
@menu @menu
* Introduction:: Purpose and features of Arg_parser * Introduction:: Purpose and features of Arg_parser
* Argument syntax:: By convention, options start with a hyphen * Argument syntax:: By convention, options start with a hyphen
* Initialization:: Parsing arguments and reporting errors * C++ version:: Using the C++ version of Arg_parser
* Using Arg_parser:: Reading the options and arguments parsed
* C version:: Using the C version of Arg_parser * C version:: Using the C version of Arg_parser
* C++ example:: Tutorial for the C++ version
* C example:: Tutorial for the C version
* Problems:: Reporting bugs * Problems:: Reporting bugs
* Concept index:: Index of concepts * Concept index:: Index of concepts
* Function index:: Index of functions, constants, and variables
@end menu @end menu
@sp 1 @sp 1
Copyright @copyright{} 2006-2022 Antonio Diaz Diaz. Copyright @copyright{} 2006-2026 Antonio Diaz Diaz.
This manual is free documentation: you have unlimited permission to copy, This manual is free documentation: you have unlimited permission to copy,
distribute, and modify it. distribute, and modify it.
@ -59,17 +61,18 @@ distribute, and modify it.
@uref{http://www.nongnu.org/arg-parser/arg_parser.html,,Arg_parser} @uref{http://www.nongnu.org/arg-parser/arg_parser.html,,Arg_parser}
is an argument parser that follows POSIX and GNU conventions for is an argument parser that follows POSIX and GNU conventions for
command line arguments. There exist C++ and C versions of Arg_parser. The command-line arguments. There exist C++ and C versions of Arg_parser. The
C++ version is implemented as a C++ class, while the C version is C++ version is implemented as a C++ class, while the C version is
implemented as a single struct plus associated functions. Both are simpler, implemented as a single struct plus associated functions. Both are simpler,
easier to use, and safer than @samp{getopt_long}. easier to use, and safer than @samp{getopt_long}.
For maximum stability, Arg_parser is self-contained. It extracts all the For maximum stability, Arg_parser is self-contained. It extracts all the
information it needs from its arguments to avoid refering to them later. information it needs from its arguments to avoid referring to them later.
This avoids index-out-of-bounds errors. This avoids index-out-of-bounds errors and allows the parser object to be
passed as argument to other functions for further analysis.
Arg_parser does not modify its arguments, nor uses any global variables. So Arg_parser does not modify its arguments (argc, argv), nor uses any global
you may create more than one parser in your program if you need or want to. variables.
The C++ version of Arg_parser can also parse options from configuration The C++ version of Arg_parser can also parse options from configuration
files. files.
@ -83,132 +86,226 @@ including
@uref{http://www.nongnu.org/lzip/lzip.html,,lzip}, @uref{http://www.nongnu.org/lzip/lzip.html,,lzip},
@uref{http://www.gnu.org/software/ocrad/ocrad.html,,GNU ocrad}, @uref{http://www.gnu.org/software/ocrad/ocrad.html,,GNU ocrad},
@uref{http://www.nongnu.org/lzip/tarlz.html,,tarlz}, and @uref{http://www.nongnu.org/lzip/tarlz.html,,tarlz}, and
@uref{http://www.nongnu.org/zutils/zutils.html,,zutils}, with @uref{http://www.nongnu.org/zutils/zutils.html,,zutils}, with excellent
excellent results. results.
@section Differences with @samp{getopt} and @samp{getopt_long}.
@samp{getopt} parses the options one by one, which requires internal state.
Part of this state is stored in several global variables, and part of it is
hidden. For example, the POSIX specification of @samp{getopt} states that:
@quotation
When an element of argv[] contains multiple option characters, it is
unspecified how getopt() determines which options have already been
processed.
@end quotation
Parsing the options one by one is also a lot of work for the application,
which must check for errors (invalid option, missing option argument, etc)
for each option. @samp{getopt} is the 1970s way of parsing command-line
arguments.
@samp{getopt_long} is a not-very-well-designed extension on top of
@samp{getopt}. In addition to parsing the options one by one as
@samp{getopt} does, @samp{getopt_long} accepts configuration data from two
overlapping sources, which makes it easy for errors in the configuration to
remain undetected. For example, a certain long option can be declared to be
equivalent to a given short option, but both forms of the option may contain
contradictory requirements for the option's argument.
OTOH, Arg_parser parses the whole command line at once and fills a struct
with the results, like @samp{gmtime_r} and @samp{stat} do. This way, errors
are checked once, there is no need to permute the elements of argv, and the
struct returned can be used undisturbed by later changes in argv.
Finally, using Arg_parser instead of @samp{getopt_long} reduces the size of
your source code because, as @samp{getopt_long} is not available on all
systems, a portable program using it needs to ship its (larger) source
anyway.
@node Argument syntax @node Argument syntax
@chapter Syntax of command line arguments @chapter Syntax of command-line arguments
@cindex argument syntax @cindex argument syntax
POSIX recommends these conventions for command line arguments. POSIX recommends these conventions for command-line arguments.
Arg_parser makes it easy to implement them. Arg_parser makes it easy to implement them.
@itemize @bullet @itemize @bullet
@item A command line argument is an option if it begins with a hyphen @item A command-line argument is an option if it begins with a hyphen
delimiter (@samp{-}). (@samp{-}).
@item Multiple options may follow a hyphen delimiter in a single token
if the options don't take arguments. Thus, @samp{-abc} is equivalent to
@w{@samp{-a -b -c}}.
@item Option names are single alphanumeric characters. @item Option names are single alphanumeric characters.
@item Certain options require an argument. @item Certain options require an argument.
@item An option and its argument may or may not appear as separate @item An option and its argument may or may not appear as separate tokens.
tokens. (In other words, the whitespace separating them is optional). (In other words, the whitespace separating them is optional, unless the
Thus, @w{@samp{-o foo}} and @samp{-ofoo} are equivalent. argument is the empty string).
Thus, @w{@option{-o foo}} and @option{-ofoo} are equivalent.
@item One or more options without arguments, followed by at most one option
that takes an argument, may follow a hyphen in a single token.
Thus, @option{-abc} is equivalent to @w{@option{-a -b -c}}.
@item Options typically precede other non-option arguments. @item Options typically precede other non-option arguments.
Arg_parser normally makes it appear as if all the option arguments were Arg_parser normally makes it appear as if all the options were specified
specified before all the non-option arguments for the purposes of parsing, before all the non-option arguments for the purposes of parsing, even if the
even if the user of your program intermixed option and non-option arguments. user of your program intermixed options and non-option arguments. If you
If you want the arguments in the exact order the user typed them, call want the arguments in the exact order the user typed them, call
@samp{Arg_parser} with @w{@var{in_order} = true}. @samp{Arg_parser} with @w{@var{flags} = @samp{in_order}}.
@item The argument @samp{--} terminates all options; any following arguments @item The argument @samp{--} terminates all options; any following arguments
are treated as non-option arguments, even if they begin with a hyphen. are treated as non-option arguments, even if they begin with a hyphen.
@item A token consisting of a single hyphen character is interpreted as @item A token consisting of a single hyphen character is interpreted as an
an ordinary non-option argument. By convention, it is used to specify ordinary non-option argument. By convention, it is used to specify standard
input from or output to the standard input and output streams. input, standard output, or a file named @samp{-}.
@item Options may be supplied in any order, or appear multiple times. @item Options may be supplied in any order, or appear multiple times.
The interpretation is left up to the particular application program. The interpretation is left up to the particular application program.
@end itemize
@item GNU adds @dfn{long options} to these conventions. Long options GNU adds @dfn{long options} to these conventions:
consist of @samp{--} followed by a name made of alphanumeric characters
and hyphens. Option names are typically one to three words long, with @itemize @bullet
hyphens to separate words. Users can abbreviate the option names as long @item A long option consists of two hyphens (@samp{--}) followed by a name
as the abbreviations are unique. made of alphanumeric characters and hyphens. Option names are typically one
to three words long, with hyphens to separate words. Abbreviations can be
used for the long option names as long as the abbreviations are unique.
@item A long option and its argument may or may not appear as separate @item A long option and its argument may or may not appear as separate
tokens. In the latter case they must be separated by an equal sign @samp{=}. tokens. In the latter case they must be separated by an equal sign @samp{=}.
Thus, @w{@samp{--foo bar}} and @samp{--foo=bar} are equivalent. Thus, @w{@option{--foo bar}} and @option{--foo=bar} are equivalent.
@item The syntax for optional option arguments is
@samp{-<short_option><argument>} (without whitespace), or
@samp{--<long_option>=<argument>}.
@end itemize @end itemize
The syntax of options with an optional argument is
@option{-<short_option><argument>} (without whitespace), or
@option{--<long_option>=<argument>}.
@node Initialization The syntax of options with an empty argument is @option{-<short_option> ""},
@chapter Parsing arguments and reporting errors @option{--<long_option> ""}, or @option{--<long_option>=""}.
@cindex initialization
To use Arg_parser in your own programs first copy the files
@samp{arg_parser.h} and @samp{arg_parser.cc} in your source tree. See the
file @samp{main.cc} for an example of use.
The class @samp{Arg_parser} has two constructors; one to parse command line @node C++ version
@chapter Using the C++ version of Arg_parser
@cindex C++ version
The C++ version of Arg_parser is provided in the files @file{arg_parser.h}
and @file{arg_parser.cc}. To learn how to use Arg_parser in your C++
programs, @pxref{C++ example}, and the file @file{main.cc} in the
@uref{http://download.savannah.nongnu.org/releases/arg-parser/,,source tarball}.
@section Parsing arguments and reporting errors
The class @samp{Arg_parser} has two constructors; one to parse command-line
arguments from @var{argv}, and the other to parse a single token from a arguments from @var{argv}, and the other to parse a single token from a
configuration file or other source. configuration file or other source.
@anchor{struct Option} @anchor{struct Option}
@deffn {Data Type} struct Option @deftp {Data Type} struct Option
This structure describes a single option for the sake of This structure describes a single option for the sake of @samp{Arg_parser}.
@samp{Arg_parser}. The argument @var{options} must be an array of these The argument @var{options} must be an array of these structures, one for
structures, one for each option. Terminate the array with an element each option. Terminate the array with an element containing a code which is
containing a code which is zero. zero. @samp{struct Option} has the following members:
@samp{struct Option} has the following members: @deftypevr Member @code{int} code
@table @code
@item int code
This member is the code that identifies the option, normally the This member is the code that identifies the option, normally the
short-option character. Must be different from 0. A code value outside short-option character. Must be different from 0. A code value outside the
the unsigned char range means a long-only option. unsigned char range means a long-only option.
@end deftypevr
@item const char * long_name @deftypevr Member @code{const char *} long_name
This member is the long option name. It is a zero-terminated string. A This member is the long option name. It is a zero-terminated string. A null
null or empty long_name means a short-only option. or empty long_name means a short-only option.
@end deftypevr
@item enum Has_arg has_arg @deftypevr Member @code{enum Has_arg} has_arg
This member says whether the option takes an argument. It has three This member says whether the option takes an argument. It has four valid
valid values: @samp{no}, @samp{yes}, and @samp{maybe}. values: @samp{no}, @samp{yes}, @samp{maybe}, and @samp{yesme}, meaning
@end table respectively @samp{no argument}, @samp{non-empty argument required},
@end deffn @samp{optional argument}, and @samp{argument required, but it may be the
empty string}. @samp{yesme} is not recommended.
@defun Arg_parser ( const int @var{argc}, const char * const @var{argv}[], const Option @var{options}[], const bool @var{in_order} = false ) If an empty argument is passed to an option specified as @samp{yesme}, it
must be in a separate command-line argument or specified as a long option
with the empty string after the @samp{=} character. It can't be specified as
the empty string after the short option.
@end deftypevr
@end deftp
@anchor{enum Flags}
@deftp {Data Type} enum Flags
The argument @var{flags} is a bit mask. You can bitwise-OR the following
constants and assign them to @var{flags} to modify the way in which the
arguments are parsed. @w{@var{flags} = 0} chooses the default behavior
(reorder the options to put them before the non-option arguments).
@deftypevr Constant @code{Flags} in_order
Store the arguments in the exact order the user typed them, without
reordering the options to put them before the non-option arguments.
@end deftypevr
@deftypevr Constant @code{Flags} in_order_stop
Stop option processing when finding the first non-option argument as if the
argument @samp{--} had been found before it. Store the first non-option
argument and all the arguments following it as non-option arguments, even if
they begin with a hyphen. This is similar to the POSIX function @samp{getopt},
which stops option processing after finding the first non-option argument.
@end deftypevr
@deftypevr Constant @code{Flags} in_order_skip
Parse only the heading options. Skip the first non-option argument and all
the arguments following it; do not parse nor store them. The function
@samp{argv_index} returns the index in @var{argv} of the first argument
skipped (the first non-option argument), or @var{argc} if no arguments were
skipped. This mode is useful for parsing the command line of programs that
invoke other programs, like @command{timeout}, @command{xargs}, or
@command{valgrind}.
@end deftypevr
@deftypevr Constant @code{Flags} neg_non_opt
Parse the negative numbers, including @samp{-inf}, @samp{-Inf}, and
@samp{-INF}, as non-option arguments without reordering them. Numbers start
with a digit or a period and a digit. This mode is useful for parsing the
command line of tools like @command{seq} which take negative numbers as
arguments.
@end deftypevr
@end deftp
@defun Arg_parser ( const int @var{argc}, const char * const @var{argv}[], const Option @var{options}[], const int @var{flags} = 0 )
Constructor. Reads the arguments in @var{argv} and parses all options, Constructor. Reads the arguments in @var{argv} and parses all options,
option arguments, and non-option arguments contained in them. In case of option arguments, and non-option arguments contained in them. In case of
error, @samp{error().size()} will be non-zero. error, @samp{error().size()} returns nonzero.
@end defun @end defun
@defun Arg_parser ( const char * const @var{opt}, const char * const @var{arg}, const Option @var{options}[] ) @defun Arg_parser ( const char * const @var{opt}, const char * const @var{arg}, const Option @var{options}[] )
Restricted constructor. Parses a single token (plus an optional second Restricted constructor. Parses a single token (plus an optional second token
token in case an argument is needed for an option parsed). Can be used to in case an argument is needed for an option parsed). Can be used to parse
parse options from a configuration file one at a time. Be warned that a options from a configuration file one at a time. Be warned that a single
single token may produce an undefined number of short options. In case token may produce an undefined number of short options. In case of error,
of error, @samp{error().size()} will be non-zero. @samp{error().size()} returns nonzero.
@end defun @end defun
@deftypefun {const std::string &} error () const @deftypefun {const std::string &} error () const
Use this funtion to verify that the arguments have been correctly parsed Use this funtion to check that the arguments have been correctly parsed by
by the constructor. If there was an error parsing the arguments, the constructor. If there was an error parsing the arguments, @samp{error}
@samp{error} returns a non-empty error message explaining the cause. returns an error message explaining the cause, else it returns an empty
string.
@end deftypefun @end deftypefun
@deftypefun {int} argv_index () const
Return the index in @var{argv} of the first argument skipped (the first
non-option argument) when @var{flags} was set to @samp{in_order_skip}. If no
arguments were skipped, @var{argc} is returned instead.
@end deftypefun
@node Using Arg_parser @section Reading the options and arguments parsed
@chapter Using the class @samp{Arg_parser}
@cindex using Arg_parser
After a successful call to the constructor, which must be verified by After a successful call to the constructor, which must be checked by calling
calling @samp{error}, the options and arguments parsed can be accessed @samp{error}, the options and arguments parsed can be accessed by means of
by means of the following functions: the following functions:
@deftypefun int arguments () const @deftypefun int arguments () const
This function returns the number of options and non-option arguments parsed. This function returns the number of options and non-option arguments parsed.
@ -217,10 +314,10 @@ This number is usually different from argc.
@deftypefun int code ( const int @var{i} ) const @deftypefun int code ( const int @var{i} ) const
This function returns the code of the option at position @var{i}. Valid This function returns the code of the option at position @var{i}. Valid
values for @var{i} range from 0 to @w{@samp{arguments() - 1}}. If the values for @var{i} range from 0 to @w{@samp{arguments() - 1}}. If the code
code returned is non-zero, @samp{argument(@var{i})} is the option's returned is nonzero, @samp{argument(@var{i})} is the option's argument (or
argument (or is empty if the option does not have an argument). If the is empty if the option does not have an argument). If the code returned is
code returned is zero, @samp{argument(@var{i})} is a non-option argument. zero, @samp{argument(@var{i})} is a non-option argument.
@end deftypefun @end deftypefun
@deftypefun {const std::string &} parsed_name ( const int @var{i} ) const @deftypefun {const std::string &} parsed_name ( const int @var{i} ) const
@ -232,8 +329,9 @@ messages. For non-option arguments it returns the empty string.
@deftypefun {const std::string &} argument ( const int @var{i} ) const @deftypefun {const std::string &} argument ( const int @var{i} ) const
This function returns the argument at position @var{i}. It may be the This function returns the argument at position @var{i}. It may be the
argument of an option or a non-option argument, depending on the value argument of an option or a non-option argument, depending on the value
returned by @samp{code(@var{i})}. Valid values for @var{i} range from 0 returned by @samp{code(@var{i})}. Valid values for @var{i} range from 0 to
to @w{@samp{arguments() - 1}}. @w{@samp{arguments() - 1}}. If the argument does not exist, the empty string
is returned.
@end deftypefun @end deftypefun
@ -241,71 +339,303 @@ to @w{@samp{arguments() - 1}}.
@chapter Using the C version of Arg_parser @chapter Using the C version of Arg_parser
@cindex C version @cindex C version
To use the C version of Arg_parser in your own programs first copy the files The C version of Arg_parser is provided in the files @file{carg_parser.h}
@samp{carg_parser.h} and @samp{carg_parser.c} in your source tree. See the and @file{carg_parser.c}. To learn how to use Arg_parser in your C programs,
file @samp{cmain.c} for an example of use. @pxref{C example}, and the file @file{cmain.c} in the
@uref{http://download.savannah.nongnu.org/releases/arg-parser/,,source tarball}.
Then you need to declare a variable of type @samp{struct Arg_parser}, @section Parsing arguments and reporting errors
pass its address to @samp{ap_init} to initialize it, and verify that
@samp{ap_error} returns 0.
@samp{struct ap_Option} is identical to @samp{struct Option}, except You need to declare a variable of type @samp{Arg_parser}, pass its address
that @samp{Has_arg} becomes @samp{ap_Has_arg}, and the names of its to @samp{ap_init} to initialize it, and check that @samp{ap_error} returns 0.
three values are also prefixed with @samp{ap_}. @xref{struct Option},
for details about the members.
@deftypefun char ap_init ( struct Arg_parser * const @var{ap}, const int @var{argc}, const char * const @var{argv}[], const struct ap_Option @var{options}[], const char @var{in_order} ) @samp{struct ap_Option} is identical to @samp{struct Option}, except that
Reads the arguments in @var{argv} and parses all options, option @samp{Has_arg} becomes @samp{ap_Has_arg}, and the names of its four values
arguments, and non-option arguments contained in them. Returns 0 if there are also prefixed with @samp{ap_}. @xref{struct Option}, for details about
is not enough memory, else 1. In case of error, @samp{ap_error} will the members.
return a non-null pointer.
@samp{enum ap_Flags} is identical to @samp{enum Flags}, except that its
constants are also prefixed with @samp{ap_}. @xref{enum Flags}, for a
description of the constants.
@deftypefun char ap_init ( Arg_parser * const @var{ap}, const int @var{argc}, const char * const @var{argv}[], const ap_Option @var{options}[], const int @var{flags} )
Reads the arguments in @var{argv} and parses all options, option arguments,
and non-option arguments contained in them. Returns 0 if there is not enough
memory, else 1 (even if errors are found). In case of error, @samp{ap_error}
returns a non-null pointer.
@end deftypefun @end deftypefun
@deftypefun void ap_free ( struct Arg_parser * const @var{ap} ) @deftypefun void ap_free ( Arg_parser * const @var{ap} )
Frees all dynamically allocated data structures. Frees all dynamically allocated data structures.
@end deftypefun @end deftypefun
@deftypefun {const char *} ap_error ( const struct Arg_parser * const @var{ap} ) @deftypefun {const char *} ap_error ( const Arg_parser * const @var{ap} )
Use this funtion to verify that the arguments have been correctly parsed Use this funtion to check that the arguments have been correctly parsed by
by @samp{ap_init}. If there was an error parsing the arguments, @samp{ap_init}. If there was an error parsing the arguments, @samp{ap_error}
@samp{ap_error} returns a pointer to an error message explaining the returns a pointer to an error message explaining the cause, else it returns
cause, else it returns a null pointer. a null pointer.
@end deftypefun @end deftypefun
After a successful call to @samp{ap_init}, which must be verified by @deftypefun {int} ap_argv_index ( const Arg_parser * const @var{ap} )
calling @samp{ap_error}, the options and arguments parsed can be accessed Return the index in @var{argv} of the first argument skipped (the first
by means of the following functions: non-option argument) when @var{flags} was set to @samp{ap_in_order_skip}. If
no arguments were skipped, @var{argc} is returned instead.
@end deftypefun
@deftypefun int ap_arguments ( const struct Arg_parser * const @var{ap} ) @section Reading the options and arguments parsed
After a successful call to @samp{ap_init}, which must be checked by calling
@samp{ap_error}, the options and arguments parsed can be accessed by means
of the following functions:
@deftypefun int ap_arguments ( const Arg_parser * const @var{ap} )
This function returns the number of options and non-option arguments parsed. This function returns the number of options and non-option arguments parsed.
This number is usually different from argc. This number is usually different from argc.
@end deftypefun @end deftypefun
@deftypefun int ap_code ( const struct Arg_parser * const @var{ap}, const int @var{i} ) @deftypefun int ap_code ( const Arg_parser * const @var{ap}, const int @var{i} )
This function returns the code of the option at position @var{i}. Valid This function returns the code of the option at position @var{i}. Valid
values for @var{i} range from 0 to @w{@samp{ap_arguments() - 1}}. If the values for @var{i} range from 0 to @w{@samp{ap_arguments() - 1}}. If the
code returned is non-zero, @samp{ap_argument(@var{i})} is the option's code returned is nonzero, @samp{ap_argument(@var{i})} is the option's
argument (or is empty if the option does not have an argument). If the argument (or is empty if the option does not have an argument). If the code
code returned is zero, @samp{ap_argument(@var{i})} is a non-option argument. returned is zero, @samp{ap_argument(@var{i})} is a non-option argument.
@end deftypefun @end deftypefun
@deftypefun {const char *} ap_parsed_name ( const struct Arg_parser * const @var{ap}, const int @var{i} ) @deftypefun {const char *} ap_parsed_name ( const Arg_parser * const @var{ap}, const int @var{i} )
This function returns the full name of the option parsed (short or long) at This function returns the full name of the option parsed (short or long) at
position @var{i}. It may be useful to produce more accurate diagnostic position @var{i}. It may be useful to produce more accurate diagnostic
messages. For non-option arguments it returns the empty string. messages. For non-option arguments it returns the empty string.
@end deftypefun @end deftypefun
@deftypefun {const char *} ap_argument ( const struct Arg_parser * const @var{ap}, const int @var{i} ) @deftypefun {const char *} ap_argument ( const Arg_parser * const @var{ap}, const int @var{i} )
This function returns the argument at position @var{i}. It may be the This function returns the argument at position @var{i}. It may be the
argument of an option or a non-option argument, depending on the value argument of an option or a non-option argument, depending on the value
returned by @samp{ap_code(@var{i})}. Valid values for @var{i} range from 0 returned by @samp{ap_code(@var{i})}. Valid values for @var{i} range from 0
to @w{@samp{ap_arguments() - 1}}. to @w{@samp{ap_arguments() - 1}}. If the argument does not exist, the empty
string is returned.
@end deftypefun @end deftypefun
When you are finished, you should free all dynamically allocated data When you are finished, you should free all dynamically allocated data
structures by calling @samp{ap_free}. structures by calling @samp{ap_free}.
@node C++ example
@chapter Tutorial for the C++ version
@cindex C++ example
This tutorial uses @uref{http://www.nongnu.org/lzip/lzip.html,,lzip} as an
example of how to use Arg_parser in a C++ program. You need to follow these
6 steps:
First copy the files @file{arg_parser.h} and @file{arg_parser.cc} in your
source tree, in the same directory as the file containing the function
@samp{main} of your program. In lzip, @samp{main} is in @file{main.cc}.
Second, include these header files near the top of @file{main.cc}:
@verbatim
#include <string>
#include <vector>
#include "arg_parser.h"
@end verbatim
Third, define inside @samp{main} the option names and argument requirements.
Lzip defines the following options:
@verbatim
const Arg_parser::Option options[] = {
{ '0', "fast", Arg_parser::no },
{ '1', 0, Arg_parser::no },
{ '2', 0, Arg_parser::no },
{ '3', 0, Arg_parser::no },
{ '4', 0, Arg_parser::no },
{ '5', 0, Arg_parser::no },
{ '6', 0, Arg_parser::no },
{ '7', 0, Arg_parser::no },
{ '8', 0, Arg_parser::no },
{ '9', "best", Arg_parser::no },
{ 'a', "trailing-error", Arg_parser::no },
{ 'b', "member-size", Arg_parser::yes },
{ 'c', "stdout", Arg_parser::no },
{ 'd', "decompress", Arg_parser::no },
{ 'f', "force", Arg_parser::no },
{ 'F', "recompress", Arg_parser::no },
{ 'h', "help", Arg_parser::no },
{ 'k', "keep", Arg_parser::no },
{ 'l', "list", Arg_parser::no },
{ 'm', "match-length", Arg_parser::yes },
{ 'n', "threads", Arg_parser::yes },
{ 'o', "output", Arg_parser::yes },
{ 'q', "quiet", Arg_parser::no },
{ 's', "dictionary-size", Arg_parser::yes },
{ 'S', "volume-size", Arg_parser::yes },
{ 't', "test", Arg_parser::no },
{ 'v', "verbose", Arg_parser::no },
{ 'V', "version", Arg_parser::no },
{ opt_lt, "loose-trailing", Arg_parser::no },
{ 0, 0, Arg_parser::no } };
@end verbatim
Fourth, declare and initialize the parser:
@verbatim
const Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option
{ show_error( parser.error().c_str(), 0, true ); return 1; }
@end verbatim
Fifth, perform the actions corresponding to each option parsed. Lzip
performs the following actions:
@verbatim
int argind = 0;
for( ; argind < parser.arguments(); ++argind )
{
const int code = parser.code( argind );
if( !code ) break; // no more options
const char * const pn = parser.parsed_name( argind ).c_str();
const std::string & sarg = parser.argument( argind );
const char * const arg = sarg.c_str();
switch( code )
{
case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9': zero = code == '0';
encoder_options = option_mapping[code-'0']; break;
case 'a': cl_opts.ignore_trailing = false; break;
case 'b': member_size = getnum(arg, pn, 100000, max_member_size); break;
case 'c': to_stdout = true; break;
case 'd': set_mode( program_mode, m_decompress ); break;
case 'f': force = true; break;
case 'F': recompress = true; break;
case 'h': show_help(); return 0;
case 'k': keep_input_files = true; break;
case 'l': set_mode( program_mode, m_list ); break;
case 'm': encoder_options.match_len_limit =
getnum( arg, pn, min_match_len_limit, max_match_len );
zero = false; break;
case 'n': break; // ignored
case 'o': if( sarg == "-" ) to_stdout = true;
else { default_output_filename = sarg; } break;
case 'q': verbosity = -1; break;
case 's': encoder_options.dictionary_size = get_dict_size( arg, pn );
zero = false; break;
case 'S': volume_size = getnum(arg, pn, 100000, max_volume_size); break;
case 't': set_mode( program_mode, m_test ); break;
case 'v': if( verbosity < 4 ) ++verbosity; break;
case 'V': show_version(); return 0;
case opt_lt: cl_opts.loose_trailing = true; break;
default: internal_error( "uncaught option." );
}
} // end process options
@end verbatim
Sixth, process any remaining non-option arguments (file names in the case of
lzip):
@verbatim
std::vector< std::string > filenames;
bool filenames_given = false;
for( ; argind < parser.arguments(); ++argind )
{
filenames.push_back( parser.argument( argind ) );
if( filenames.back() != "-" ) filenames_given = true;
}
if( filenames.empty() ) filenames.push_back("-");
// do something with 'filenames'
@end verbatim
@node C example
@chapter Tutorial for the C version
@cindex C example
This tutorial uses @uref{http://www.gnu.org/software/ed/ed.html,,GNU ed} as
an example of how to use Arg_parser in a C program. You need to follow these
6 steps:
First copy the files @file{carg_parser.h} and @file{carg_parser.c} in your
source tree, in the same directory as the file containing the function
@samp{main} of your program. In GNU ed, @samp{main} is in @file{main.c}.
Second, include the header @file{carg_parser.h} near the top of @file{main.c}:
@verbatim
#include "carg_parser.h"
@end verbatim
Third, define inside @samp{main} the option names and argument requirements.
Ed defines the following options:
@verbatim
const ap_Option options[] = {
{ 'E', "extended-regexp", ap_no },
{ 'G', "traditional", ap_no },
{ 'h', "help", ap_no },
{ 'l', "loose-exit-status", ap_no },
{ 'p', "prompt", ap_yes },
{ 'q', "quiet", ap_no },
{ 'q', "silent", ap_no },
{ 'r', "restricted", ap_no },
{ 's', "script", ap_no },
{ 'v', "verbose", ap_no },
{ 'V', "version", ap_no },
{ opt_cr, "strip-trailing-cr", ap_no },
{ opt_un, "unsafe-names", ap_no },
{ 0, 0, ap_no } };
@end verbatim
Fourth, declare and initialize the parser:
@verbatim
Arg_parser parser;
if( !ap_init( &parser, argc, argv, options, 0 ) )
{ show_error( "Memory exhausted.", 0, false ); return 1; }
if( ap_error( &parser ) ) /* bad option */
{ show_error( ap_error( &parser ), 0, true ); return 1; }
@end verbatim
Fifth, perform the actions corresponding to each option parsed. Ed performs
the following actions:
@verbatim
int argind = 0;
for( ; argind < ap_arguments( &parser ); ++argind )
{
const int code = ap_code( &parser, argind );
if( !code ) break; /* no more options */
const char * const arg = ap_argument( &parser, argind );
switch( code )
{
case 'E': extended_regexp_ = true; break;
case 'G': traditional_ = true; break; /* backward compatibility */
case 'h': show_help(); return 0;
case 'l': loose = true; break;
case 'p': if( set_prompt( arg ) ) break; else return 1;
case 'q': quiet = true; break;
case 'r': restricted_ = true; break;
case 's': scripted_ = true; break;
case 'v': set_verbose(); break;
case 'V': show_version(); return 0;
case opt_cr: strip_cr_ = true; break;
case opt_un: safe_names = false; break;
default: show_error( "internal error: uncaught option.", 0, false );
return 3;
}
} /* end process options */
@end verbatim
Sixth, process any remaining non-option arguments (line number and file
name in the case of ed):
@verbatim
for( ; argind < ap_arguments( &parser ); ++argind )
{
const char * const arg = ap_argument( &parser, argind );
/* do something with 'arg' */
}
@end verbatim
@node Problems @node Problems
@chapter Reporting bugs @chapter Reporting bugs
@cindex bugs @cindex bugs
@ -326,4 +656,14 @@ you can find by running @w{@samp{arg_parser --version}}.
@printindex cp @printindex cp
@node Function index
@unnumbered Index of constants and variables
@printindex vr
@sp 1
@unnumbered Index of functions
@printindex fn
@bye @bye

View file

@ -1,32 +1,34 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.16. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2.
.TH CARG_PARSER "1" "February 2022" "carg_parser 1.17" "User Commands" .TH CARG_PARSER "1" "March 2026" "carg_parser 1.21" "User Commands"
.SH NAME .SH NAME
carg_parser \- test program for Arg_parser carg_parser \- test program for Arg_parser
.SH SYNOPSIS .SH SYNOPSIS
.B carg_parser .B carg_parser
[\fI\,options\/\fR] [\fI\,options\/\fR]
.SH DESCRIPTION .SH DESCRIPTION
Arg_parser \- POSIX/GNU command line argument parser. (C version) Arg_parser \- POSIX/GNU command\-line argument parser. (C version)
.PP .PP
Arg_parser is an argument parser that follows POSIX and GNU conventions for Arg_parser is an argument parser that follows POSIX and GNU conventions for
command line arguments. There exist C++ and C versions of Arg_parser. The command\-line arguments. There exist C++ and C versions of Arg_parser. The
C++ version is implemented as a C++ class, while the C version is C++ version is implemented as a C++ class, while the C version is
implemented as a single struct plus associated functions. Both are simpler, implemented as a single struct plus associated functions. Both are simpler,
easier to use, and safer than 'getopt_long'. easier to use, and safer than 'getopt_long'.
.PP .PP
For maximum stability, Arg_parser is self\-contained. It extracts all the For maximum stability, Arg_parser is self\-contained. It extracts all the
information it needs from its arguments to avoid refering to them later. information it needs from its arguments to avoid referring to them later.
This avoids index\-out\-of\-bounds errors. This avoids index\-out\-of\-bounds errors and allows the parser object to be
passed as argument to other functions for further analysis.
.PP .PP
Arg_parser does not modify its arguments, nor uses any global variables. So Arg_parser does not modify its arguments (argc, argv), nor uses any global
you may create more than one parser in your program if you need or want to. variables.
.PP .PP
The C++ version of Arg_parser can also parse options from configuration The C++ version of Arg_parser can also parse options from configuration
files. files.
.PP .PP
To use Arg_parser in your own programs simply copy the files 'carg_parser.h' The C version of Arg_parser is provided in the files 'carg_parser.h' and
and 'carg_parser.c' in your source tree. See the file 'cmain.c' for an \&'carg_parser.c'. To learn how to use Arg_parser in your C programs, see the
example of use. C example in the manual and the example file 'cmain.c' in the source
tarball.
.SH OPTIONS .SH OPTIONS
.TP .TP
\fB\-h\fR, \fB\-\-help\fR \fB\-h\fR, \fB\-\-help\fR
@ -44,10 +46,13 @@ example of option with required argument
\fB\-c\fR, \fB\-\-casual[=\fR<arg>] \fB\-c\fR, \fB\-\-casual[=\fR<arg>]
example of option with optional argument example of option with optional argument
.TP .TP
\fB\-e\fR, \fB\-\-empty=\fR<arg>
example of option with maybe empty argument
.TP
\fB\-o\fR <arg> \fB\-o\fR <arg>
example of short only option example of short only option
.TP .TP
\fB\-\-orphan\fR \fB\-\-long\-only\fR
example of long only option example of long only option
.TP .TP
\fB\-q\fR, \fB\-\-quiet\fR \fB\-q\fR, \fB\-\-quiet\fR
@ -63,7 +68,7 @@ Report bugs to arg\-parser\-bug@nongnu.org
.br .br
Arg_parser home page: http://www.nongnu.org/arg\-parser/arg_parser.html Arg_parser home page: http://www.nongnu.org/arg\-parser/arg_parser.html
.SH COPYRIGHT .SH COPYRIGHT
Copyright \(co 2022 Antonio Diaz Diaz. Copyright \(co 2026 Antonio Diaz Diaz.
License 2\-clause BSD. License 2\-clause BSD.
.br .br
This is free software: you are free to change and redistribute it. This is free software: you are free to change and redistribute it.

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C++ version) /* Arg_parser - POSIX/GNU command-line argument parser. (C++ version)
Copyright (C) 2006-2022 Antonio Diaz Diaz. Copyright (C) 2006-2026 Antonio Diaz Diaz.
This program is free software: you have unlimited permission to This program is free software: you have unlimited permission to
copy, distribute, and modify it. copy, distribute, and modify it.
@ -10,9 +10,9 @@
*/ */
/* /*
Exit status: 0 for a normal exit, 1 for environmental problems Exit status: 0 for a normal exit, 1 for environmental problems
(file not found, invalid flags, I/O errors, etc), 2 to indicate a (file not found, invalid command-line options, I/O errors, etc), 2 to
corrupt or invalid input file, 3 for an internal consistency error indicate a corrupt or invalid input file, 3 for an internal consistency
(e.g., bug) which caused arg_parser to panic. error (e.g., bug) which caused arg_parser to panic.
*/ */
#include <climits> #include <climits>
@ -24,48 +24,54 @@
#include "arg_parser.h" #include "arg_parser.h"
namespace { namespace {
const char * const program_name = "arg_parser"; const char * const program_name = "arg_parser";
const char * const program_year = "2022"; const char * const program_year = "2026";
const char * invocation_name = program_name; // default value const char * invocation_name = program_name; // default value
unsigned verbosity = 0;
enum { opt_lo = 256 };
void show_help( const bool verbose ) void show_help()
{ {
std::printf( "Arg_parser - POSIX/GNU command line argument parser. (C++ version)\n" std::fputs(
"\nArg_parser is an argument parser that follows POSIX and GNU conventions for\n" "Arg_parser - POSIX/GNU command-line argument parser. (C++ version)\n"
"command line arguments. There exist C++ and C versions of Arg_parser. The\n" "\nArg_parser is an argument parser that follows POSIX and GNU conventions for\n"
"C++ version is implemented as a C++ class, while the C version is\n" "command-line arguments. There exist C++ and C versions of Arg_parser. The\n"
"implemented as a single struct plus associated functions. Both are simpler,\n" "C++ version is implemented as a C++ class, while the C version is\n"
"easier to use, and safer than 'getopt_long'.\n" "implemented as a single struct plus associated functions. Both are simpler,\n"
"\nFor maximum stability, Arg_parser is self-contained. It extracts all the\n" "easier to use, and safer than 'getopt_long'.\n"
"information it needs from its arguments to avoid refering to them later.\n" "\nFor maximum stability, Arg_parser is self-contained. It extracts all the\n"
"This avoids index-out-of-bounds errors.\n" "information it needs from its arguments to avoid referring to them later.\n"
"\nArg_parser does not modify its arguments, nor uses any global variables. So\n" "This avoids index-out-of-bounds errors and allows the parser object to be\n"
"you may create more than one parser in your program if you need or want to.\n" "passed as argument to other functions for further analysis.\n"
"\nThe C++ version of Arg_parser can also parse options from configuration\n" "\nArg_parser does not modify its arguments (argc, argv), nor uses any global\n"
"files.\n" "variables.\n"
"\nTo use Arg_parser in your own programs simply copy the files 'arg_parser.h'\n" "\nThe C++ version of Arg_parser can also parse options from configuration\n"
"and 'arg_parser.cc' in your source tree. See the file 'main.cc' for an\n" "files.\n"
"example of use.\n" "\nThe C++ version of Arg_parser is provided in the files 'arg_parser.h' and\n"
"\nUsage: %s [options]\n", invocation_name ); "'arg_parser.cc'. To learn how to use Arg_parser in your C++ programs, see\n"
std::printf( "\nOptions:\n" "the C++ example in the manual and the example file 'main.cc' in the source\n"
" -h, --help display this help and exit\n" "tarball.\n", stdout );
" -V, --version output version information and exit\n" std::printf( "\nUsage: %s [options]\n", invocation_name );
" -a, --append example of option with no argument\n" std::fputs( "\nOptions:\n"
" -b, --block=<arg> example of option with required argument\n" " -h, --help display this help and exit\n"
" -c, --casual[=<arg>] example of option with optional argument\n" " -V, --version output version information and exit\n"
" -o <arg> example of short only option\n" " -a, --append example of option with no argument\n"
" --orphan example of long only option\n" " -b, --block=<arg> example of option with required argument\n"
" -q, --quiet quiet operation\n" " -c, --casual[=<arg>] example of option with optional argument\n"
" -u, --uncaught example of intentional bug\n" " -e, --empty=<arg> example of option with maybe empty argument\n"
" -v, --verbose verbose operation\n" ); " -o <arg> example of short only option\n"
if( verbose ) " --long-only example of long only option\n"
std::printf( " -H, --hidden example of hidden option (shown with -v -h)\n" ); " -q, --quiet quiet operation\n"
std::printf( "\nReport bugs to arg-parser-bug@nongnu.org\n" " -u, --uncaught example of intentional bug\n"
"Arg_parser home page: http://www.nongnu.org/arg-parser/arg_parser.html\n" ); " -v, --verbose verbose operation\n", stdout );
if( verbosity ) std::fputs(
" -H, --hidden example of hidden option (shown with -v -h)\n", stdout );
std::fputs(
"\nReport bugs to arg-parser-bug@nongnu.org\n"
"Arg_parser home page: http://www.nongnu.org/arg-parser/arg_parser.html\n", stdout );
} }
@ -73,9 +79,9 @@ void show_version()
{ {
std::printf( "%s %s\n", program_name, PROGVERSION ); std::printf( "%s %s\n", program_name, PROGVERSION );
std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year ); std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year );
std::printf( "License 2-clause BSD.\n" std::fputs( "License 2-clause BSD.\n"
"This is free software: you are free to change and redistribute it.\n" "This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n" ); "There is NO WARRANTY, to the extent permitted by law.\n", stdout );
} }
@ -98,74 +104,119 @@ void internal_error( const char * const msg )
std::exit( 3 ); std::exit( 3 );
} }
void print_opt( const char * const arg, const char * const option_name,
const int code )
{
std::printf( "option '%s'", option_name );
if( arg[0] || code == 'e' ) std::printf( " with argument '%s'", arg );
if( code == INT_MIN || code == INT_MAX ) std::printf( " (code 0x%X)", code );
std::fputc( '\n', stdout );
}
bool process_options( const Arg_parser & parser )
{
verbosity = 0;
for( int argind = 0; argind < parser.arguments(); ++argind )
{
const int code = parser.code( argind );
const char * const pn = parser.parsed_name( argind ).c_str();
const char * const arg = parser.argument( argind ).c_str();
switch( code )
{
case 0: std::printf( "non-option argument '%s'\n", arg ); break;
case '.': case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c':
case 'e': print_opt( arg, pn, code ); break;
case 'h': show_help(); return false;
case 'H': case 'i': case 'I':
case 'o': print_opt( arg, pn, code ); break;
case 'q': print_opt( arg, pn, code ); verbosity = 0; break;
// case 'u': break; // intentionally not caught
case 'v': print_opt( arg, pn, code ); ++verbosity; break;
case 'V': show_version(); return false;
case opt_lo:
case INT_MIN: case INT_MAX: print_opt( arg, pn, code ); break;
default: internal_error( "uncaught option." );
}
} // end process arguments
return true;
}
} // end namespace } // end namespace
int main( const int argc, const char * const argv[] ) int main( const int argc, const char * const argv[] )
{ {
bool verbose = false;
if( argc > 0 ) invocation_name = argv[0]; if( argc > 0 ) invocation_name = argv[0];
const Arg_parser::Option options[] = const Arg_parser::Option options[] =
{ {
{ 'a', "append", Arg_parser::no }, // code, long_name, has_arg (no/yes/maybe/yesme)
{ 'b', "block", Arg_parser::yes }, { '.', 0, Arg_parser::maybe },
{ 'c', "casual", Arg_parser::maybe }, { '0', 0, Arg_parser::maybe },
{ 'h', "help", Arg_parser::no }, { '1', 0, Arg_parser::maybe },
{ 'H', "hidden", Arg_parser::no }, { '2', 0, Arg_parser::maybe },
{ 'o', 0, Arg_parser::yes }, { '3', 0, Arg_parser::maybe },
{ 'q', "quiet", Arg_parser::no }, { '4', 0, Arg_parser::maybe },
{ 'u', "uncaught", Arg_parser::no }, { '5', 0, Arg_parser::maybe },
{ 'v', "verbose", Arg_parser::no }, { '6', 0, Arg_parser::maybe },
{ 'V', "version", Arg_parser::no }, { '7', 0, Arg_parser::maybe },
{ 256, "orphan", Arg_parser::no }, { '8', 0, Arg_parser::maybe },
{ INT_MIN, "int-min", Arg_parser::no }, { '9', 0, Arg_parser::maybe },
{ INT_MAX, "int-max", Arg_parser::no }, { 'a', "append", Arg_parser::no },
{ 0, 0, Arg_parser::no } }; { 'b', "block", Arg_parser::yes },
{ 'c', "casual", Arg_parser::maybe },
{ 'e', "empty", Arg_parser::yesme },
{ 'h', "help", Arg_parser::no },
{ 'H', "hidden", Arg_parser::no },
{ 'i', 0, Arg_parser::maybe },
{ 'I', 0, Arg_parser::maybe },
{ 'o', 0, Arg_parser::yes },
{ 'q', "quiet", Arg_parser::no },
{ 'u', "uncaught", Arg_parser::no },
{ 'v', "verbose", Arg_parser::no },
{ 'V', "version", Arg_parser::no },
{ opt_lo, "long-only", Arg_parser::no },
{ INT_MIN, "int-min", Arg_parser::no },
{ INT_MAX, "int-max", Arg_parser::no },
{ 0, 0, Arg_parser::no } };
{
const Arg_parser parser( argc, argv, options ); const Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option if( parser.error().size() ) // bad option
{ show_error( parser.error().c_str(), 0, true ); return 1; } { show_error( parser.error().c_str(), 0, true ); return 1; }
if( !process_options( parser ) ) return 0;
std::fputs( !parser.arguments() ?
"No arguments!\n" : "(options reordered)\n", stdout );
if( verbosity ) std::fputc( '\n', stdout ); else return 0;
}
{
const Arg_parser parser( argc, argv, options, Arg_parser::neg_non_opt );
if( parser.error().size() ) // bad option
{ show_error( parser.error().c_str(), 0, true ); return 1; }
process_options( parser );
std::fputs( "(options reordered"
" and negative numbers treated as non-options)\n", stdout );
if( verbosity == 1 ) return 0;
}
for( int argind = 0; argind < parser.arguments(); ++argind ) for( int flags = 1; flags < 16; ++flags )
{ {
const int code = parser.code( argind ); if( flags == Arg_parser::neg_non_opt ) continue; // done above
if( !code ) break; // no more options
switch( code )
{
case 'a': break; // example, do nothing
case 'b': break; // example, do nothing
case 'c': break; // example, do nothing
case 'h': show_help( verbose ); return 0;
case 'H': break; // example, do nothing
case 'o': break; // example, do nothing
case 'q': verbose = false; break;
// case 'u': break; // intentionally not caught
case 'v': verbose = true; break;
case 'V': show_version(); return 0;
case 256: break; // example, do nothing
case INT_MIN: break; // example, do nothing
case INT_MAX: break; // example, do nothing
default : internal_error( "uncaught option." );
}
} // end process options
for( int argind = 0; argind < parser.arguments(); ++argind )
{
const int code = parser.code( argind );
const char * const arg = parser.argument( argind ).c_str();
if( code ) // option
{
std::printf( "option '%s'", parser.parsed_name( argind ).c_str() );
if( arg[0] ) std::printf( " with argument '%s'", arg );
if( code == INT_MIN || code == INT_MAX ) std::printf( " (code 0x%X)", code );
}
else // non-option
std::printf( "non-option argument '%s'", arg );
std::fputc( '\n', stdout ); std::fputc( '\n', stdout );
const Arg_parser parser( argc, argv, options, flags );
if( parser.error().size() ) // bad option
{ show_error( parser.error().c_str(), 0, true ); return 1; }
process_options( parser );
for( int i = parser.argv_index(); i < argc; ++i )
std::printf( "skipped non-option argument '%s'\n", argv[i] );
std::printf( "(0%s%s%s%s)\n",
(flags & parser.in_order) ? " | in_order" : "",
(flags & parser.in_order_stop) ? " | in_order_stop" : "",
(flags & parser.in_order_skip) ? " | in_order_skip" : "",
(flags & parser.neg_non_opt) ? " | neg_non_opt" : "" );
} }
if( !parser.arguments() ) std::fputs( "Hello, world!\n", stdout );
return 0; return 0;
} }

View file

@ -1,6 +1,6 @@
#! /bin/sh #! /bin/sh
# check script for Arg_parser - POSIX/GNU command line argument parser. # check script for Arg_parser - POSIX/GNU command-line argument parser.
# Copyright (C) 2011-2022 Antonio Diaz Diaz. # Copyright (C) 2011-2026 Antonio Diaz Diaz.
# #
# This script is free software: you have unlimited permission # This script is free software: you have unlimited permission
# to copy, distribute, and modify it. # to copy, distribute, and modify it.
@ -26,35 +26,38 @@ if [ -d tmp ] ; then rm -rf tmp ; fi
mkdir tmp mkdir tmp
cd "${objdir}"/tmp || framework_failure cd "${objdir}"/tmp || framework_failure
in="${testdir}"/test.txt
fail=0 fail=0
test_failed() { fail=1 ; printf "\n$1" ; [ -z "$2" ] || printf "($2)" ; } test_failed() { fail=1 ; printf "\n$1" ; [ -z "$2" ] || printf "($2)" ; }
printf "testing arg_parser-%s..." "$2" printf "testing arg_parser-%s..." "$2"
for i in "${PARSER}" "${CPARSER}" ; do for i in "${PARSER}" "${CPARSER}" ; do
"$i" -h > /dev/null || test_failed $LINENO "$i" "$i" -h > /dev/null || test_failed $LINENO "$i"
"$i" --help > /dev/null || test_failed $LINENO "$i" "$i" --help > /dev/null || test_failed $LINENO "$i"
"$i" -V > /dev/null || test_failed $LINENO "$i" "$i" -V > /dev/null || test_failed $LINENO "$i"
"$i" --version > /dev/null || test_failed $LINENO "$i" "$i" --version > /dev/null || test_failed $LINENO "$i"
"$i" -h -v > /dev/null || test_failed $LINENO "$i" "$i" -h -v > /dev/null || test_failed $LINENO "$i"
"$i" -v -h > /dev/null || test_failed $LINENO "$i" "$i" -v -h > /dev/null || test_failed $LINENO "$i"
"$i" --append -a -b 5 --block 10 -c -carg --casual --casual=arg -o file --orphan --verbose file1 file2 > out || "$i" --append -a -b 5 --block 10 -c -carg --casual= --casual=arg -e "" -1 \
test_failed $LINENO "$i" -earg --empty= --empty=arg -H -o file --l -v --verbose -.5 -3.14 -inf \
cmp "${in}" out || test_failed $LINENO "$i" -Inf -INF file1 file2 > out || test_failed $LINENO "$i"
cmp "${testdir}"/test1.txt out || test_failed $LINENO "$i"
"$i" file1 file2 --append -ab5 --block=10 -c -carg --casua --casua=arg -ofile --orpha --verbos > out || "$i" -1 -.5 file1 -3.14 -inf file2 --appen -ab5 --bloc=10 -c -carg --casu \
test_failed $LINENO "$i" --casu=arg -e "" -earg --empt="" --empt=arg -H -ofile --long-o -v \
cmp "${in}" out || test_failed $LINENO "$i" --verbos -Inf -INF > out || test_failed $LINENO "$i"
cmp "${testdir}"/test2.txt out || test_failed $LINENO "$i"
"$i" --append -a file1 -b 5 --block 10 -c -carg --casu --casu=arg -o file file2 --orph --verbo > out || "$i" --appe -1 -.5 -a file1 -b 5 --blo 10 -c -carg --casua --casua=arg \
test_failed $LINENO "$i" -e "" -e arg -3.14 --emp "" --emp arg -inf -H -o file file2 \
cmp "${in}" out || test_failed $LINENO "$i" --lon -Inf -v -INF --verbo > out || test_failed $LINENO "$i"
cmp "${testdir}"/test3.txt out || test_failed $LINENO "$i"
"$i" --append -a -b5 --block=10 -c -carg --cas= file1 --cas=arg -ofile --orp --verb file2 > out || "$i" --app -a -b5 -1 --bl=10 -c -.5 -carg --cas= --cas=arg -e "" \
test_failed $LINENO "$i" file1 -e arg --em "" -3.14 --em arg -H -ofile -inf --long-only -v \
cmp "${in}" out || test_failed $LINENO "$i" --verb file2 -Inf -INF > out || test_failed $LINENO "$i"
cmp "${testdir}"/test4.txt out || test_failed $LINENO "$i"
done done
"${PARSER}" --int-min --int-max > out || test_failed $LINENO "${PARSER}" --int-min --int-max > out || test_failed $LINENO
@ -64,36 +67,42 @@ cmp out cout || test_failed $LINENO
printf "\ntesting bad input..." printf "\ntesting bad input..."
for i in "${PARSER}" "${CPARSER}" ; do for i in "${PARSER}" "${CPARSER}" ; do
"$i" --v 2> /dev/null # ambiguous option cp cout out || framework_failure
"$i" --v 2> cout # ambiguous option
[ $? = 1 ] || test_failed $LINENO "$i" [ $? = 1 ] || test_failed $LINENO "$i"
"$i" --ve 2> /dev/null "$i" --ve 2>> cout
[ $? = 1 ] || test_failed $LINENO "$i" [ $? = 1 ] || test_failed $LINENO "$i"
"$i" --ver 2> /dev/null "$i" --ver 2>> cout
[ $? = 1 ] || test_failed $LINENO "$i" [ $? = 1 ] || test_failed $LINENO "$i"
"$i" --unrecognized 2> /dev/null # unrecognized option "$i" --unrecognized 2>> cout # unrecognized option
[ $? = 1 ] || test_failed $LINENO "$i" [ $? = 1 ] || test_failed $LINENO "$i"
"$i" -x 2> /dev/null # invalid option "$i" -x 2>> cout # invalid option
[ $? = 1 ] || test_failed $LINENO "$i" [ $? = 1 ] || test_failed $LINENO "$i"
"$i" -aARG 2> /dev/null # argument not allowed "$i" -aARG 2>> cout # argument not allowed
[ $? = 1 ] || test_failed $LINENO "$i" [ $? = 1 ] || test_failed $LINENO "$i"
"$i" -b 2> /dev/null # argument required "$i" -b 2>> cout # argument required
[ $? = 1 ] || test_failed $LINENO "$i" [ $? = 1 ] || test_failed $LINENO "$i"
"$i" --block 2> /dev/null "$i" --block 2>> cout
[ $? = 1 ] || test_failed $LINENO "$i" [ $? = 1 ] || test_failed $LINENO "$i"
"$i" --block= 2> /dev/null "$i" --block= 2>> cout
[ $? = 1 ] || test_failed $LINENO "$i" [ $? = 1 ] || test_failed $LINENO "$i"
"$i" -u 2> /dev/null # uncaught option "$i" -e"" 2>> cout
[ $? = 1 ] || test_failed $LINENO "$i"
"$i" -u 2>> cout # uncaught option
[ $? = 3 ] || test_failed $LINENO "$i" [ $? = 3 ] || test_failed $LINENO "$i"
done done
sed -e 's/carg_parser/arg_parser/' cout > out2 || framework_failure
diff -u out out2 || test_failed $LINENO
echo echo
if [ ${fail} = 0 ] ; then if [ ${fail} = 0 ] ; then

View file

@ -1,13 +0,0 @@
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-o' with argument 'file'
option '--orphan'
option '--verbose'
non-option argument 'file1'
non-option argument 'file2'

View file

@ -0,0 +1,437 @@
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-1'
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
option '-.' with argument '5'
option '-3' with argument '.14'
option '-i' with argument 'nf'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
non-option argument 'file1'
non-option argument 'file2'
(options reordered)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
non-option argument '-1'
non-option argument '-.5'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument '-Inf'
non-option argument '-INF'
non-option argument 'file1'
non-option argument 'file2'
(options reordered and negative numbers treated as non-options)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-1'
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
option '-.' with argument '5'
option '-3' with argument '.14'
option '-i' with argument 'nf'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
non-option argument 'file1'
non-option argument 'file2'
(0 | in_order)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-1'
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
option '-.' with argument '5'
option '-3' with argument '.14'
option '-i' with argument 'nf'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
non-option argument 'file1'
non-option argument 'file2'
(0 | in_order_stop)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-1'
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
option '-.' with argument '5'
option '-3' with argument '.14'
option '-i' with argument 'nf'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
non-option argument 'file1'
non-option argument 'file2'
(0 | in_order | in_order_stop)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-1'
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
option '-.' with argument '5'
option '-3' with argument '.14'
option '-i' with argument 'nf'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
skipped non-option argument 'file1'
skipped non-option argument 'file2'
(0 | in_order_skip)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-1'
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
option '-.' with argument '5'
option '-3' with argument '.14'
option '-i' with argument 'nf'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
skipped non-option argument 'file1'
skipped non-option argument 'file2'
(0 | in_order | in_order_skip)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-1'
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
option '-.' with argument '5'
option '-3' with argument '.14'
option '-i' with argument 'nf'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
skipped non-option argument 'file1'
skipped non-option argument 'file2'
(0 | in_order_stop | in_order_skip)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-1'
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
option '-.' with argument '5'
option '-3' with argument '.14'
option '-i' with argument 'nf'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
skipped non-option argument 'file1'
skipped non-option argument 'file2'
(0 | in_order | in_order_stop | in_order_skip)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
non-option argument '-1'
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
non-option argument '-.5'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument '-Inf'
non-option argument '-INF'
non-option argument 'file1'
non-option argument 'file2'
(0 | in_order | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
non-option argument '-1'
non-option argument '-earg'
non-option argument '--empty='
non-option argument '--empty=arg'
non-option argument '-H'
non-option argument '-o'
non-option argument 'file'
non-option argument '--l'
non-option argument '-v'
non-option argument '--verbose'
non-option argument '-.5'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument '-Inf'
non-option argument '-INF'
non-option argument 'file1'
non-option argument 'file2'
(0 | in_order_stop | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
non-option argument '-1'
non-option argument '-earg'
non-option argument '--empty='
non-option argument '--empty=arg'
non-option argument '-H'
non-option argument '-o'
non-option argument 'file'
non-option argument '--l'
non-option argument '-v'
non-option argument '--verbose'
non-option argument '-.5'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument '-Inf'
non-option argument '-INF'
non-option argument 'file1'
non-option argument 'file2'
(0 | in_order | in_order_stop | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
skipped non-option argument '-1'
skipped non-option argument '-earg'
skipped non-option argument '--empty='
skipped non-option argument '--empty=arg'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument '--l'
skipped non-option argument '-v'
skipped non-option argument '--verbose'
skipped non-option argument '-.5'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
skipped non-option argument 'file1'
skipped non-option argument 'file2'
(0 | in_order_skip | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
skipped non-option argument '-1'
skipped non-option argument '-earg'
skipped non-option argument '--empty='
skipped non-option argument '--empty=arg'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument '--l'
skipped non-option argument '-v'
skipped non-option argument '--verbose'
skipped non-option argument '-.5'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
skipped non-option argument 'file1'
skipped non-option argument 'file2'
(0 | in_order | in_order_skip | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
skipped non-option argument '-1'
skipped non-option argument '-earg'
skipped non-option argument '--empty='
skipped non-option argument '--empty=arg'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument '--l'
skipped non-option argument '-v'
skipped non-option argument '--verbose'
skipped non-option argument '-.5'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
skipped non-option argument 'file1'
skipped non-option argument 'file2'
(0 | in_order_stop | in_order_skip | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
skipped non-option argument '-1'
skipped non-option argument '-earg'
skipped non-option argument '--empty='
skipped non-option argument '--empty=arg'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument '--l'
skipped non-option argument '-v'
skipped non-option argument '--verbose'
skipped non-option argument '-.5'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
skipped non-option argument 'file1'
skipped non-option argument 'file2'
(0 | in_order | in_order_stop | in_order_skip | neg_non_opt)

View file

@ -0,0 +1,431 @@
option '-1'
option '-.' with argument '5'
option '-3' with argument '.14'
option '-i' with argument 'nf'
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
non-option argument 'file1'
non-option argument 'file2'
(options reordered)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
non-option argument '-1'
non-option argument '-.5'
non-option argument 'file1'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument 'file2'
non-option argument '-Inf'
non-option argument '-INF'
(options reordered and negative numbers treated as non-options)
option '-1'
option '-.' with argument '5'
non-option argument 'file1'
option '-3' with argument '.14'
option '-i' with argument 'nf'
non-option argument 'file2'
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
(0 | in_order)
option '-1'
option '-.' with argument '5'
non-option argument 'file1'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument 'file2'
non-option argument '--appen'
non-option argument '-ab5'
non-option argument '--bloc=10'
non-option argument '-c'
non-option argument '-carg'
non-option argument '--casu'
non-option argument '--casu=arg'
non-option argument '-e'
non-option argument ''
non-option argument '-earg'
non-option argument '--empt='
non-option argument '--empt=arg'
non-option argument '-H'
non-option argument '-ofile'
non-option argument '--long-o'
non-option argument '-v'
non-option argument '--verbos'
non-option argument '-Inf'
non-option argument '-INF'
(0 | in_order_stop)
option '-1'
option '-.' with argument '5'
non-option argument 'file1'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument 'file2'
non-option argument '--appen'
non-option argument '-ab5'
non-option argument '--bloc=10'
non-option argument '-c'
non-option argument '-carg'
non-option argument '--casu'
non-option argument '--casu=arg'
non-option argument '-e'
non-option argument ''
non-option argument '-earg'
non-option argument '--empt='
non-option argument '--empt=arg'
non-option argument '-H'
non-option argument '-ofile'
non-option argument '--long-o'
non-option argument '-v'
non-option argument '--verbos'
non-option argument '-Inf'
non-option argument '-INF'
(0 | in_order | in_order_stop)
option '-1'
option '-.' with argument '5'
skipped non-option argument 'file1'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument 'file2'
skipped non-option argument '--appen'
skipped non-option argument '-ab5'
skipped non-option argument '--bloc=10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casu'
skipped non-option argument '--casu=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-earg'
skipped non-option argument '--empt='
skipped non-option argument '--empt=arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '--long-o'
skipped non-option argument '-v'
skipped non-option argument '--verbos'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order_skip)
option '-1'
option '-.' with argument '5'
skipped non-option argument 'file1'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument 'file2'
skipped non-option argument '--appen'
skipped non-option argument '-ab5'
skipped non-option argument '--bloc=10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casu'
skipped non-option argument '--casu=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-earg'
skipped non-option argument '--empt='
skipped non-option argument '--empt=arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '--long-o'
skipped non-option argument '-v'
skipped non-option argument '--verbos'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order | in_order_skip)
option '-1'
option '-.' with argument '5'
skipped non-option argument 'file1'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument 'file2'
skipped non-option argument '--appen'
skipped non-option argument '-ab5'
skipped non-option argument '--bloc=10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casu'
skipped non-option argument '--casu=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-earg'
skipped non-option argument '--empt='
skipped non-option argument '--empt=arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '--long-o'
skipped non-option argument '-v'
skipped non-option argument '--verbos'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order_stop | in_order_skip)
option '-1'
option '-.' with argument '5'
skipped non-option argument 'file1'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument 'file2'
skipped non-option argument '--appen'
skipped non-option argument '-ab5'
skipped non-option argument '--bloc=10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casu'
skipped non-option argument '--casu=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-earg'
skipped non-option argument '--empt='
skipped non-option argument '--empt=arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '--long-o'
skipped non-option argument '-v'
skipped non-option argument '--verbos'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order | in_order_stop | in_order_skip)
non-option argument '-1'
non-option argument '-.5'
non-option argument 'file1'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument 'file2'
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
non-option argument '-Inf'
non-option argument '-INF'
(0 | in_order | neg_non_opt)
non-option argument '-1'
non-option argument '-.5'
non-option argument 'file1'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument 'file2'
non-option argument '--appen'
non-option argument '-ab5'
non-option argument '--bloc=10'
non-option argument '-c'
non-option argument '-carg'
non-option argument '--casu'
non-option argument '--casu=arg'
non-option argument '-e'
non-option argument ''
non-option argument '-earg'
non-option argument '--empt='
non-option argument '--empt=arg'
non-option argument '-H'
non-option argument '-ofile'
non-option argument '--long-o'
non-option argument '-v'
non-option argument '--verbos'
non-option argument '-Inf'
non-option argument '-INF'
(0 | in_order_stop | neg_non_opt)
non-option argument '-1'
non-option argument '-.5'
non-option argument 'file1'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument 'file2'
non-option argument '--appen'
non-option argument '-ab5'
non-option argument '--bloc=10'
non-option argument '-c'
non-option argument '-carg'
non-option argument '--casu'
non-option argument '--casu=arg'
non-option argument '-e'
non-option argument ''
non-option argument '-earg'
non-option argument '--empt='
non-option argument '--empt=arg'
non-option argument '-H'
non-option argument '-ofile'
non-option argument '--long-o'
non-option argument '-v'
non-option argument '--verbos'
non-option argument '-Inf'
non-option argument '-INF'
(0 | in_order | in_order_stop | neg_non_opt)
skipped non-option argument '-1'
skipped non-option argument '-.5'
skipped non-option argument 'file1'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument 'file2'
skipped non-option argument '--appen'
skipped non-option argument '-ab5'
skipped non-option argument '--bloc=10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casu'
skipped non-option argument '--casu=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-earg'
skipped non-option argument '--empt='
skipped non-option argument '--empt=arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '--long-o'
skipped non-option argument '-v'
skipped non-option argument '--verbos'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order_skip | neg_non_opt)
skipped non-option argument '-1'
skipped non-option argument '-.5'
skipped non-option argument 'file1'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument 'file2'
skipped non-option argument '--appen'
skipped non-option argument '-ab5'
skipped non-option argument '--bloc=10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casu'
skipped non-option argument '--casu=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-earg'
skipped non-option argument '--empt='
skipped non-option argument '--empt=arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '--long-o'
skipped non-option argument '-v'
skipped non-option argument '--verbos'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order | in_order_skip | neg_non_opt)
skipped non-option argument '-1'
skipped non-option argument '-.5'
skipped non-option argument 'file1'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument 'file2'
skipped non-option argument '--appen'
skipped non-option argument '-ab5'
skipped non-option argument '--bloc=10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casu'
skipped non-option argument '--casu=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-earg'
skipped non-option argument '--empt='
skipped non-option argument '--empt=arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '--long-o'
skipped non-option argument '-v'
skipped non-option argument '--verbos'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order_stop | in_order_skip | neg_non_opt)
skipped non-option argument '-1'
skipped non-option argument '-.5'
skipped non-option argument 'file1'
skipped non-option argument '-3.14'
skipped non-option argument '-inf'
skipped non-option argument 'file2'
skipped non-option argument '--appen'
skipped non-option argument '-ab5'
skipped non-option argument '--bloc=10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casu'
skipped non-option argument '--casu=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-earg'
skipped non-option argument '--empt='
skipped non-option argument '--empt=arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '--long-o'
skipped non-option argument '-v'
skipped non-option argument '--verbos'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order | in_order_stop | in_order_skip | neg_non_opt)

View file

@ -0,0 +1,515 @@
option '--append'
option '-1'
option '-.' with argument '5'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
option '-3' with argument '.14'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-i' with argument 'nf'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-I' with argument 'nf'
option '-v'
option '-I' with argument 'NF'
option '--verbose'
non-option argument 'file1'
non-option argument 'file2'
(options reordered)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
non-option argument '-1'
non-option argument '-.5'
non-option argument 'file1'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument 'file2'
non-option argument '-Inf'
non-option argument '-INF'
(options reordered and negative numbers treated as non-options)
option '--append'
option '-1'
option '-.' with argument '5'
option '-a'
non-option argument 'file1'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
option '-3' with argument '.14'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-i' with argument 'nf'
option '-H'
option '-o' with argument 'file'
non-option argument 'file2'
option '--long-only'
option '-I' with argument 'nf'
option '-v'
option '-I' with argument 'NF'
option '--verbose'
(0 | in_order)
option '--append'
option '-1'
option '-.' with argument '5'
option '-a'
non-option argument 'file1'
non-option argument '-b'
non-option argument '5'
non-option argument '--blo'
non-option argument '10'
non-option argument '-c'
non-option argument '-carg'
non-option argument '--casua'
non-option argument '--casua=arg'
non-option argument '-e'
non-option argument ''
non-option argument '-e'
non-option argument 'arg'
non-option argument '-3.14'
non-option argument '--emp'
non-option argument ''
non-option argument '--emp'
non-option argument 'arg'
non-option argument '-inf'
non-option argument '-H'
non-option argument '-o'
non-option argument 'file'
non-option argument 'file2'
non-option argument '--lon'
non-option argument '-Inf'
non-option argument '-v'
non-option argument '-INF'
non-option argument '--verbo'
(0 | in_order_stop)
option '--append'
option '-1'
option '-.' with argument '5'
option '-a'
non-option argument 'file1'
non-option argument '-b'
non-option argument '5'
non-option argument '--blo'
non-option argument '10'
non-option argument '-c'
non-option argument '-carg'
non-option argument '--casua'
non-option argument '--casua=arg'
non-option argument '-e'
non-option argument ''
non-option argument '-e'
non-option argument 'arg'
non-option argument '-3.14'
non-option argument '--emp'
non-option argument ''
non-option argument '--emp'
non-option argument 'arg'
non-option argument '-inf'
non-option argument '-H'
non-option argument '-o'
non-option argument 'file'
non-option argument 'file2'
non-option argument '--lon'
non-option argument '-Inf'
non-option argument '-v'
non-option argument '-INF'
non-option argument '--verbo'
(0 | in_order | in_order_stop)
option '--append'
option '-1'
option '-.' with argument '5'
option '-a'
skipped non-option argument 'file1'
skipped non-option argument '-b'
skipped non-option argument '5'
skipped non-option argument '--blo'
skipped non-option argument '10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casua'
skipped non-option argument '--casua=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '-3.14'
skipped non-option argument '--emp'
skipped non-option argument ''
skipped non-option argument '--emp'
skipped non-option argument 'arg'
skipped non-option argument '-inf'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument 'file2'
skipped non-option argument '--lon'
skipped non-option argument '-Inf'
skipped non-option argument '-v'
skipped non-option argument '-INF'
skipped non-option argument '--verbo'
(0 | in_order_skip)
option '--append'
option '-1'
option '-.' with argument '5'
option '-a'
skipped non-option argument 'file1'
skipped non-option argument '-b'
skipped non-option argument '5'
skipped non-option argument '--blo'
skipped non-option argument '10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casua'
skipped non-option argument '--casua=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '-3.14'
skipped non-option argument '--emp'
skipped non-option argument ''
skipped non-option argument '--emp'
skipped non-option argument 'arg'
skipped non-option argument '-inf'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument 'file2'
skipped non-option argument '--lon'
skipped non-option argument '-Inf'
skipped non-option argument '-v'
skipped non-option argument '-INF'
skipped non-option argument '--verbo'
(0 | in_order | in_order_skip)
option '--append'
option '-1'
option '-.' with argument '5'
option '-a'
skipped non-option argument 'file1'
skipped non-option argument '-b'
skipped non-option argument '5'
skipped non-option argument '--blo'
skipped non-option argument '10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casua'
skipped non-option argument '--casua=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '-3.14'
skipped non-option argument '--emp'
skipped non-option argument ''
skipped non-option argument '--emp'
skipped non-option argument 'arg'
skipped non-option argument '-inf'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument 'file2'
skipped non-option argument '--lon'
skipped non-option argument '-Inf'
skipped non-option argument '-v'
skipped non-option argument '-INF'
skipped non-option argument '--verbo'
(0 | in_order_stop | in_order_skip)
option '--append'
option '-1'
option '-.' with argument '5'
option '-a'
skipped non-option argument 'file1'
skipped non-option argument '-b'
skipped non-option argument '5'
skipped non-option argument '--blo'
skipped non-option argument '10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casua'
skipped non-option argument '--casua=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '-3.14'
skipped non-option argument '--emp'
skipped non-option argument ''
skipped non-option argument '--emp'
skipped non-option argument 'arg'
skipped non-option argument '-inf'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument 'file2'
skipped non-option argument '--lon'
skipped non-option argument '-Inf'
skipped non-option argument '-v'
skipped non-option argument '-INF'
skipped non-option argument '--verbo'
(0 | in_order | in_order_stop | in_order_skip)
option '--append'
non-option argument '-1'
non-option argument '-.5'
option '-a'
non-option argument 'file1'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
non-option argument '-3.14'
option '--empty' with argument ''
option '--empty' with argument 'arg'
non-option argument '-inf'
option '-H'
option '-o' with argument 'file'
non-option argument 'file2'
option '--long-only'
non-option argument '-Inf'
option '-v'
non-option argument '-INF'
option '--verbose'
(0 | in_order | neg_non_opt)
option '--append'
non-option argument '-1'
non-option argument '-.5'
non-option argument '-a'
non-option argument 'file1'
non-option argument '-b'
non-option argument '5'
non-option argument '--blo'
non-option argument '10'
non-option argument '-c'
non-option argument '-carg'
non-option argument '--casua'
non-option argument '--casua=arg'
non-option argument '-e'
non-option argument ''
non-option argument '-e'
non-option argument 'arg'
non-option argument '-3.14'
non-option argument '--emp'
non-option argument ''
non-option argument '--emp'
non-option argument 'arg'
non-option argument '-inf'
non-option argument '-H'
non-option argument '-o'
non-option argument 'file'
non-option argument 'file2'
non-option argument '--lon'
non-option argument '-Inf'
non-option argument '-v'
non-option argument '-INF'
non-option argument '--verbo'
(0 | in_order_stop | neg_non_opt)
option '--append'
non-option argument '-1'
non-option argument '-.5'
non-option argument '-a'
non-option argument 'file1'
non-option argument '-b'
non-option argument '5'
non-option argument '--blo'
non-option argument '10'
non-option argument '-c'
non-option argument '-carg'
non-option argument '--casua'
non-option argument '--casua=arg'
non-option argument '-e'
non-option argument ''
non-option argument '-e'
non-option argument 'arg'
non-option argument '-3.14'
non-option argument '--emp'
non-option argument ''
non-option argument '--emp'
non-option argument 'arg'
non-option argument '-inf'
non-option argument '-H'
non-option argument '-o'
non-option argument 'file'
non-option argument 'file2'
non-option argument '--lon'
non-option argument '-Inf'
non-option argument '-v'
non-option argument '-INF'
non-option argument '--verbo'
(0 | in_order | in_order_stop | neg_non_opt)
option '--append'
skipped non-option argument '-1'
skipped non-option argument '-.5'
skipped non-option argument '-a'
skipped non-option argument 'file1'
skipped non-option argument '-b'
skipped non-option argument '5'
skipped non-option argument '--blo'
skipped non-option argument '10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casua'
skipped non-option argument '--casua=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '-3.14'
skipped non-option argument '--emp'
skipped non-option argument ''
skipped non-option argument '--emp'
skipped non-option argument 'arg'
skipped non-option argument '-inf'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument 'file2'
skipped non-option argument '--lon'
skipped non-option argument '-Inf'
skipped non-option argument '-v'
skipped non-option argument '-INF'
skipped non-option argument '--verbo'
(0 | in_order_skip | neg_non_opt)
option '--append'
skipped non-option argument '-1'
skipped non-option argument '-.5'
skipped non-option argument '-a'
skipped non-option argument 'file1'
skipped non-option argument '-b'
skipped non-option argument '5'
skipped non-option argument '--blo'
skipped non-option argument '10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casua'
skipped non-option argument '--casua=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '-3.14'
skipped non-option argument '--emp'
skipped non-option argument ''
skipped non-option argument '--emp'
skipped non-option argument 'arg'
skipped non-option argument '-inf'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument 'file2'
skipped non-option argument '--lon'
skipped non-option argument '-Inf'
skipped non-option argument '-v'
skipped non-option argument '-INF'
skipped non-option argument '--verbo'
(0 | in_order | in_order_skip | neg_non_opt)
option '--append'
skipped non-option argument '-1'
skipped non-option argument '-.5'
skipped non-option argument '-a'
skipped non-option argument 'file1'
skipped non-option argument '-b'
skipped non-option argument '5'
skipped non-option argument '--blo'
skipped non-option argument '10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casua'
skipped non-option argument '--casua=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '-3.14'
skipped non-option argument '--emp'
skipped non-option argument ''
skipped non-option argument '--emp'
skipped non-option argument 'arg'
skipped non-option argument '-inf'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument 'file2'
skipped non-option argument '--lon'
skipped non-option argument '-Inf'
skipped non-option argument '-v'
skipped non-option argument '-INF'
skipped non-option argument '--verbo'
(0 | in_order_stop | in_order_skip | neg_non_opt)
option '--append'
skipped non-option argument '-1'
skipped non-option argument '-.5'
skipped non-option argument '-a'
skipped non-option argument 'file1'
skipped non-option argument '-b'
skipped non-option argument '5'
skipped non-option argument '--blo'
skipped non-option argument '10'
skipped non-option argument '-c'
skipped non-option argument '-carg'
skipped non-option argument '--casua'
skipped non-option argument '--casua=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '-3.14'
skipped non-option argument '--emp'
skipped non-option argument ''
skipped non-option argument '--emp'
skipped non-option argument 'arg'
skipped non-option argument '-inf'
skipped non-option argument '-H'
skipped non-option argument '-o'
skipped non-option argument 'file'
skipped non-option argument 'file2'
skipped non-option argument '--lon'
skipped non-option argument '-Inf'
skipped non-option argument '-v'
skipped non-option argument '-INF'
skipped non-option argument '--verbo'
(0 | in_order | in_order_stop | in_order_skip | neg_non_opt)

View file

@ -0,0 +1,473 @@
option '--append'
option '-a'
option '-b' with argument '5'
option '-1'
option '--block' with argument '10'
option '-c'
option '-.' with argument '5'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
option '--empty' with argument ''
option '-3' with argument '.14'
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '-i' with argument 'nf'
option '--long-only'
option '-v'
option '--verbose'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
non-option argument 'file1'
non-option argument 'file2'
(options reordered)
option '--append'
option '-a'
option '-b' with argument '5'
option '--block' with argument '10'
option '-c'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
option '-e' with argument 'arg'
option '--empty' with argument ''
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '--long-only'
option '-v'
option '--verbose'
non-option argument '-1'
non-option argument '-.5'
non-option argument 'file1'
non-option argument '-3.14'
non-option argument '-inf'
non-option argument 'file2'
non-option argument '-Inf'
non-option argument '-INF'
(options reordered and negative numbers treated as non-options)
option '--append'
option '-a'
option '-b' with argument '5'
option '-1'
option '--block' with argument '10'
option '-c'
option '-.' with argument '5'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
non-option argument 'file1'
option '-e' with argument 'arg'
option '--empty' with argument ''
option '-3' with argument '.14'
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
option '-i' with argument 'nf'
option '--long-only'
option '-v'
option '--verbose'
non-option argument 'file2'
option '-I' with argument 'nf'
option '-I' with argument 'NF'
(0 | in_order)
option '--append'
option '-a'
option '-b' with argument '5'
option '-1'
option '--block' with argument '10'
option '-c'
option '-.' with argument '5'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
non-option argument 'file1'
non-option argument '-e'
non-option argument 'arg'
non-option argument '--em'
non-option argument ''
non-option argument '-3.14'
non-option argument '--em'
non-option argument 'arg'
non-option argument '-H'
non-option argument '-ofile'
non-option argument '-inf'
non-option argument '--long-only'
non-option argument '-v'
non-option argument '--verb'
non-option argument 'file2'
non-option argument '-Inf'
non-option argument '-INF'
(0 | in_order_stop)
option '--append'
option '-a'
option '-b' with argument '5'
option '-1'
option '--block' with argument '10'
option '-c'
option '-.' with argument '5'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
non-option argument 'file1'
non-option argument '-e'
non-option argument 'arg'
non-option argument '--em'
non-option argument ''
non-option argument '-3.14'
non-option argument '--em'
non-option argument 'arg'
non-option argument '-H'
non-option argument '-ofile'
non-option argument '-inf'
non-option argument '--long-only'
non-option argument '-v'
non-option argument '--verb'
non-option argument 'file2'
non-option argument '-Inf'
non-option argument '-INF'
(0 | in_order | in_order_stop)
option '--append'
option '-a'
option '-b' with argument '5'
option '-1'
option '--block' with argument '10'
option '-c'
option '-.' with argument '5'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
skipped non-option argument 'file1'
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '--em'
skipped non-option argument ''
skipped non-option argument '-3.14'
skipped non-option argument '--em'
skipped non-option argument 'arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '-inf'
skipped non-option argument '--long-only'
skipped non-option argument '-v'
skipped non-option argument '--verb'
skipped non-option argument 'file2'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order_skip)
option '--append'
option '-a'
option '-b' with argument '5'
option '-1'
option '--block' with argument '10'
option '-c'
option '-.' with argument '5'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
skipped non-option argument 'file1'
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '--em'
skipped non-option argument ''
skipped non-option argument '-3.14'
skipped non-option argument '--em'
skipped non-option argument 'arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '-inf'
skipped non-option argument '--long-only'
skipped non-option argument '-v'
skipped non-option argument '--verb'
skipped non-option argument 'file2'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order | in_order_skip)
option '--append'
option '-a'
option '-b' with argument '5'
option '-1'
option '--block' with argument '10'
option '-c'
option '-.' with argument '5'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
skipped non-option argument 'file1'
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '--em'
skipped non-option argument ''
skipped non-option argument '-3.14'
skipped non-option argument '--em'
skipped non-option argument 'arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '-inf'
skipped non-option argument '--long-only'
skipped non-option argument '-v'
skipped non-option argument '--verb'
skipped non-option argument 'file2'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order_stop | in_order_skip)
option '--append'
option '-a'
option '-b' with argument '5'
option '-1'
option '--block' with argument '10'
option '-c'
option '-.' with argument '5'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
skipped non-option argument 'file1'
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '--em'
skipped non-option argument ''
skipped non-option argument '-3.14'
skipped non-option argument '--em'
skipped non-option argument 'arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '-inf'
skipped non-option argument '--long-only'
skipped non-option argument '-v'
skipped non-option argument '--verb'
skipped non-option argument 'file2'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order | in_order_stop | in_order_skip)
option '--append'
option '-a'
option '-b' with argument '5'
non-option argument '-1'
option '--block' with argument '10'
option '-c'
non-option argument '-.5'
option '-c' with argument 'arg'
option '--casual'
option '--casual' with argument 'arg'
option '-e' with argument ''
non-option argument 'file1'
option '-e' with argument 'arg'
option '--empty' with argument ''
non-option argument '-3.14'
option '--empty' with argument 'arg'
option '-H'
option '-o' with argument 'file'
non-option argument '-inf'
option '--long-only'
option '-v'
option '--verbose'
non-option argument 'file2'
non-option argument '-Inf'
non-option argument '-INF'
(0 | in_order | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
non-option argument '-1'
non-option argument '--bl=10'
non-option argument '-c'
non-option argument '-.5'
non-option argument '-carg'
non-option argument '--cas='
non-option argument '--cas=arg'
non-option argument '-e'
non-option argument ''
non-option argument 'file1'
non-option argument '-e'
non-option argument 'arg'
non-option argument '--em'
non-option argument ''
non-option argument '-3.14'
non-option argument '--em'
non-option argument 'arg'
non-option argument '-H'
non-option argument '-ofile'
non-option argument '-inf'
non-option argument '--long-only'
non-option argument '-v'
non-option argument '--verb'
non-option argument 'file2'
non-option argument '-Inf'
non-option argument '-INF'
(0 | in_order_stop | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
non-option argument '-1'
non-option argument '--bl=10'
non-option argument '-c'
non-option argument '-.5'
non-option argument '-carg'
non-option argument '--cas='
non-option argument '--cas=arg'
non-option argument '-e'
non-option argument ''
non-option argument 'file1'
non-option argument '-e'
non-option argument 'arg'
non-option argument '--em'
non-option argument ''
non-option argument '-3.14'
non-option argument '--em'
non-option argument 'arg'
non-option argument '-H'
non-option argument '-ofile'
non-option argument '-inf'
non-option argument '--long-only'
non-option argument '-v'
non-option argument '--verb'
non-option argument 'file2'
non-option argument '-Inf'
non-option argument '-INF'
(0 | in_order | in_order_stop | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
skipped non-option argument '-1'
skipped non-option argument '--bl=10'
skipped non-option argument '-c'
skipped non-option argument '-.5'
skipped non-option argument '-carg'
skipped non-option argument '--cas='
skipped non-option argument '--cas=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument 'file1'
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '--em'
skipped non-option argument ''
skipped non-option argument '-3.14'
skipped non-option argument '--em'
skipped non-option argument 'arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '-inf'
skipped non-option argument '--long-only'
skipped non-option argument '-v'
skipped non-option argument '--verb'
skipped non-option argument 'file2'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order_skip | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
skipped non-option argument '-1'
skipped non-option argument '--bl=10'
skipped non-option argument '-c'
skipped non-option argument '-.5'
skipped non-option argument '-carg'
skipped non-option argument '--cas='
skipped non-option argument '--cas=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument 'file1'
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '--em'
skipped non-option argument ''
skipped non-option argument '-3.14'
skipped non-option argument '--em'
skipped non-option argument 'arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '-inf'
skipped non-option argument '--long-only'
skipped non-option argument '-v'
skipped non-option argument '--verb'
skipped non-option argument 'file2'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order | in_order_skip | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
skipped non-option argument '-1'
skipped non-option argument '--bl=10'
skipped non-option argument '-c'
skipped non-option argument '-.5'
skipped non-option argument '-carg'
skipped non-option argument '--cas='
skipped non-option argument '--cas=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument 'file1'
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '--em'
skipped non-option argument ''
skipped non-option argument '-3.14'
skipped non-option argument '--em'
skipped non-option argument 'arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '-inf'
skipped non-option argument '--long-only'
skipped non-option argument '-v'
skipped non-option argument '--verb'
skipped non-option argument 'file2'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order_stop | in_order_skip | neg_non_opt)
option '--append'
option '-a'
option '-b' with argument '5'
skipped non-option argument '-1'
skipped non-option argument '--bl=10'
skipped non-option argument '-c'
skipped non-option argument '-.5'
skipped non-option argument '-carg'
skipped non-option argument '--cas='
skipped non-option argument '--cas=arg'
skipped non-option argument '-e'
skipped non-option argument ''
skipped non-option argument 'file1'
skipped non-option argument '-e'
skipped non-option argument 'arg'
skipped non-option argument '--em'
skipped non-option argument ''
skipped non-option argument '-3.14'
skipped non-option argument '--em'
skipped non-option argument 'arg'
skipped non-option argument '-H'
skipped non-option argument '-ofile'
skipped non-option argument '-inf'
skipped non-option argument '--long-only'
skipped non-option argument '-v'
skipped non-option argument '--verb'
skipped non-option argument 'file2'
skipped non-option argument '-Inf'
skipped non-option argument '-INF'
(0 | in_order | in_order_stop | in_order_skip | neg_non_opt)

View file

@ -1,4 +1,4 @@
Copyright © 2005-2013 Kepler Project, 2015-2023 Thijs Schreijer. Copyright © 2005-2013 Kepler Project, 2015-2026 Thijs Schreijer.
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation obtaining a copy of this software and associated documentation

View file

@ -26,6 +26,7 @@ install:
cp src/copas/lock.lua $(DESTDIR)$(LUA_DIR)/copas/lock.lua cp src/copas/lock.lua $(DESTDIR)$(LUA_DIR)/copas/lock.lua
cp src/copas/semaphore.lua $(DESTDIR)$(LUA_DIR)/copas/semaphore.lua cp src/copas/semaphore.lua $(DESTDIR)$(LUA_DIR)/copas/semaphore.lua
cp src/copas/queue.lua $(DESTDIR)$(LUA_DIR)/copas/queue.lua cp src/copas/queue.lua $(DESTDIR)$(LUA_DIR)/copas/queue.lua
cp src/copas/future.lua $(DESTDIR)$(LUA_DIR)/copas/future.lua
tests/certs/clientA.pem: tests/certs/clientA.pem:
cd ./tests/certs && \ cd ./tests/certs && \
@ -43,13 +44,16 @@ test: certs
$(LUA) $(DELIM) $(PKGPATH) tests/close.lua $(LUA) $(DELIM) $(PKGPATH) tests/close.lua
$(LUA) $(DELIM) $(PKGPATH) tests/connecttwice.lua $(LUA) $(DELIM) $(PKGPATH) tests/connecttwice.lua
$(LUA) $(DELIM) $(PKGPATH) tests/errhandlers.lua $(LUA) $(DELIM) $(PKGPATH) tests/errhandlers.lua
$(LUA) $(DELIM) $(PKGPATH) tests/future.lua
$(LUA) $(DELIM) $(PKGPATH) tests/exit.lua $(LUA) $(DELIM) $(PKGPATH) tests/exit.lua
$(LUA) $(DELIM) $(PKGPATH) tests/exittest.lua $(LUA) $(DELIM) $(PKGPATH) tests/exittest.lua
$(LUA) $(DELIM) $(PKGPATH) tests/http-timeout.lua $(LUA) $(DELIM) $(PKGPATH) tests/http-timeout.lua
$(LUA) $(DELIM) $(PKGPATH) tests/httpredirect.lua $(LUA) $(DELIM) $(PKGPATH) tests/httpredirect.lua
$(LUA) $(DELIM) $(PKGPATH) tests/httpvalidate.lua
$(LUA) $(DELIM) $(PKGPATH) tests/largetransfer.lua $(LUA) $(DELIM) $(PKGPATH) tests/largetransfer.lua
$(LUA) $(DELIM) $(PKGPATH) tests/lock.lua $(LUA) $(DELIM) $(PKGPATH) tests/lock.lua
$(LUA) $(DELIM) $(PKGPATH) tests/loop_starter.lua $(LUA) $(DELIM) $(PKGPATH) tests/loop_starter.lua
$(LUA) $(DELIM) $(PKGPATH) tests/no_luasocket.lua
$(LUA) $(DELIM) $(PKGPATH) tests/pause.lua $(LUA) $(DELIM) $(PKGPATH) tests/pause.lua
$(LUA) $(DELIM) $(PKGPATH) tests/queue.lua $(LUA) $(DELIM) $(PKGPATH) tests/queue.lua
$(LUA) $(DELIM) $(PKGPATH) tests/removeserver.lua $(LUA) $(DELIM) $(PKGPATH) tests/removeserver.lua
@ -61,8 +65,10 @@ test: certs
$(LUA) $(DELIM) $(PKGPATH) tests/tcptimeout.lua $(LUA) $(DELIM) $(PKGPATH) tests/tcptimeout.lua
$(LUA) $(DELIM) $(PKGPATH) tests/timer.lua $(LUA) $(DELIM) $(PKGPATH) tests/timer.lua
$(LUA) $(DELIM) $(PKGPATH) tests/timeout_errors.lua $(LUA) $(DELIM) $(PKGPATH) tests/timeout_errors.lua
$(LUA) $(DELIM) $(PKGPATH) tests/tls-params.lua
$(LUA) $(DELIM) $(PKGPATH) tests/tls-sni.lua $(LUA) $(DELIM) $(PKGPATH) tests/tls-sni.lua
$(LUA) $(DELIM) $(PKGPATH) tests/udptimeout.lua $(LUA) $(DELIM) $(PKGPATH) tests/udptimeout.lua
$(LUA) $(DELIM) $(PKGPATH) tests/wakeup.lua
$(LUA) $(DELIM) $(LUA) $(DELIM)
coverage: coverage:

View file

@ -14,3 +14,4 @@ install:
copy src\copas\semaphore.lua "$(LUA_DIR)\copas\semaphore.lua" copy src\copas\semaphore.lua "$(LUA_DIR)\copas\semaphore.lua"
copy src\copas\smtp.lua "$(LUA_DIR)\copas\smtp.lua" copy src\copas\smtp.lua "$(LUA_DIR)\copas\smtp.lua"
copy src\copas\timer.lua "$(LUA_DIR)\copas\timer.lua" copy src\copas\timer.lua "$(LUA_DIR)\copas\timer.lua"
copy src\copas\future.lua "$(LUA_DIR)\copas\future.lua"

View file

@ -1,4 +1,4 @@
# Copas 4.7 # Copas 4.12
[![Unix build](https://img.shields.io/github/actions/workflow/status/lunarmodules/copas/unix_build.yml?branch=master&label=Unix%20build&logo=linux)](https://github.com/lunarmodules/copas/actions) [![Unix build](https://img.shields.io/github/actions/workflow/status/lunarmodules/copas/unix_build.yml?branch=master&label=Unix%20build&logo=linux)](https://github.com/lunarmodules/copas/actions)
[![Coveralls code coverage](https://img.shields.io/coveralls/github/lunarmodules/copas?logo=coveralls)](https://coveralls.io/github/lunarmodules/copas) [![Coveralls code coverage](https://img.shields.io/coveralls/github/lunarmodules/copas?logo=coveralls)](https://coveralls.io/github/lunarmodules/copas)
@ -27,9 +27,10 @@ For more details see [the documentation](http://lunarmodules.github.io/copas/).
- update changelog in docs (`index.html`, update `history` and `status` sections) - update changelog in docs (`index.html`, update `history` and `status` sections)
- update version in `copas.lua` - update version in `copas.lua`
- update version at the top of this README, - update version at the top of this README,
- update copyright years if needed - update copyright years if needed (in `license.html` and `copas.lua`)
- update rockspec - update cvs rockspec, and create a new rockspec in ./rockspec
- commit as `release X.Y.Z` - create a release branch and commit as `release X.Y.Z`
- create PR and wait for merge
- tag as `vX_Y_Z` and as `X.Y.Z` - tag as `vX_Y_Z` and as `X.Y.Z`
- push commit and tag - push commit and tag
- upload to luarocks - upload to luarocks

View file

@ -27,8 +27,8 @@ description = {
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
} }
dependencies = { dependencies = {
"lua >= 5.1, < 5.5", "lua >= 5.1, < 5.6",
"luasocket >= 2.1, <= 3.0rc1-2", "luasocket ~> 3",
"coxpcall >= 1.14", "coxpcall >= 1.14",
"binaryheap >= 0.4", "binaryheap >= 0.4",
"timerwheel ~> 1", "timerwheel ~> 1",
@ -48,6 +48,7 @@ build = {
["copas.timer"] = "src/copas/timer.lua", ["copas.timer"] = "src/copas/timer.lua",
["copas.lock"] = "src/copas/lock.lua", ["copas.lock"] = "src/copas/lock.lua",
["copas.semaphore"] = "src/copas/semaphore.lua", ["copas.semaphore"] = "src/copas/semaphore.lua",
["copas.future"] = "src/copas/future.lua",
["copas.queue"] = "src/copas/queue.lua", ["copas.queue"] = "src/copas/queue.lua",
}, },
copy_directories = { copy_directories = {

View file

@ -57,7 +57,7 @@ and client support for http(s), ftp and smtp requests.
<p> <p>
It uses <a href="http://www.cs.princeton.edu/~diego/professional/luasocket/">LuaSocket</a> It uses <a href="http://www.cs.princeton.edu/~diego/professional/luasocket/">LuaSocket</a>
as the interface with the TCP/IP stack and <a href="https://github.com/brunoos/luasec">LuaSec</a> for ssl as the interface with the TCP/IP stack and <a href="https://github.com/brunoos/luasec">LuaSec</a> for ssl
support. support. For concurrent programming Copas provides timers, locks, semaphores, queues, and futures.
</p> </p>
<p> <p>
@ -69,12 +69,12 @@ For a full implementation of a Copas HTTP server you can refer to
</p> </p>
<p>Copas is free software and uses the same <a href="license.html">license</a> <p>Copas is free software and uses the same <a href="license.html">license</a>
as Lua 5.1 to 5.4</p> as Lua 5.1 to 5.5</p>
<h2><a name="status"></a>Status</h2> <h2><a name="status"></a>Status</h2>
<p>Current version is 4.7.0 and was developed for Lua 5.1 to 5.4.</p> <p>Current version is 4.12.0 and was developed for Lua 5.1 to 5.5.</p>
<h2><a name="download"></a>Download</h2> <h2><a name="download"></a>Download</h2>
@ -93,17 +93,113 @@ luarocks install copas
<h2><a name="dependencies"></a>Dependencies</h2> <h2><a name="dependencies"></a>Dependencies</h2>
<p>Copas depends on <p>Copas depends on LuaSocket (or LuaSystem), <a href="http://keplerproject.github.io/coxpcall/">Coxpcall</a>
LuaSocket, <a href="http://keplerproject.github.io/coxpcall/">Coxpcall</a> (only when using Lua 5.1), and (optionally) LuaSec. (only when using Lua 5.1), and (optionally) LuaSec.</p>
</p>
<h2><a name="history"></a>History</h2> <h2><a name="history"></a>History</h2>
<dl class="history"> <dl class="history">
<dt><strong>Copas 4.7.x</strong> [unreleased]</dt>
<dt><strong>Copas 4.12.0</strong> [15/Aug/2026]</dt>
<dd><ul>
<li>Fix: a 0-byte <code>receive(0)</code> on a TCP/SSL socket was treated like a normal
read and queued the caller on <code>select</code>, blocking until more data arrived even
though nothing was needed. Per POSIX, a 0-byte read on a stream socket is a no-op; it now
returns <code>""</code> immediately (#223).</li>
<li>Fix: <code>copas.http</code> included the URL fragment in the request line sent to
the origin server (and, in proxy mode, to the proxy). Fragments are client-side only and
must never be transmitted on the wire; a fragment containing sensitive data (e.g. an OAuth
callback token) was disclosed to the server and to proxy infrastructure/logs (#220).</li>
<li>Fix: <code>sock:dohandshake()</code> returned <code>nil + err</code> on a handshake
timeout instead of throwing like every other failure path. A
timed-out handshake could leave a connection running without the intended TLS
configuration instead of aborting it; it now throws consistently on a failure (#189).</li>
<li>Fix: <code>copas.http</code> reused the SNI server name from the first request of a
redirect chain for every subsequent hop, so a redirect to a different host still sent the
original host's name in the TLS handshake instead of the new target's. This also applied
to an explicitly caller-supplied SNI name, which is now cleared on redirect so it no longer
follows the chain to unrelated hosts.</li>
<li>Fix: <code>copas.http</code> sent a redirect target's literal IP address as the TLS
SNI name when no hostname was available. IP literals are not valid SNI names per RFC 6066;
the SNI extension is now omitted entirely for such requests instead of sending a bogus
name.</li>
<li>Fix: a recurring timer that cancelled and immediately re-armed itself from within
its own callback leaked the old (cancelled) coroutine into the sleeping heap. The old
coroutine would sit there for its full delay (#213).</li>
<li>Fix: TLS <code>sslparams</code> tables that didn't contain the current
<code>wrap</code>/<code>sni</code> keys (e.g. an empty table, or one with misspelled keys)
were misidentified and silently ended up disabling TLS, producing a plaintext connection
with no error. Such tables are now passed through to LuaSec, which validates and errors on
them (#211).</li>
<li>Fix: a socket read/write/connect timeout left a stale entry in that socket's wait queue.
If the socket was reused afterwards (e.g. returned to a pool), a later real waiter could
queue up behind the stale entry and never get woken. Only a single waiter per direction is
now allowed; a conflicting second waiter gets an immediate "already in progress" error
instead of being silently abandoned (#209).</li>
<li>Fix: <code>timer:cancel()</code> could abandon an already in-flight callback instead of
letting it complete (#207).</li>
<li>Fix: several accounting bugs in <code>copas.semaphore</code>: <code>take()</code> and
<code>give()</code> didn't validate their amount, so negative, NaN, or infinite values could
corrupt or unbound the internal count; <code>destroy()</code> only released up to
<code>max</code> queued waiters instead of all of them; and <code>get_wait()</code> counted
waiters that had already been cancelled (#205).</li>
<li>Added <code>semaphore:release_all()</code> to
release every waiter past <code>max</code> (#205).</li>
<li>Fix: <code>copas.future</code> completion was capped at 9999 waiters; the cap has been
removed (#204).</li>
<li>Fix: <code>copas.lock:get()</code> with a zero timeout that failed to acquire immediately
still queued the caller, which then waited forever with nothing left to time it out (#202).</li>
<li>Fix: <code>copas.lock</code> and <code>copas.semaphore</code> could deadlock or leak
resources when a queued waiter was cancelled externally (e.g. via
<code>copas.removethread()</code> or <code>future:cancel()</code>) before being resumed --
<code>lock:release()</code> could hand ownership to a coroutine that would never run, and
<code>semaphore:give()</code> could deduct resources for a waiter that would never consume
them. <code>copas.wakeup()</code> now reports whether it actually woke a coroutine, so callers
can skip stale entries instead (#200).</li>
<li>Fix: <code>copas.http</code> silently fell back to plain HTTP when given a URL with an
unknown/unrecognized scheme, instead of erroring (#195).</li>
</ul></dd>
<dt><strong>Copas 4.11.0</strong> [11/Jun/2026]</dt>
<dd><ul>
<li>Feat: added <code>copas.cancelall()</code>. It cancels pending work to force-exit the loop.
Intended use is for testing, to force a loop exit, even if a test fails.</li>
<li>Fix: when a socket timeout occurs, the registers weren't properly cleaned. This caused
a memory leak. Fix by @haoxianhan in #186.</li>
</ul></dd>
<dt><strong>Copas 4.10.0</strong> [27/Mar/2026]</dt>
<dd><ul>
<li>Feat: added <code>copas.future</code> class. Wraps a thread in a future, allowing other
threads to wait for its result via <code>future:get()</code> or poll with <code>future:try()</code>.
Errors in the task are propagated to callers. Pending tasks can be cancelled with
<code>future:cancel()</code>.</li>
<li>Deps: bump LuaSocket to 3.x release.</li>
</ul></dd>
<dt><strong>Copas 4.9.0</strong> [11/Mar/2026]</dt>
<dd><ul>
<li>Feat: <code>copas.pause()</code> and <code>copas.pauseforever()</code> now return the
actual time slept, in case they got woken up early, and to return a truthy success value.</li>
<li>Fix: the <code>lock</code> and <code>semaphore</code> classes now use the proper coxpcall
version of <code>coroutine.running</code> in case of Lua 5.1, which could cause rare race-conditions.</li>
</ul></dd>
<dt><strong>Copas 4.8.0</strong> [13/Mar/2025]</dt>
<dd><ul>
<li>Change: Copas no longer requires LuaSocket, if no sockets are needed, LuaSystem will be enough as a fallback.</li>
<li>Feat: added <code>copas.gettime()</code>, which transparently maps to either LuaSockets or
LuaSystems implementation, ensuring independence of the availability of either one of those.</li>
<li>Feat: Controlled exit of the Copas loop. Adding <code>copas.exit()</code>, <code>copas.exiting()</code>, and
<code>copas.waitforexit()</code>.</li>
</ul></dd>
<dt><strong>Copas 4.7.1</strong> [9/Mar/2024]</dt>
<dd><ul> <dd><ul>
<li>Fix: <code>copas.removethread</code> would not remove a sleeping thread immediately (it would not execute, but <li>Fix: <code>copas.removethread</code> would not remove a sleeping thread immediately (it would not execute, but
would prevent the Copas loop from exiting until the timer expired).</li> would prevent the Copas loop from exiting until the timer expired).</li>
<li>Fix: <code>queue:finish</code> will return after the last item has been handled, not just popped
(if using workers).</li>
</ul></dd> </ul></dd>
<dt><strong>Copas 4.7.0</strong> [15/Jan/2023]</dt> <dt><strong>Copas 4.7.0</strong> [15/Jan/2023]</dt>

View file

@ -64,7 +64,7 @@ licensed software.</p>
</p> </p>
<hr /> <hr />
<p>Copyright &copy; 2005-2013 Kepler Project, 2015-2023 Thijs Schreijer.</p> <p>Copyright &copy; 2005-2013 Kepler Project, 2015-2026 Thijs Schreijer.</p>
<p> <p>
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation obtaining a copy of this software and associated documentation

View file

@ -81,8 +81,12 @@ local function connection_handler(skt)
end end
copas.addserver(server_socket, copas.handler(connection_handler, copas.addthread(function()
ssl_params), "my_TCP_server") copas.addserver(server_socket, copas.handler(connection_handler,
ssl_params), "my_TCP_server")
copas.waitforexit()
copas.removeserver(server_socket)
end)
copas() copas()
</pre> </pre>
@ -180,6 +184,29 @@ are used to register servers and to execute the main loop of Copas:</p>
truthy.</p> truthy.</p>
</dd> </dd>
<dt><strong><code>copas.exit()</code></strong></dt>
<dd>
<p>Sets a flag that the application is intending to exit. After calling
this function <code>copas.exiting()</code> will be returning <code>true</code>, and
all threads blocked on <code>copas.waitforexit()</code> will be released.</p>
<p>Copas itself will call this function when <code>copas.finished()</code> returns
<code>true</code>.</p>
</dd>
<dt><strong><code>bool = copas.exiting()</code></strong></dt>
<dd>
<p>Returns a flag indicating whether the application is supposed to exit.
Returns <code>false</code> until after <code>copas.exit()</code> has been called,
after which it will start returning <code>true</code>.</p>
<p>Clients should check whether they are to cease their operation and exit. They
can do this by checking this flag, or by registering a task waiting on
<code>copas.waitforexit()</code>. Clients should cancel pending work and close sockets
when an exit is announced, otherwise Copas will not exit.
</p>
</dd>
<dt><strong><code>bool = copas.finished()</code></strong></dt> <dt><strong><code>bool = copas.finished()</code></strong></dt>
<dd> <dd>
<p>Checks whether anything remains to be done.</p> <p>Checks whether anything remains to be done.</p>
@ -210,6 +237,12 @@ are used to register servers and to execute the main loop of Copas:</p>
currently running coroutine.</p> currently running coroutine.</p>
</dd> </dd>
<dt><strong><code>number = copas.gettime()</code></strong></dt>
<dd>
<p>Returns the (fractional) number of seconds since the epoch. This directly
maps to either the LuaSocket or LuaSystem implementation of <code>gettime()</code>.</p>
</dd>
<dt><strong><code>string = copas.gettraceback([msg], [co], [skt])</code></strong></dt> <dt><strong><code>string = copas.gettraceback([msg], [co], [skt])</code></strong></dt>
<dd> <dd>
<p>Creates a traceback (string). Can be used from custom errorhandlers to create <p>Creates a traceback (string). Can be used from custom errorhandlers to create
@ -298,6 +331,15 @@ are used to register servers and to execute the main loop of Copas:</p>
currently running coroutine.</p> currently running coroutine.</p>
</dd> </dd>
<dt><strong><code>copas.waitforexit()</code></strong></dt>
<dd>
<p>This will block the calling coroutine until the <code>copas.exit()</code> function
is called. Clients should check whether they are to cease their operation and exit. They
can do this by waiting on this call, or by checking the <code>copas.exiting()</code> flag.
Clients should cancel pending work and close sockets when an exit is announced, otherwise
Copas will not exit.</p>
</dd>
<dt><strong><code>skt = copas.wrap(skt [, sslparams] )</code></strong></dt> <dt><strong><code>skt = copas.wrap(skt [, sslparams] )</code></strong></dt>
<dd> <dd>
<p>Wraps a LuaSocket socket and returns a Copas socket that implements LuaSocket's API <p>Wraps a LuaSocket socket and returns a Copas socket that implements LuaSocket's API
@ -339,15 +381,17 @@ the clients, and by threads registered with <code>addthread</code> to
exchange data with other services.</p> exchange data with other services.</p>
<dl class="reference"> <dl class="reference">
<dt><strong><code>copas.pause([delay])</code></strong></dt> <dt><strong><code>number = copas.pause([delay])</code></strong></dt>
<dd> <dd>
<p>Pauses the current co-routine. Parameter <code>delay</code> (in seconds) is optional <p>Pauses the current co-routine. Parameter <code>delay</code> (in seconds) is optional
and defaults to 0. If <code>delay &lt= 0</code> then it will pause for 0 seconds.</p> and defaults to 0. If <code>delay &lt= 0</code> then it will pause for 0 seconds.</p>
<p>Returns the time slept (in seconds).</p>
</dd> </dd>
<dt><strong><code>copas.pauseforever()</code></strong></dt> <dt><strong><code>number = copas.pauseforever()</code></strong></dt>
<dd> <dd>
<p>Pauses the current co-routine until explicitly woken by a call to <code>copas.wakeup()</code>.</p> <p>Pauses the current co-routine until explicitly woken by a call to <code>copas.wakeup()</code>.</p>
<p>Returns the time slept (in seconds).</p>
</dd> </dd>
<dt><strong><code>copas.sleep([sleeptime])</code></strong></dt> <dt><strong><code>copas.sleep([sleeptime])</code></strong></dt>
@ -355,11 +399,22 @@ exchange data with other services.</p>
<p><i>Deprecated:</i> use <code>copas.pause</code> and <code>copas.pauseforever</code> instead.</p> <p><i>Deprecated:</i> use <code>copas.pause</code> and <code>copas.pauseforever</code> instead.</p>
</dd> </dd>
<dt><strong><code>copas.wakeup(co)</code></strong></dt> <dt><strong><code>ok, err = copas.wakeup(co)</code></strong></dt>
<dd> <dd>
<p>Immediately wakes up a coroutine that was sleeping or sleeping-forever. <p>Immediately wakes up a coroutine that was sleeping or sleeping-forever.
<code>co</code> is the coroutine to wakeup, see <code>copas.pause()</code> <code>co</code> is the coroutine to wakeup, see <code>copas.pause()</code>
and <code>copas.pauseforever()</code>. Does nothing if the coroutine wasn't sleeping.</p> and <code>copas.pauseforever()</code>.</p>
<p>Returns <code>true</code> on success, or <code>nil + "not sleeping"</code> if the
coroutine wasn't sleeping (eg. it already finished, was already woken up, or was
canceled through <code>copas.removethread()</code>).</p>
</dd>
<dt><strong><code>bool = copas.issleeping(co)</code></strong></dt>
<dd>
<p>Checks whether <code>co</code> is currently sleeping (paused or paused-forever),
without waking it up. Useful for code that queues coroutines and later needs to tell
a still-legitimately-waiting coroutine apart from one that was canceled in the
meantime (eg. through <code>copas.removethread()</code>) while it was queued.</p>
</dd> </dd>
<dt><strong><code>sock:close()</code></strong></dt> <dt><strong><code>sock:close()</code></strong></dt>
@ -383,6 +438,9 @@ exchange data with other services.</p>
<code>copas.handler</code> (for incoming connections) and <code>copas.wrap</code> (for <code>copas.handler</code> (for incoming connections) and <code>copas.wrap</code> (for
outgoing connections), which then ensures that the connection will automatically be outgoing connections), which then ensures that the connection will automatically be
secured when started.</p> secured when started.</p>
<p>Unlike the LuaSec method, this does not return <code>nil + error</code> on failure;
it throws an error instead (see <code>copas.dohandshake()</code>).</p>
</dd> </dd>
<dt><strong><code>sock:receive([pattern [, prefix]])</code></strong></dt> <dt><strong><code>sock:receive([pattern [, prefix]])</code></strong></dt>
@ -390,6 +448,18 @@ exchange data with other services.</p>
<p>Non-blocking equivalent to the LuaSocket method (after <code>copas.wrap</code>). <p>Non-blocking equivalent to the LuaSocket method (after <code>copas.wrap</code>).
Please see <code>sock:receivepartial</code> for differences with LuaSocket, especially Please see <code>sock:receivepartial</code> for differences with LuaSocket, especially
when using the <code>"*a"</code> pattern.</p> when using the <code>"*a"</code> pattern.</p>
<p><strong>Security note:</strong> the default pattern, <code>"*l"</code>, has no
maximum line length. Just like LuaSocket and Lua's own file-io, it will keep
buffering data until a newline, end-of-file, or a socket error is encountered.
Because the Copas timeout resets on every chunk received (see the timeout note
above), a peer that trickles in bytes without ever sending a newline is not timed
out either, so the buffer keeps growing for as long as the peer stays connected.
Do not use the default <code>receive()</code>/<code>"*l"</code> pattern directly on
data from an untrusted or remote peer unless the protocol itself guarantees a
bounded line length. For untrusted input, read a bounded/known size (a numeric
pattern), or use <code>receivepartial</code> and enforce your own cumulative size
limit, closing the connection when it is exceeded.</p>
</dd> </dd>
<dt><strong><code>sock:receivefrom([size])</code></strong></dt> <dt><strong><code>sock:receivefrom([size])</code></strong></dt>
@ -467,6 +537,58 @@ exchange data with other services.</p>
secured when started.</p> secured when started.</p>
</dd> </dd>
<dt><strong><code>future:cancel()</code></strong></dt>
<dd>
<p>Cancels a pending task. Returns <code>true</code> if the task was successfully cancelled,
or <code>false</code> if the task had already completed.</p>
<p>Any coroutines blocked on <code>future:get()</code> will be released immediately and
return <code>false + "cancelled"</code>. Subsequent calls to <code>future:try()</code>
will return <code>future.ERROR, "cancelled"</code>.</p>
</dd>
<dt><strong><code>ok [, results...] = future:get()</code></strong></dt>
<dd>
<p>Waits for the task to complete and returns its results. If the task has already
completed, returns immediately. Can be called multiple times; subsequent calls return
the cached result without blocking.</p>
<p>Returns like <code>pcall</code>: on success returns <code>true</code> followed by
the task's return values; on failure returns <code>false</code> followed by the error message.</p>
<p>The future is also callable: <code>future()</code> is equivalent to <code>future:get()</code>.</p>
</dd>
<dt><strong><code>status [, results...] = future:try()</code></strong></dt>
<dd>
<p>Non-blocking check on the future's status. Returns one of three outcomes:</p>
<ul>
<li><code>future.PENDING</code> (<code>false</code>) — the task has not yet completed.</li>
<li><code>future.SUCCESS</code> (<code>true</code>), results... — the task completed successfully, followed by its return values.</li>
<li><code>future.ERROR</code> (<code>"error"</code>), errmsg — the task failed or was cancelled, followed by the error message.</li>
</ul>
<p><code>future.PENDING</code> is the only falsy value, so a simple truthiness check indicates
whether the task is done (whether successfully or with an error).</p>
</dd>
<dt><strong><code>future.ERROR</code>, <code>future.PENDING</code>, <code>future.SUCCESS</code></strong></dt>
<dd>
<p>Status constants for use with <code>future:try()</code>. Their values are
<code>"error"</code>, <code>false</code>, and <code>true</code> respectively.</p>
</dd>
<dt><strong><code>future = copas.future.addnamedthread(name, func, ...)</code></strong></dt>
<dd>
<p>Equivalent to <code>copas.addnamedthread</code> but returns a future
instead of the coroutine.</p>
</dd>
<dt><strong><code>future = copas.future.addthread(func, ...)</code></strong></dt>
<dd>
<p>Equivalent to <code>copas.addthread</code> but returns a future
instead of the coroutine.</p>
</dd>
<dt><strong><code>lock:destroy()</code></strong></dt> <dt><strong><code>lock:destroy()</code></strong></dt>
<dd> <dd>
<p>Will destroy the lock and release all waiting threads. The result for those <p>Will destroy the lock and release all waiting threads. The result for those
@ -530,6 +652,11 @@ exchange data with other services.</p>
<p>Finishes a queue. Calls <code>queue:stop()</code> and then waits for the queue to run <p>Finishes a queue. Calls <code>queue:stop()</code> and then waits for the queue to run
empty (and be destroyed) before returning.</p> empty (and be destroyed) before returning.</p>
<p>When using "workers" via <code>queue:add_worker()</code>
then this method will return after the worker has finished processing the popped item.
When using your own threads and calling <code>queue:pop()</code>, then this method will
return after the last item has been popped, but not necessarily also processed. </p>
<p>The <code>timeout</code> defaults to 10 seconds <p>The <code>timeout</code> defaults to 10 seconds
(the default timeout value for a lock), <code>math.huge</code> can be used to wait forever.</p> (the default timeout value for a lock), <code>math.huge</code> can be used to wait forever.</p>
@ -613,17 +740,21 @@ exchange data with other services.</p>
<dd> <dd>
<p>Returns the total number of resources requested by all currently waiting threads minus <p>Returns the total number of resources requested by all currently waiting threads minus
the available resources. Such that <code>sempahore:give(semaphore:get_wait())</code> will the available resources. Such that <code>sempahore:give(semaphore:get_wait())</code> will
release all waiting threads and leave the semaphore with 0 resources. If there are no waiting threads release all waiting threads and leave the semaphore with 0 resources (provided they are
then the result will be 0, and the number of resources in the semaphore will be greater than or equal to 0.</p> within the <code>max</code> limits). If there are no waiting threads then the result will
be 0, and the number of resources in the semaphore will be greater than or equal to 0.</p>
</dd> </dd>
<dt><strong><code>semaphore:give([given])</code></strong></dt> <dt><strong><code>semaphore:give([given])</code></strong></dt>
<dd> <dd>
<p>Gives resources to the semaphore. Parameter <code>given</code> is the number of resources <p>Gives resources to the semaphore. Parameter <code>given</code> is the number of resources
given to the semaphore, if omitted it defaults to 1.</p> given to the semaphore, if omitted it defaults to 1. <code>given</code> must be a finite
number greater than or equal to 0, otherwise an error is thrown.</p>
<p>If the total resources in the semaphore exceed the maximum, then it will be capped at the <p>If the total resources in the semaphore exceed the maximum, then it will be capped at the
maximum. In that case the result will be <code>nil + "too many"</code>.</p> maximum. In that case the result will be <code>nil + "too many"</code>. To release every
currently waiting thread regardless of <code>max</code>, use <code>semaphore:release_all()</code>
instead.</p>
</dd> </dd>
<dt><strong><code>copas.semaphore.new(max, [start], [timeout])</code></strong></dt> <dt><strong><code>copas.semaphore.new(max, [start], [timeout])</code></strong></dt>
@ -631,16 +762,26 @@ exchange data with other services.</p>
<p>Creates and returns a new semaphore (fifo).</p> <p>Creates and returns a new semaphore (fifo).</p>
<p><code>max</code> specifies the maximum number of resources the semaphore can hold. <p><code>max</code> specifies the maximum number of resources the semaphore can hold.
Use <code>math.huge</code> to not limit the number of resources.
The optional <code>start</code> parameter (default 0) specifies the number of resources upon creation.</p> The optional <code>start</code> parameter (default 0) specifies the number of resources upon creation.</p>
<p>The <code>timeout</code> specifies the default timeout for the semaphore in <p>The <code>timeout</code> specifies the default timeout for the semaphore in
seconds, and defaults to 10 (<code>math.huge</code> can be used to wait forever).</p> seconds, and defaults to 10 (<code>math.huge</code> can be used to wait forever).</p>
</dd> </dd>
<dt><strong><code>semaphore:release_all()</code></strong></dt>
<dd>
<p>Releases every currently queued waiter, regardless of <code>max</code>. Unlike
<code>semaphore:give(semaphore:get_wait())</code>, which is capped at <code>max</code> and can
strand waiters beyond it, this feeds the required amount into <code>give()</code> in
<code>max</code>-sized chunks until nobody is left waiting.</p>
</dd>
<dt><strong><code>semaphore:take([requested], [timeout])</code></strong></dt> <dt><strong><code>semaphore:take([requested], [timeout])</code></strong></dt>
<dd> <dd>
<p>Takes resources from the semaphore. Parameter <code>requested</code> is the number of resources <p>Takes resources from the semaphore. Parameter <code>requested</code> is the number of resources
requested from the semaphore, if omitted it defaults to 1.</p> requested from the semaphore, if omitted it defaults to 1. <code>requested</code> must be a
number greater than or equal to 1, otherwise an error is thrown.</p>
<p>If not enough resources are available it <p>If not enough resources are available it
will yield and wait until enough resources are available, or a timeout occurs. The exception is when will yield and wait until enough resources are available, or a timeout occurs. The exception is when
@ -684,7 +825,8 @@ exchange data with other services.</p>
<dt><strong><code>timer:cancel()</code></strong></dt> <dt><strong><code>timer:cancel()</code></strong></dt>
<dd> <dd>
<p>Will cancel the timer.</p> <p>Will cancel the timer. If the callback is currently in progress then the callback won't be
cancelled, just new invocations will be prevented.</p>
</dd> </dd>
</dl> </dl>
@ -800,6 +942,11 @@ servers.</p>
<p><strong>Note:</strong> for UDP sockets the <code>size</code> parameter is NOT <p><strong>Note:</strong> for UDP sockets the <code>size</code> parameter is NOT
optional. For the wrapped function <code>socket:receive([size])</code> it is optional. For the wrapped function <code>socket:receive([size])</code> it is
optional again.</p> optional again.</p>
<p><strong>Security note:</strong> see the note under <code>sock:receive</code>
above regarding the default <code>"*l"</code> pattern having no maximum length,
which makes it unsafe to use directly on untrusted/remote input without an
application-enforced size limit.</p>
</dd> </dd>
<dt><strong><code>copas.receivepartial(skt [, pattern [, prefix]])</code></strong></dt> <dt><strong><code>copas.receivepartial(skt [, pattern [, prefix]])</code></strong></dt>
@ -961,6 +1108,39 @@ print("result: ", result)
<dl class="reference"> <dl class="reference">
<dt><strong><code>copas.cancelall()</code></strong></dt>
<dd>
<p>This will clear pending work (client sockets, server sockets, timers) to force the copas
loop to exit immediately. This is useful for testing and debugging.
</p>
<p>Here's an example which, upon an error, cancels all pending work to ensure the test
ends immediately instead of waiting for the long sleep to end:
</p>
<pre class="example">
local failure
copas(function()
copas.seterrorhandler(function(msg, co, skt)
failure = copas.gettraceback(msg, co, skt)
copas.cancelall() -- cancel all pending work to force exit
end, true) -- true -&gt; set as default errorhandler
copas.addthread(function()
print("sleeping")
copas.pause(3600) -- would block copas loop from exiting
end)
copas.addthread(function()
copas.pause(1)
error("boom!!") -- invokes the erorrhandler above
end)
end)
assert(not failure, failure)
</pre>
</dd>
<dt><strong><code>copas.debug.start([logger] [, core])</code></strong></dt> <dt><strong><code>copas.debug.start([logger] [, core])</code></strong></dt>
<dd> <dd>
<p>This will internally replace coroutine handler functions to provide log output to <p>This will internally replace coroutine handler functions to provide log output to

View file

@ -0,0 +1,57 @@
local package_name = "copas"
local package_version = "4.10.0"
local rockspec_revision = "1"
local github_account_name = "lunarmodules"
local github_repo_name = package_name
package = package_name
version = package_version.."-"..rockspec_revision
source = {
url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "cvs") and "master" or nil,
tag = (package_version ~= "cvs") and package_version or nil,
}
description = {
summary = "Coroutine Oriented Portable Asynchronous Services",
detailed = [[
Copas is a dispatcher based on coroutines that can be used by
TCP/IP servers. It uses LuaSocket as the interface with the
TCP/IP stack. A server registered with Copas should provide a
handler for requests and use Copas socket functions to send
the response. Copas loops through requests and invokes the
corresponding handlers. For a full implementation of a Copas
HTTP server you can refer to Xavante as an example.
]],
license = "MIT/X11",
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
}
dependencies = {
"lua >= 5.1, < 5.6",
"luasocket ~> 3",
"coxpcall >= 1.14",
"binaryheap >= 0.4",
"timerwheel ~> 1",
}
build = {
type = "builtin",
install = {
bin = {
copas = "bin/copas.lua",
}
},
modules = {
["copas"] = "src/copas.lua",
["copas.http"] = "src/copas/http.lua",
["copas.ftp"] = "src/copas/ftp.lua",
["copas.smtp"] = "src/copas/smtp.lua",
["copas.timer"] = "src/copas/timer.lua",
["copas.lock"] = "src/copas/lock.lua",
["copas.semaphore"] = "src/copas/semaphore.lua",
["copas.future"] = "src/copas/future.lua",
["copas.queue"] = "src/copas/queue.lua",
},
copy_directories = {
"docs",
},
}

View file

@ -0,0 +1,57 @@
local package_name = "copas"
local package_version = "4.11.0"
local rockspec_revision = "1"
local github_account_name = "lunarmodules"
local github_repo_name = package_name
package = package_name
version = package_version.."-"..rockspec_revision
source = {
url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "cvs") and "master" or nil,
tag = (package_version ~= "cvs") and package_version or nil,
}
description = {
summary = "Coroutine Oriented Portable Asynchronous Services",
detailed = [[
Copas is a dispatcher based on coroutines that can be used by
TCP/IP servers. It uses LuaSocket as the interface with the
TCP/IP stack. A server registered with Copas should provide a
handler for requests and use Copas socket functions to send
the response. Copas loops through requests and invokes the
corresponding handlers. For a full implementation of a Copas
HTTP server you can refer to Xavante as an example.
]],
license = "MIT/X11",
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
}
dependencies = {
"lua >= 5.1, < 5.6",
"luasocket ~> 3",
"coxpcall >= 1.14",
"binaryheap >= 0.4",
"timerwheel ~> 1",
}
build = {
type = "builtin",
install = {
bin = {
copas = "bin/copas.lua",
}
},
modules = {
["copas"] = "src/copas.lua",
["copas.http"] = "src/copas/http.lua",
["copas.ftp"] = "src/copas/ftp.lua",
["copas.smtp"] = "src/copas/smtp.lua",
["copas.timer"] = "src/copas/timer.lua",
["copas.lock"] = "src/copas/lock.lua",
["copas.semaphore"] = "src/copas/semaphore.lua",
["copas.future"] = "src/copas/future.lua",
["copas.queue"] = "src/copas/queue.lua",
},
copy_directories = {
"docs",
},
}

View file

@ -0,0 +1,57 @@
local package_name = "copas"
local package_version = "4.12.0"
local rockspec_revision = "1"
local github_account_name = "lunarmodules"
local github_repo_name = package_name
package = package_name
version = package_version.."-"..rockspec_revision
source = {
url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "cvs") and "master" or nil,
tag = (package_version ~= "cvs") and package_version or nil,
}
description = {
summary = "Coroutine Oriented Portable Asynchronous Services",
detailed = [[
Copas is a dispatcher based on coroutines that can be used by
TCP/IP servers. It uses LuaSocket as the interface with the
TCP/IP stack. A server registered with Copas should provide a
handler for requests and use Copas socket functions to send
the response. Copas loops through requests and invokes the
corresponding handlers. For a full implementation of a Copas
HTTP server you can refer to Xavante as an example.
]],
license = "MIT/X11",
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
}
dependencies = {
"lua >= 5.1, < 5.6",
"luasocket ~> 3",
"coxpcall >= 1.14",
"binaryheap >= 0.4",
"timerwheel ~> 1",
}
build = {
type = "builtin",
install = {
bin = {
copas = "bin/copas.lua",
}
},
modules = {
["copas"] = "src/copas.lua",
["copas.http"] = "src/copas/http.lua",
["copas.ftp"] = "src/copas/ftp.lua",
["copas.smtp"] = "src/copas/smtp.lua",
["copas.timer"] = "src/copas/timer.lua",
["copas.lock"] = "src/copas/lock.lua",
["copas.semaphore"] = "src/copas/semaphore.lua",
["copas.future"] = "src/copas/future.lua",
["copas.queue"] = "src/copas/queue.lua",
},
copy_directories = {
"docs",
},
}

View file

@ -0,0 +1,56 @@
local package_name = "copas"
local package_version = "4.7.1"
local rockspec_revision = "1"
local github_account_name = "lunarmodules"
local github_repo_name = package_name
package = package_name
version = package_version.."-"..rockspec_revision
source = {
url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "cvs") and "master" or nil,
tag = (package_version ~= "cvs") and package_version or nil,
}
description = {
summary = "Coroutine Oriented Portable Asynchronous Services",
detailed = [[
Copas is a dispatcher based on coroutines that can be used by
TCP/IP servers. It uses LuaSocket as the interface with the
TCP/IP stack. A server registered with Copas should provide a
handler for requests and use Copas socket functions to send
the response. Copas loops through requests and invokes the
corresponding handlers. For a full implementation of a Copas
HTTP server you can refer to Xavante as an example.
]],
license = "MIT/X11",
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
}
dependencies = {
"lua >= 5.1, < 5.5",
"luasocket >= 2.1, <= 3.0rc1-2",
"coxpcall >= 1.14",
"binaryheap >= 0.4",
"timerwheel ~> 1",
}
build = {
type = "builtin",
install = {
bin = {
copas = "bin/copas.lua",
}
},
modules = {
["copas"] = "src/copas.lua",
["copas.http"] = "src/copas/http.lua",
["copas.ftp"] = "src/copas/ftp.lua",
["copas.smtp"] = "src/copas/smtp.lua",
["copas.timer"] = "src/copas/timer.lua",
["copas.lock"] = "src/copas/lock.lua",
["copas.semaphore"] = "src/copas/semaphore.lua",
["copas.queue"] = "src/copas/queue.lua",
},
copy_directories = {
"docs",
},
}

View file

@ -0,0 +1,56 @@
local package_name = "copas"
local package_version = "4.8.0"
local rockspec_revision = "1"
local github_account_name = "lunarmodules"
local github_repo_name = package_name
package = package_name
version = package_version.."-"..rockspec_revision
source = {
url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "cvs") and "master" or nil,
tag = (package_version ~= "cvs") and package_version or nil,
}
description = {
summary = "Coroutine Oriented Portable Asynchronous Services",
detailed = [[
Copas is a dispatcher based on coroutines that can be used by
TCP/IP servers. It uses LuaSocket as the interface with the
TCP/IP stack. A server registered with Copas should provide a
handler for requests and use Copas socket functions to send
the response. Copas loops through requests and invokes the
corresponding handlers. For a full implementation of a Copas
HTTP server you can refer to Xavante as an example.
]],
license = "MIT/X11",
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
}
dependencies = {
"lua >= 5.1, < 5.5",
"luasocket >= 2.1, <= 3.0rc1-2",
"coxpcall >= 1.14",
"binaryheap >= 0.4",
"timerwheel ~> 1",
}
build = {
type = "builtin",
install = {
bin = {
copas = "bin/copas.lua",
}
},
modules = {
["copas"] = "src/copas.lua",
["copas.http"] = "src/copas/http.lua",
["copas.ftp"] = "src/copas/ftp.lua",
["copas.smtp"] = "src/copas/smtp.lua",
["copas.timer"] = "src/copas/timer.lua",
["copas.lock"] = "src/copas/lock.lua",
["copas.semaphore"] = "src/copas/semaphore.lua",
["copas.queue"] = "src/copas/queue.lua",
},
copy_directories = {
"docs",
},
}

View file

@ -0,0 +1,56 @@
local package_name = "copas"
local package_version = "4.8.0"
local rockspec_revision = "2"
local github_account_name = "lunarmodules"
local github_repo_name = package_name
package = package_name
version = package_version.."-"..rockspec_revision
source = {
url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "cvs") and "master" or nil,
tag = (package_version ~= "cvs") and package_version or nil,
}
description = {
summary = "Coroutine Oriented Portable Asynchronous Services",
detailed = [[
Copas is a dispatcher based on coroutines that can be used by
TCP/IP servers. It uses LuaSocket as the interface with the
TCP/IP stack. A server registered with Copas should provide a
handler for requests and use Copas socket functions to send
the response. Copas loops through requests and invokes the
corresponding handlers. For a full implementation of a Copas
HTTP server you can refer to Xavante as an example.
]],
license = "MIT/X11",
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
}
dependencies = {
"lua >= 5.1, < 5.6",
"luasocket >= 2.1, <= 3.0rc1-2",
"coxpcall >= 1.14",
"binaryheap >= 0.4",
"timerwheel ~> 1",
}
build = {
type = "builtin",
install = {
bin = {
copas = "bin/copas.lua",
}
},
modules = {
["copas"] = "src/copas.lua",
["copas.http"] = "src/copas/http.lua",
["copas.ftp"] = "src/copas/ftp.lua",
["copas.smtp"] = "src/copas/smtp.lua",
["copas.timer"] = "src/copas/timer.lua",
["copas.lock"] = "src/copas/lock.lua",
["copas.semaphore"] = "src/copas/semaphore.lua",
["copas.queue"] = "src/copas/queue.lua",
},
copy_directories = {
"docs",
},
}

View file

@ -0,0 +1,56 @@
local package_name = "copas"
local package_version = "4.9.0"
local rockspec_revision = "1"
local github_account_name = "lunarmodules"
local github_repo_name = package_name
package = package_name
version = package_version.."-"..rockspec_revision
source = {
url = "git+https://github.com/"..github_account_name.."/"..github_repo_name..".git",
branch = (package_version == "cvs") and "master" or nil,
tag = (package_version ~= "cvs") and package_version or nil,
}
description = {
summary = "Coroutine Oriented Portable Asynchronous Services",
detailed = [[
Copas is a dispatcher based on coroutines that can be used by
TCP/IP servers. It uses LuaSocket as the interface with the
TCP/IP stack. A server registered with Copas should provide a
handler for requests and use Copas socket functions to send
the response. Copas loops through requests and invokes the
corresponding handlers. For a full implementation of a Copas
HTTP server you can refer to Xavante as an example.
]],
license = "MIT/X11",
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
}
dependencies = {
"lua >= 5.1, < 5.6",
"luasocket >= 2.1, <= 3.0rc1-2",
"coxpcall >= 1.14",
"binaryheap >= 0.4",
"timerwheel ~> 1",
}
build = {
type = "builtin",
install = {
bin = {
copas = "bin/copas.lua",
}
},
modules = {
["copas"] = "src/copas.lua",
["copas.http"] = "src/copas/http.lua",
["copas.ftp"] = "src/copas/ftp.lua",
["copas.smtp"] = "src/copas/smtp.lua",
["copas.timer"] = "src/copas/timer.lua",
["copas.lock"] = "src/copas/lock.lua",
["copas.semaphore"] = "src/copas/semaphore.lua",
["copas.queue"] = "src/copas/queue.lua",
},
copy_directories = {
"docs",
},
}

View file

@ -8,7 +8,7 @@
-- Contributors: Diego Nehab, Mike Pall, David Burgess, Leonardo Godinho, -- Contributors: Diego Nehab, Mike Pall, David Burgess, Leonardo Godinho,
-- Thomas Harning Jr., and Gary NG -- Thomas Harning Jr., and Gary NG
-- --
-- Copyright 2005-2013 - Kepler Project (www.keplerproject.org), 2015-2023 Thijs Schreijer -- Copyright 2005-2013 - Kepler Project (www.keplerproject.org), 2015-2026 Thijs Schreijer
-- --
-- $Id: copas.lua,v 1.37 2009/04/07 22:09:52 carregal Exp $ -- $Id: copas.lua,v 1.37 2009/04/07 22:09:52 carregal Exp $
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -20,14 +20,32 @@ if package.loaded["copas.http"] and (_VERSION=="Lua 5.1") then -- obsolete:
error("you must require copas before require'ing copas.http") error("you must require copas before require'ing copas.http")
end end
-- load either LuaSocket, or LuaSystem
-- note: with luasocket we don't use 'sleep' but 'select' with no sockets
local socket, system do
if pcall(require, "socket") then
-- found LuaSocket
socket = require "socket"
end
-- try LuaSystem as fallback
if pcall(require, "system") then
system = require "system"
end
if not (socket or system) then
error("Neither LuaSocket nor LuaSystem found, Copas requires at least one of them")
end
end
local socket = require "socket"
local binaryheap = require "binaryheap" local binaryheap = require "binaryheap"
local gettime = socket.gettime local gettime = (socket or system).gettime
local block_sleep = (socket or system).sleep
local ssl -- only loaded upon demand local ssl -- only loaded upon demand
local core_timer_thread
local WATCH_DOG_TIMEOUT = 120 local WATCH_DOG_TIMEOUT = 120
local UDP_DATAGRAM_MAX = socket._DATAGRAMSIZE or 8192 local UDP_DATAGRAM_MAX = (socket or {})._DATAGRAMSIZE or 8192
local TIMEOUT_PRECISION = 0.1 -- 100ms local TIMEOUT_PRECISION = 0.1 -- 100ms
local fnil = function() end local fnil = function() end
@ -52,7 +70,7 @@ if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compat
end end
do if socket then
-- Redefines LuaSocket functions with coroutine safe versions (pure Lua) -- Redefines LuaSocket functions with coroutine safe versions (pure Lua)
-- (this allows the use of socket.http from within copas) -- (this allows the use of socket.http from within copas)
local err_mt = { local err_mt = {
@ -94,7 +112,7 @@ end
-- Setup the Copas meta table to auto-load submodules and define a default method -- Setup the Copas meta table to auto-load submodules and define a default method
local copas do local copas do
local submodules = { "ftp", "http", "lock", "queue", "semaphore", "smtp", "timer" } local submodules = { "ftp", "future", "http", "lock", "queue", "semaphore", "smtp", "timer" }
for i, key in ipairs(submodules) do for i, key in ipairs(submodules) do
submodules[key] = true submodules[key] = true
submodules[i] = nil submodules[i] = nil
@ -116,9 +134,9 @@ end
-- Meta information is public even if beginning with an "_" -- Meta information is public even if beginning with an "_"
copas._COPYRIGHT = "Copyright (C) 2005-2013 Kepler Project, 2015-2023 Thijs Schreijer" copas._COPYRIGHT = "Copyright (C) 2005-2013 Kepler Project, 2015-2026 Thijs Schreijer"
copas._DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services" copas._DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services"
copas._VERSION = "Copas 4.7.0" copas._VERSION = "Copas 4.12.0"
-- Close the socket associated with the current connection after the handler finishes -- Close the socket associated with the current connection after the handler finishes
copas.autoclose = true copas.autoclose = true
@ -126,6 +144,8 @@ copas.autoclose = true
-- indicator for the loop running -- indicator for the loop running
copas.running = false copas.running = false
-- gettime method from either LuaSocket or LuaSystem: time in (fractional) seconds, since epoch.
copas.gettime = gettime
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Object names, to track names of thread/coroutines and sockets -- Object names, to track names of thread/coroutines and sockets
@ -143,7 +163,9 @@ local object_names = setmetatable({}, {
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Simple set implementation -- Simple set implementation
-- adds a FIFO queue for each socket in the set -- tracks at the waiting coroutine per socket in the set.
-- Sets exist for reading and writing. So each socket can have a reader and writer
-- simultaneously, but only one reader and one writer at a time.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local function newsocketset() local function newsocketset()
@ -180,26 +202,42 @@ local function newsocketset()
end end
do -- queues implementation do -- single-waiter implementation
local fifo_queues = setmetatable({},{ -- the set instance (read or write) determines what operation the coroutine is waiting for
__mode = "k", -- auto collect queue if socket is gone local waiters = setmetatable({}, { __mode = "k" }) -- coroutine by socket
__index = function(self, skt) -- auto create fifo queue if not found
local newfifo = {}
self[skt] = newfifo
return newfifo
end,
})
-- pushes an item in the fifo queue for the socket. -- Registers the coroutine as the socket's waiter, to be resumed
function set:push(skt, itm) -- once the socket becomes ready.
local queue = fifo_queues[skt] -- @return true on success, or nil + error message if another coroutine
queue[#queue + 1] = itm -- is already waiting on this socket read/write.
function set:claim(skt, co)
if waiters[skt] then
return nil, "Operation already in progress"
end
waiters[skt] = co
return true
end end
-- pops an item from the fifo queue for the socket -- Clears and returns the coroutine waiting on the socket read/write operation, or nil if
function set:pop(skt) -- none is waiting.
local queue = fifo_queues[skt] function set:release(skt)
return table.remove(queue, 1) local co = waiters[skt]
waiters[skt] = nil
return co
end
-- Drops the socket from the set and discards its waiting coroutine (if
-- any), without resuming it. Only call this when the socket is being
-- discarded outright and no waiter is expected to be woken through the
-- normal readiness path (e.g. the socket object itself is being thrown
-- away). Do not use this as a general substitute for `remove`: if a
-- waiter still needs to observe the outcome (for example a coroutine
-- waiting on a socket that is being closed, which is resumed via the
-- normal tick()/release() path with a "closed" result), purging here
-- would silently drop it instead.
function set:purge(skt)
waiters[skt] = nil
self:remove(skt)
end end
end end
@ -281,11 +319,21 @@ local _sleeping = {} do
if lethargy[co] then if lethargy[co] then
lethargy[co] = nil lethargy[co] = nil
_resumable:push(co) _resumable:push(co)
return return true
end end
if heap:remove(co) then if heap:remove(co) then
_resumable:push(co) _resumable:push(co)
return true
end end
return nil, "not sleeping"
end
-- non-destructive check; unlike wakeup/cancel it doesn't remove 'co'
function _sleeping:issleeping(co)
if lethargy[co] then
return true
end
return heap:valueByPayload(co) ~= nil
end end
function _sleeping:cancel(co) function _sleeping:cancel(co)
@ -293,6 +341,12 @@ local _sleeping = {} do
heap:remove(co) heap:remove(co)
end end
function _sleeping:cancelall()
while heap:size() > 0 do heap:pop() end
heap:insert(gettime() + TIMEOUT_PRECISION, core_timer_thread)
-- lethargy is weak; copas's idle GC sweeps will clean it within a few steps
end
-- @param tos number of timeouts running -- @param tos number of timeouts running
function _sleeping:done(tos) function _sleeping:done(tos)
-- return true if we have nothing more to do -- return true if we have nothing more to do
@ -374,6 +428,7 @@ local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
local timeout_flags = setmetatable({}, { __mode = "k" }) -- true if timedout, by coroutine local timeout_flags = setmetatable({}, { __mode = "k" }) -- true if timedout, by coroutine
-- The callback called when a socket timeout occurs.
local function socket_callback(co) local function socket_callback(co)
local skt = socket_register[co] local skt = socket_register[co]
local queue = operation_register[co] local queue = operation_register[co]
@ -382,10 +437,13 @@ local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
timeout_flags[co] = true timeout_flags[co] = true
_resumable:push(co) _resumable:push(co)
-- clear the socket from the current queue -- release our claim on the socket and stop watching it; the timer, not
-- the readiness path, is resuming `co`, so nothing else will do this
if queue == "read" then if queue == "read" then
_reading:release(skt)
_reading:remove(skt) _reading:remove(skt)
elseif queue == "write" then elseif queue == "write" then
_writing:release(skt)
_writing:remove(skt) _writing:remove(skt)
else else
error("bad queue name; expected 'read'/'write', got: "..tostring(queue)) error("bad queue name; expected 'read'/'write', got: "..tostring(queue))
@ -395,10 +453,11 @@ local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
-- Sets a socket timeout. -- Sets a socket timeout.
-- Calling it as `sto_timeout()` will cancel the timeout. -- Calling it as `sto_timeout()` will cancel the timeout.
-- @param queue (string) the queue the socket is currently in, must be either "read" or "write" -- @param skt (socket) the socket on which to operate, use 'nil' to cancel the current timeout
-- @param skt (socket) the socket on which to operate -- @param queue (string) the queue the socket is currently in: "read" or "write"
-- @param use_connect_to (bool) timeout to use is determined based on queue (read/write) or if this -- @param use_connect_to (bool) if truthy, use the connect timeout instead of the
-- is truthy, it is the connect timeout. -- read/write timeout implied by queue. Needed because connect also uses the "write"
-- queue, so the queue value alone cannot distinguish connect from send operations.
-- @return true -- @return true
function sto_timeout(skt, queue, use_connect_to) function sto_timeout(skt, queue, use_connect_to)
local co = coroutine_running() local co = coroutine_running()
@ -438,6 +497,14 @@ local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
function sto_error(err) function sto_error(err)
return useSocketTimeoutErrors[coroutine_running()] and err or "timeout" return useSocketTimeoutErrors[coroutine_running()] and err or "timeout"
end end
-- only in case of testing export some internals
if _G._TEST then
copas._socket_register = socket_register
copas._operation_register = operation_register
copas._timeout_flags = timeout_flags
end
end end
@ -446,6 +513,21 @@ end
-- Coroutine based socket I/O functions. -- Coroutine based socket I/O functions.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Claims the socket for the current coroutine and yields to wait for it to
-- become ready, returning `true` once resumed.
-- @return nil + error if another coroutine is already waiting on this
-- socket (a bug in the caller, not a Copas failure).
local function wait_on(queue, skt)
local claimed, err = queue:claim(skt, coroutine_running())
if not claimed then
return nil, err
end
queue:insert(skt)
coroutine_yield(skt, queue)
return true
end
-- Returns "tcp"" for plain TCP and "ssl" for ssl-wrapped sockets, so truthy -- Returns "tcp"" for plain TCP and "ssl" for ssl-wrapped sockets, so truthy
-- for tcp based, and falsy for udp based. -- for tcp based, and falsy for udp based.
local isTCP do local isTCP do
@ -459,13 +541,22 @@ local isTCP do
end end
end end
-- POSIX: a 0-byte read on a stream socket (TCP/SSL) is a no-op and must
-- return immediately without waiting on `select`. UDP is different: a
-- 0-byte datagram is a distinct payload, so it must still wait.
-- See https://github.com/lunarmodules/copas/issues/223
local function is_zero_byte_tcp_read(client, pattern)
return pattern == 0 and isTCP(client)
end
function copas.close(skt, ...) function copas.close(skt, ...)
_closed[#_closed+1] = skt _closed[#_closed+1] = skt
return skt:close(...) return skt:close(...)
end end
-- nil or negative is indefinitly -- nil or negative is indefinitly
function copas.settimeout(skt, timeout) function copas.settimeout(skt, timeout)
timeout = timeout or -1 timeout = timeout or -1
@ -476,6 +567,7 @@ function copas.settimeout(skt, timeout)
return copas.settimeouts(skt, timeout, timeout, timeout) return copas.settimeouts(skt, timeout, timeout, timeout)
end end
-- negative is indefinitly, nil means do not change -- negative is indefinitly, nil means do not change
function copas.settimeouts(skt, connect, send, read) function copas.settimeouts(skt, connect, send, read)
@ -519,7 +611,18 @@ end
-- UDP: a UDP socket expects a second argument to be a number, so it MUST -- UDP: a UDP socket expects a second argument to be a number, so it MUST
-- be provided as the 'pattern' below defaults to a string. Will throw a -- be provided as the 'pattern' below defaults to a string. Will throw a
-- 'bad argument' error if omitted. -- 'bad argument' error if omitted.
-- SECURITY: the default pattern "*l" has no maximum length, matching LuaSocket
-- and Lua file-io semantics. It buffers until a newline/EOF/error, and the
-- per-operation timeout resets on every partial receive, so it does not bound
-- the accumulated size either. Do not use the default line-read directly on
-- untrusted/remote input without an application-enforced size limit; use a
-- numeric (sized) pattern or receivepartial with your own cumulative cap instead.
function copas.receive(client, pattern, part) function copas.receive(client, pattern, part)
if is_zero_byte_tcp_read(client, pattern) then
copas.pause() -- yield so a tight receive(0) loop can't starve other coroutines
return part or "", nil, nil
end
local s, err local s, err
pattern = pattern or "*l" pattern = pattern or "*l"
local current_log = _reading_log local current_log = _reading_log
@ -545,19 +648,28 @@ function copas.receive(client, pattern, part)
elseif sto_timed_out() then elseif sto_timed_out() then
current_log[client] = nil current_log[client] = nil
sto_timeout()
return nil, sto_error(err), part return nil, sto_error(err), part
end end
local queue, direction
if err == "wantwrite" then -- wantwrite may be returned during SSL renegotiations if err == "wantwrite" then -- wantwrite may be returned during SSL renegotiations
queue = _writing
direction = "write"
current_log = _writing_log current_log = _writing_log
current_log[client] = gettime()
sto_change_queue("write")
coroutine_yield(client, _writing)
else else
queue = _reading
direction = "read"
current_log = _reading_log current_log = _reading_log
current_log[client] = gettime() end
sto_change_queue("read")
coroutine_yield(client, _reading) current_log[client] = gettime()
sto_change_queue(direction)
local ok, werr = wait_on(queue, client)
if not ok then
current_log[client] = nil
sto_timeout()
return nil, werr, part
end end
until false until false
end end
@ -589,17 +701,28 @@ function copas.receivefrom(client, size)
elseif sto_timed_out() then elseif sto_timed_out() then
_reading_log[client] = nil _reading_log[client] = nil
sto_timeout()
return nil, sto_error(err), port return nil, sto_error(err), port
end end
_reading_log[client] = gettime() _reading_log[client] = gettime()
coroutine_yield(client, _reading) local ok, werr = wait_on(_reading, client)
if not ok then
_reading_log[client] = nil
sto_timeout()
return nil, werr, port
end
until false until false
end end
-- same as above but with special treatment when reading chunks, -- same as above but with special treatment when reading chunks,
-- unblocks on any data received. -- unblocks on any data received.
function copas.receivepartial(client, pattern, part) function copas.receivepartial(client, pattern, part)
if is_zero_byte_tcp_read(client, pattern) then
copas.pause() -- yield so a tight receive(0) loop can't starve other coroutines
return part or "", nil, nil
end
local s, err local s, err
pattern = pattern or "*l" pattern = pattern or "*l"
local orig_size = #(part or "") local orig_size = #(part or "")
@ -626,19 +749,28 @@ function copas.receivepartial(client, pattern, part)
elseif sto_timed_out() then elseif sto_timed_out() then
current_log[client] = nil current_log[client] = nil
sto_timeout()
return nil, sto_error(err), part return nil, sto_error(err), part
end end
local queue, direction
if err == "wantwrite" then if err == "wantwrite" then
queue = _writing
direction = "write"
current_log = _writing_log current_log = _writing_log
current_log[client] = gettime()
sto_change_queue("write")
coroutine_yield(client, _writing)
else else
queue = _reading
direction = "read"
current_log = _reading_log current_log = _reading_log
current_log[client] = gettime() end
sto_change_queue("read")
coroutine_yield(client, _reading) current_log[client] = gettime()
sto_change_queue(direction)
local ok, werr = wait_on(queue, client)
if not ok then
current_log[client] = nil
sto_timeout()
return nil, werr, part
end end
until false until false
end end
@ -674,19 +806,28 @@ function copas.send(client, data, from, to)
elseif sto_timed_out() then elseif sto_timed_out() then
current_log[client] = nil current_log[client] = nil
sto_timeout()
return nil, sto_error(err), lastIndex return nil, sto_error(err), lastIndex
end end
local queue, direction
if err == "wantread" then if err == "wantread" then
queue = _reading
direction = "read"
current_log = _reading_log current_log = _reading_log
current_log[client] = gettime()
sto_change_queue("read")
coroutine_yield(client, _reading)
else else
queue = _writing
direction = "write"
current_log = _writing_log current_log = _writing_log
current_log[client] = gettime() end
sto_change_queue("write")
coroutine_yield(client, _writing) current_log[client] = gettime()
sto_change_queue(direction)
local ok, werr = wait_on(queue, client)
if not ok then
current_log[client] = nil
sto_timeout()
return nil, werr, lastIndex
end end
until false until false
end end
@ -722,12 +863,18 @@ function copas.connect(skt, host, port)
elseif sto_timed_out() then elseif sto_timed_out() then
_writing_log[skt] = nil _writing_log[skt] = nil
sto_timeout()
return nil, sto_error(err) return nil, sto_error(err)
end end
tried_more_than_once = tried_more_than_once or true tried_more_than_once = tried_more_than_once or true
_writing_log[skt] = gettime() _writing_log[skt] = gettime()
coroutine_yield(skt, _writing) local ok, werr = wait_on(_writing, skt)
if not ok then
_writing_log[skt] = nil
sto_timeout()
return nil, werr
end
until false until false
end end
@ -789,15 +936,19 @@ local function normalize_sslt(sslt)
r.sni = false r.sni = false
elseif t == "table" then elseif t == "table" then
if sslt.mode or sslt.protocol then if sslt.wrap or sslt.sni then
-- has the mandatory fields for the ssl-params table for handshake -- has the target definition (current format), copy our known keys
-- backward compatibility
r.wrap = sslt
r.sni = false
else
-- has the target definition, copy our known keys
r.wrap = sslt.wrap or false -- 'or false' because we do not want nils r.wrap = sslt.wrap or false -- 'or false' because we do not want nils
r.sni = sslt.sni or false -- 'or false' because we do not want nils r.sni = sslt.sni or false -- 'or false' because we do not want nils
else
-- neither of our own keys is truthy, so treat it as a flat luasec
-- context/wrap-params table (backward compatibility), or garbage.
-- There are no default TLS settings, so we cannot detect/reject a bad
-- table here. But we do not need to; LuaSec validates its mandatory
-- fields (mode, protocol, etc.) itself and throws on anything
-- incomplete, so this can never fail open into silent plaintext.
r.wrap = sslt
r.sni = false
end end
elseif t == "userdata" then elseif t == "userdata" then
@ -846,7 +997,8 @@ function copas.dohandshake(skt, wrap_params)
error("TLS/SSL handshake failed: " .. tostring(err)) error("TLS/SSL handshake failed: " .. tostring(err))
elseif sto_timed_out() then elseif sto_timed_out() then
return nil, sto_error(err) sto_timeout()
error("TLS/SSL handshake timeout: " .. tostring(err))
elseif err == "wantwrite" then elseif err == "wantwrite" then
sto_change_queue("write") sto_change_queue("write")
@ -860,7 +1012,11 @@ function copas.dohandshake(skt, wrap_params)
error("TLS/SSL handshake failed: " .. tostring(err)) error("TLS/SSL handshake failed: " .. tostring(err))
end end
coroutine_yield(nskt, queue) local ok, werr = wait_on(queue, nskt)
if not ok then
sto_timeout()
error("TLS/SSL handshake failed: " .. tostring(werr))
end
until false until false
end end
@ -906,6 +1062,8 @@ local _skt_mt_tcp = {
-- if ssl parameters are available, it will also include a handshake -- if ssl parameters are available, it will also include a handshake
connect = function(self, ...) connect = function(self, ...)
local res, err = copas.connect(self.socket, ...) local res, err = copas.connect(self.socket, ...)
-- sni errors out if it fails, so we do not need to check for errors here.
-- sni() itself returns nothing on success, so its result must not be folded into 'res'.
if res then if res then
if self.ssl_params.sni then self:sni() end if self.ssl_params.sni then self:sni() end
if self.ssl_params.wrap then res, err = self:dohandshake() end if self.ssl_params.wrap then res, err = self:dohandshake() end
@ -975,13 +1133,13 @@ local _skt_mt_tcp = {
names = sslp.sni.names names = sslp.sni.names
strict = sslp.sni.strict strict = sslp.sni.strict
end end
-- sni() throws on bad parameters by itself, and returns nothing on success
return self.socket:sni(names, strict) return self.socket:sni(names, strict)
end, end,
dohandshake = function(self, wrap_params) dohandshake = function(self, wrap_params)
local nskt, err = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap) -- copas.dohandshake() either returns the wrapped ssl socket, or throws
if not nskt then return nskt, err end self.socket = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap)
self.socket = nskt -- replace internal socket with the newly wrapped ssl one
return self return self
end, end,
@ -1040,7 +1198,8 @@ _skt_mt_udp.__index.settimeouts = function (self, connect, send, receive)
--- ---
-- Wraps a LuaSocket socket object in an async Copas based socket object. -- Wraps a LuaSocket socket object in an async Copas based socket object.
-- @param skt The socket to wrap -- @param skt The socket to wrap
-- @sslt (optional) Table with ssl parameters, use an empty table to use ssl with defaults -- @param[opt] sslt Table with ssl parameters (see 'sslparams' in the reference docs).
-- Omit the parameter to disable TLS entirely.
-- @return wrapped socket object -- @return wrapped socket object
function copas.wrap (skt, sslt) function copas.wrap (skt, sslt)
if (getmetatable(skt) == _skt_mt_tcp) or (getmetatable(skt) == _skt_mt_udp) then if (getmetatable(skt) == _skt_mt_tcp) or (getmetatable(skt) == _skt_mt_udp) then
@ -1063,6 +1222,8 @@ function copas.handler(handler, sslparams)
return function (skt, ...) return function (skt, ...)
skt = copas.wrap(skt, sslparams) -- this call will normalize the sslparams table skt = copas.wrap(skt, sslparams) -- this call will normalize the sslparams table
local sslp = skt.ssl_params local sslp = skt.ssl_params
-- sni doesn't return an error, just throws on bad params, so no error checking needed.
-- the wrapped dohandshake does the same, so also no error handling needed.
if sslp.sni then skt:sni(sslp.sni.names, sslp.sni.strict) end if sslp.sni then skt:sni(sslp.sni.names, sslp.sni.strict) end
if sslp.wrap then skt:dohandshake(sslp.wrap) end if sslp.wrap then skt:dohandshake(sslp.wrap) end
return handler(skt, ...) return handler(skt, ...)
@ -1151,8 +1312,14 @@ local function _doTick (co, skt, ...)
-- pcall(_errhandlers[co] or _deferror, "task ran for "..tostring(duration).." milliseconds.", co, skt) -- pcall(_errhandlers[co] or _deferror, "task ran for "..tostring(duration).." milliseconds.", co, skt)
-- end -- end
if new_q == _reading or new_q == _writing or new_q == _sleeping then if new_q == _reading or new_q == _writing then
-- we're yielding to a new queue -- we're yielding to wait on a socket; the claim was already taken by
-- the coroutine itself before it yielded (see wait_on below), so by
-- construction this can't fail here
new_q:insert (res)
return
elseif new_q == _sleeping then
-- we're yielding to sleep
new_q:insert (res) new_q:insert (res)
new_q:push (res, co) new_q:push (res, co)
return return
@ -1326,23 +1493,38 @@ end
-- yields the current coroutine and wakes it after 'sleeptime' seconds. -- yields the current coroutine and wakes it after 'sleeptime' seconds.
-- if sleeptime < 0 then it sleeps 0 seconds. -- if sleeptime < 0 then it sleeps 0 seconds.
function copas.pause(sleeptime) function copas.pause(sleeptime)
local s = gettime()
if sleeptime and sleeptime > 0 then if sleeptime and sleeptime > 0 then
coroutine_yield(sleeptime, _sleeping) coroutine_yield(sleeptime, _sleeping)
else else
coroutine_yield(0, _sleeping) coroutine_yield(0, _sleeping)
end end
return gettime() - s
end end
-- yields the current coroutine until explicitly woken up using 'wakeup' -- yields the current coroutine until explicitly woken up using 'wakeup'
function copas.pauseforever() function copas.pauseforever()
local s = gettime()
coroutine_yield(-1, _sleeping) coroutine_yield(-1, _sleeping)
return gettime() - s
end end
-- Wakes up a sleeping coroutine 'co'. -- Wakes up a sleeping coroutine 'co'.
-- @return true on success, or nil+"not sleeping" if 'co' wasn't sleeping
-- (eg. it was already woken up, finished, or canceled through `copas.removethread`).
function copas.wakeup(co) function copas.wakeup(co)
_sleeping:wakeup(co) return _sleeping:wakeup(co)
end
-- Checks whether a coroutine 'co' is currently sleeping (paused or
-- paused-forever), without waking it up. Useful to detect a coroutine
-- that was canceled (eg. through `copas.removethread`) while it was
-- expected to still be waiting.
function copas.issleeping(co)
return _sleeping:issleeping(co)
end end
@ -1353,16 +1535,16 @@ end
do do
local timeout_register = setmetatable({}, { __mode = "k" }) local timeout_register = setmetatable({}, { __mode = "k" })
local time_out_thread
local timerwheel = require("timerwheel").new({ local timerwheel = require("timerwheel").new({
now = gettime,
precision = TIMEOUT_PRECISION, precision = TIMEOUT_PRECISION,
ringsize = math.floor(60*60*24/TIMEOUT_PRECISION), -- ring size 1 day ringsize = math.floor(60*60*24/TIMEOUT_PRECISION), -- ring size 1 day
err_handler = function(err) err_handler = function(err)
return _deferror(err, time_out_thread) return _deferror(err, core_timer_thread)
end, end,
}) })
time_out_thread = copas.addnamedthread("copas_core_timer", function() core_timer_thread = copas.addnamedthread("copas_core_timer", function()
while true do while true do
copas.pause(TIMEOUT_PRECISION) copas.pause(TIMEOUT_PRECISION)
timerwheel:step() timerwheel:step()
@ -1416,13 +1598,15 @@ end
-- a task to check ready to read events -- a task to check ready to read events
local _readable_task = {} do local _readable_task = {} do
_readable_task._events = {}
local function tick(skt) local function tick(skt)
local handler = _servers[skt] local handler = _servers[skt]
if handler then if handler then
_accept(skt, handler) _accept(skt, handler)
else else
_reading:remove(skt) _reading:remove(skt)
_doTick(_reading:pop(skt), skt) _doTick(_reading:release(skt), skt)
end end
end end
@ -1439,9 +1623,11 @@ end
-- a task to check ready to write events -- a task to check ready to write events
local _writable_task = {} do local _writable_task = {} do
_writable_task._events = {}
local function tick(skt) local function tick(skt)
_writing:remove(skt) _writing:remove(skt)
_doTick(_writing:pop(skt), skt) _doTick(_writing:release(skt), skt)
end end
function _writable_task:step() function _writable_task:step()
@ -1502,59 +1688,65 @@ local _select_plain do
local last_cleansing = 0 local last_cleansing = 0
local duration = function(t2, t1) return t2-t1 end local duration = function(t2, t1) return t2-t1 end
_select_plain = function(timeout) if not socket then
local err -- socket module unavailable, switch to luasystem sleep
local now = gettime() _select_plain = block_sleep
else
-- use socket.select to handle socket-io
_select_plain = function(timeout)
local err
local now = gettime()
-- remove any closed sockets to prevent select from hanging on them -- remove any closed sockets to prevent select from hanging on them
if _closed[1] then if _closed[1] then
for i, skt in ipairs(_closed) do for i, skt in ipairs(_closed) do
_closed[i] = { _reading:remove(skt), _writing:remove(skt) } _closed[i] = { _reading:remove(skt), _writing:remove(skt) }
end
end
_readable_task._events, _writable_task._events, err = socket.select(_reading, _writing, timeout)
local r_events, w_events = _readable_task._events, _writable_task._events
-- inject closed sockets in readable/writeable task so they can error out properly
if _closed[1] then
for i, skts in ipairs(_closed) do
_closed[i] = nil
r_events[#r_events+1] = skts[1]
w_events[#w_events+1] = skts[2]
end
end
if duration(now, last_cleansing) > WATCH_DOG_TIMEOUT then
last_cleansing = now
-- Check all sockets selected for reading, and check how long they have been waiting
-- for data already, without select returning them as readable
for skt,time in pairs(_reading_log) do
if not r_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
-- This one timedout while waiting to become readable, so move
-- it in the readable list and try and read anyway, despite not
-- having been returned by select
_reading_log[skt] = nil
r_events[#r_events + 1] = skt
r_events[skt] = #r_events
end end
end end
-- Do the same for writing _readable_task._events, _writable_task._events, err = socket.select(_reading, _writing, timeout)
for skt,time in pairs(_writing_log) do local r_events, w_events = _readable_task._events, _writable_task._events
if not w_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
_writing_log[skt] = nil -- inject closed sockets in readable/writeable task so they can error out properly
w_events[#w_events + 1] = skt if _closed[1] then
w_events[skt] = #w_events for i, skts in ipairs(_closed) do
_closed[i] = nil
r_events[#r_events+1] = skts[1]
w_events[#w_events+1] = skts[2]
end end
end end
end
if err == "timeout" and #r_events + #w_events > 0 then if duration(now, last_cleansing) > WATCH_DOG_TIMEOUT then
return nil last_cleansing = now
else
return err -- Check all sockets selected for reading, and check how long they have been waiting
-- for data already, without select returning them as readable
for skt,time in pairs(_reading_log) do
if not r_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
-- This one timedout while waiting to become readable, so move
-- it in the readable list and try and read anyway, despite not
-- having been returned by select
_reading_log[skt] = nil
r_events[#r_events + 1] = skt
r_events[skt] = #r_events
end
end
-- Do the same for writing
for skt,time in pairs(_writing_log) do
if not w_events[skt] and duration(now, time) > WATCH_DOG_TIMEOUT then
_writing_log[skt] = nil
w_events[#w_events + 1] = skt
w_events[skt] = #w_events
end
end
end
if err == "timeout" and #r_events + #w_events > 0 then
return nil
else
return err
end
end end
end end
end end
@ -1640,6 +1832,77 @@ function copas.finished()
return #_reading == 0 and #_writing == 0 and _resumable:done() and _sleeping:done(copas.gettimeouts()) return #_reading == 0 and #_writing == 0 and _resumable:done() and _sleeping:done(copas.gettimeouts())
end end
local resetexit do
local exit_semaphore, exiting
function resetexit()
exit_semaphore = copas.semaphore.new(1, 0, math.huge)
exiting = false
end
-- Signals tasks to exit. But only if they check for it. By calling `copas.exiting`
-- they can check if they should exit. Or by calling `copas.waitforexit` they can
-- wait until the exit signal is given.
function copas.exit()
if exiting then return end
exiting = true
exit_semaphore:destroy()
end
-- returns whether Copas is in the process of exiting. Exit can be started by
-- calling `copas.exit()`.
function copas.exiting()
return exiting
end
-- Pauses the current coroutine until Copas is exiting. To be used as an exit
-- signal for tasks that need to clean up before exiting.
function copas.waitforexit()
exit_semaphore:take(1)
end
end
--- Forcibly cancels all pending work and signals exit.
-- Intended for test teardown only. Abandons all registered threads and sockets
-- without giving them a chance to clean up. After this call copas.finished()
-- will return true and the loop will exit. The module is left in a clean state
-- ready for the next copas.loop() call.
function copas.cancelall()
-- 1. clear resumable queue
_resumable:clear_resumelist()
-- 2. drain sleeping heap
_sleeping:cancelall()
-- 3. close and drain reading sockets
while _reading[1] do
copas.close(_reading[1])
_reading:remove(_reading[1])
end
-- 4. close and drain writing sockets
while _writing[1] do
copas.close(_writing[1])
_writing:remove(_writing[1])
end
-- 5. remove all servers
while _servers[1] do
copas.removeserver(_servers[1])
end
-- 6. clear non-weak ancillary tables
_closed = {}
_reading_log = {}
_writing_log = {}
-- 7. signal exit
copas.exit()
end
local _getstats do local _getstats do
local _getstats_instrumented, _getstats_plain local _getstats_instrumented, _getstats_plain
@ -1730,8 +1993,17 @@ function copas.loop(initializer, timeout)
timeout = initializer or timeout timeout = initializer or timeout
end end
resetexit()
copas.running = true copas.running = true
while not copas.finished() do copas.step(timeout) end while true do
copas.step(timeout)
if copas.finished() then
if copas.exiting() then
break
end
copas.exit()
end
end
copas.running = false copas.running = false
end end

122
thirdparty/copas/src/copas/future.lua vendored Normal file
View file

@ -0,0 +1,122 @@
local copas = require("copas")
local semaphore = require("copas.semaphore")
local pcall = pcall
if _VERSION == "Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility
pcall = require("coxpcall").pcall
end
-- nil-safe versions for pack/unpack
local _unpack = unpack or table.unpack
local unpack = function(t, i, j) return _unpack(t, i or 1, j or t.n or #t) end
local pack = function(...) return { n = select("#", ...), ...} end
-- Module table
local M = {}
M.SUCCESS = true
M.PENDING = false
M.ERROR = "error"
setmetatable(M, {
__index = function(_, k)
error("unknown field 'future." .. tostring(k) .. "'", 2)
end,
})
-- Future class
local future = {}
future.__index = future
-- calling on the future executes the `get` method
future.__call = function(self, ...) return self:get(...) end
local function new_future()
local self = setmetatable({
results = nil, -- results will be stored here in a 'packed' table (pcall-style: true/false prefix)
sema = semaphore.new(math.huge, 0, math.huge),
coro = nil -- the coroutine that will execute the task
}, future)
return self
end
-- Waits for the task to complete.
-- Returns like pcall: true + results on success, false + errmsg on error.
function future:get()
if not self.results then
self.sema:take(1, math.huge) -- wait until the result is ready
end
return unpack(self.results)
end
-- Non-blocking check on the future status.
-- Returns:
-- M.PENDING (false) -- task not yet complete
-- M.SUCCESS (true), results... -- task completed successfully
-- M.ERROR ("error"), errmsg -- task failed with an error
function future:try()
if not self.results then
return M.PENDING
end
if self.results[1] then
return M.SUCCESS, unpack(self.results, 2)
else
return M.ERROR, self.results[2]
end
end
-- Cancels the task if it has not yet completed.
-- Returns true if cancelled, false if already done.
function future:cancel()
if self.results then
return false -- already done (or already cancelled)
end
self.results = pack(false, "cancelled")
self.sema:release_all()
copas.removethread(self.coro)
return true
end
-- Module implementation
-- Mimics copas.addnamedthread but returns a future instead of the coroutine.
function M.addnamedthread(name, func, ...)
local f = new_future()
f.coro = copas.addnamedthread(name, function(...)
local results
local ok, err = pcall(function(...) results = pack(true, func(...)) end, ...)
if not ok then
results = pack(false, err)
end
if not f.results then -- don't overwrite a cancel
f.results = results
f.sema:release_all()
end
end, ...)
return f
end
-- Mimica copas.addthread but returns a future instead of the coroutine.
function M.addthread(func, ...)
return M.addnamedthread(nil, func, ...)
end
return M

View file

@ -37,7 +37,6 @@ _M.SSLPORT = 443
_M.SSLPROTOCOL = "tlsv1_2" _M.SSLPROTOCOL = "tlsv1_2"
_M.SSLOPTIONS = "all" _M.SSLOPTIONS = "all"
_M.SSLVERIFY = "none" _M.SSLVERIFY = "none"
_M.SSLSNISTRICT = false
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@ -205,13 +204,13 @@ end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local function adjusturi(reqt) local function adjusturi(reqt)
local u = reqt local u = reqt
reqt.fragment = nil -- fragments are client-side only and must never be sent on the wire
-- if there is a proxy, we need the full url. otherwise, just a part. -- if there is a proxy, we need the full url. otherwise, just a part.
if not reqt.proxy and not _M.PROXY then if not reqt.proxy and not _M.PROXY then
u = { u = {
path = socket.try(reqt.path, "invalid path 'nil'"), path = socket.try(reqt.path, "invalid path 'nil'"),
params = reqt.params, params = reqt.params,
query = reqt.query, query = reqt.query,
fragment = reqt.fragment
} }
end end
return url.build(u) return url.build(u)
@ -261,6 +260,11 @@ local function adjustrequest(reqt)
local nreqt = reqt.url and url.parse(reqt.url, default) or {} local nreqt = reqt.url and url.parse(reqt.url, default) or {}
-- explicit components override url -- explicit components override url
for i,v in base.pairs(reqt) do nreqt[i] = v end for i,v in base.pairs(reqt) do nreqt[i] = v end
socket.try(base.type(nreqt.scheme) == "string",
"invalid scheme '" .. base.tostring(nreqt.scheme) .. "'")
nreqt.scheme = string.lower(nreqt.scheme)
socket.try(nreqt.scheme == "http" or nreqt.scheme == "https",
"unsupported scheme '" .. nreqt.scheme .. "'")
if nreqt.port == "" then nreqt.port = 80 end if nreqt.port == "" then nreqt.port = 80 end
socket.try(nreqt.host and nreqt.host ~= "", socket.try(nreqt.host and nreqt.host ~= "",
"invalid host '" .. base.tostring(nreqt.host) .. "'") "invalid host '" .. base.tostring(nreqt.host) .. "'")
@ -350,6 +354,16 @@ end
return 1, code, headers, status return 1, code, headers, status
end end
-- Returns true if `host` is a literal IPv4 or IPv6 address rather than a DNS
-- hostname. RFC 6066 disallows IP literals in the TLS SNI extension, so these
-- must not be sent as an SNI name.
local function ishostliteral(host)
if string.find(host, ":", 1, true) then
return true -- IPv6 addresses always contain a colon (socket.url strips the brackets)
end
return string.match(host, "^%d+%.%d+%.%d+%.%d+$") ~= nil
end
-- Return a function which creates a tcp socket that will -- Return a function which creates a tcp socket that will
-- include the optional SSL/TLS connection, and unsafe redirect checks -- include the optional SSL/TLS connection, and unsafe redirect checks
function _M.getcreatefunc(params) function _M.getcreatefunc(params)
@ -361,9 +375,7 @@ function _M.getcreatefunc(params)
options = params.options, options = params.options,
verify = params.verify, verify = params.verify,
} }
ssl_params.sni = ssl_params.sni or { ssl_params.sni = ssl_params.sni or {}
strict = _M.SSLSNISTRICT
}
-- Default settings -- Default settings
ssl_params.wrap.protocol = ssl_params.wrap.protocol or _M.SSLPROTOCOL ssl_params.wrap.protocol = ssl_params.wrap.protocol or _M.SSLPROTOCOL
@ -373,8 +385,9 @@ function _M.getcreatefunc(params)
end end
ssl_params.wrap.mode = "client" -- Force client mode ssl_params.wrap.mode = "client" -- Force client mode
if not ssl_params.sni.names then local sni_name = ssl_params.sni.names
-- names haven't been set, and hence will be set below. Since this alters if not sni_name then
-- name hasn't been set, and hence will be set below. Since this alters
-- the table, we must make a copy. Otherwise the altered table might be -- the table, we must make a copy. Otherwise the altered table might be
-- reused if a redirect is encountered. -- reused if a redirect is encountered.
local old_params = ssl_params local old_params = ssl_params
@ -382,18 +395,34 @@ function _M.getcreatefunc(params)
for k,v in pairs(old_params) do for k,v in pairs(old_params) do
ssl_params[k] = v ssl_params[k] = v
end end
ssl_params.sni = { strict = old_params.sni.strict } ssl_params.sni = {}
end end
-- upvalue to track https -> http redirection -- upvalue to track https -> http redirection
local washttps = false local washttps = false
local first_request = true -- on follow up redirects we must clear sni-name
-- 'create' function for LuaSocket -- 'create' function for LuaSocket
return function (reqt) return function (reqt)
local u = url.parse(reqt.url) local u = url.parse(reqt.url)
if (reqt.scheme or u.scheme) == "https" then if (reqt.scheme or u.scheme) == "https" then
-- set SNI name to host if not given if type(ssl_params.sni) ~= "table" then
ssl_params.sni.names = ssl_params.sni.names or u.host ssl_params.sni = {} -- was collapsed to `false` below on a prior IP-literal hop
end
if first_request then
ssl_params.sni.names = sni_name -- set SNI name to the given name
first_request = false
else
ssl_params.sni.names = nil -- clear SNI name for follow up redirects
end
if not ssl_params.sni.names and not ishostliteral(u.host) then
ssl_params.sni.names = u.host
end
if not ssl_params.sni.names then
-- no hostname to send: u.host is an IP literal, which RFC 6066 forbids
-- in the SNI extension, so omit it entirely rather than send a bogus name
ssl_params.sni = false
end
-- https, provide an ssl wrapped socket -- https, provide an ssl wrapped socket
local conn = copas.wrap(socket.tcp(), ssl_params) local conn = copas.wrap(socket.tcp(), ssl_params)
-- insert https default port, overriding http port inserted by LuaSocket -- insert https default port, overriding http port inserted by LuaSocket

View file

@ -1,5 +1,10 @@
local copas = require("copas") local copas = require("copas")
local gettime = require("socket").gettime local gettime = copas.gettime
local coroutine_running = coroutine.running
if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility
coroutine_running = require("coxpcall").running
end
local DEFAULT_TIMEOUT = 10 local DEFAULT_TIMEOUT = 10
@ -104,7 +109,7 @@ end
-- @param timeout (optional) timeout in seconds, defaults to the timeout passed to `new` (use `math.huge` to have no timeout). -- @param timeout (optional) timeout in seconds, defaults to the timeout passed to `new` (use `math.huge` to have no timeout).
-- @return wait-time on success, or nil+error+wait_time on failure. Errors can be "timeout", "destroyed", or "lock is not re-entrant" -- @return wait-time on success, or nil+error+wait_time on failure. Errors can be "timeout", "destroyed", or "lock is not re-entrant"
function lock:get(timeout) function lock:get(timeout)
local co = coroutine.running() local co = coroutine_running()
local start_time local start_time
-- is the lock already taken? -- is the lock already taken?
@ -115,13 +120,14 @@ function lock:get(timeout)
return 0 return 0
end end
self.queue[self.q_tail] = co
self.q_tail = self.q_tail + 1
timeout = timeout or self.timeout timeout = timeout or self.timeout
if timeout == 0 then if timeout == 0 then
return nil, "timeout", 0 return nil, "timeout", 0
end end
self.queue[self.q_tail] = co
self.q_tail = self.q_tail + 1
-- set up timeout -- set up timeout
registry[co] = self registry[co] = self
copas.timeout(timeout, timeout_handler) copas.timeout(timeout, timeout_handler)
@ -154,7 +160,7 @@ end
-- an error. -- an error.
-- returns true, or nil+err on an error -- returns true, or nil+err on an error
function lock:release() function lock:release()
local co = coroutine.running() local co = coroutine_running()
if co ~= self.owner then if co ~= self.owner then
return nil, "cannot release a lock not owned" return nil, "cannot release a lock not owned"
@ -167,17 +173,18 @@ function lock:release()
end end
-- need a loop, since individual coroutines might have been removed -- need a loop, since individual coroutines might have been removed
-- so there might be holes -- (or canceled externally, eg. via `copas.removethread`/`future:cancel`)
-- so there might be holes, or stale entries whose wakeup will fail
while self.q_tip < self.q_tail do while self.q_tip < self.q_tail do
local next_up = self.queue[self.q_tip] local next_up = self.queue[self.q_tip]
if next_up then self.queue[self.q_tip] = nil
self.q_tip = self.q_tip + 1
if next_up and copas.wakeup(next_up) then
self.owner = next_up self.owner = next_up
self.queue[self.q_tip] = nil
self.q_tip = self.q_tip + 1
copas.wakeup(next_up)
return true return true
end end
self.q_tip = self.q_tip + 1 -- either an empty hole, or 'next_up' was canceled and will never resume
-- to release the lock itself, so move on to the next waiter in line
end end
-- queue is empty, reset pointers -- queue is empty, reset pointers
self.owner = nil self.owner = nil

View file

@ -1,4 +1,5 @@
local copas = require "copas" local copas = require "copas"
local gettime = copas.gettime
local Sema = copas.semaphore local Sema = copas.semaphore
local Lock = copas.lock local Lock = copas.lock
@ -30,6 +31,7 @@ function Queue.new(opts)
self.workers = setmetatable({}, { __mode = "k" }) self.workers = setmetatable({}, { __mode = "k" })
self.stopping = false self.stopping = false
self.worker_id = 0 self.worker_id = 0
self.exit_semaphore = Sema.new(10^9)
return self return self
end end
@ -105,6 +107,8 @@ end
-- destroyed on a timeout. -- destroyed on a timeout.
function Queue:finish(timeout, no_destroy_on_timeout) function Queue:finish(timeout, no_destroy_on_timeout)
self:stop() self:stop()
timeout = timeout or self.lock.timeout
local endtime = gettime() + timeout
local _, err = self.lock:get(timeout) local _, err = self.lock:get(timeout)
-- the lock never gets released, only destroyed, so we have to check the error string -- the lock never gets released, only destroyed, so we have to check the error string
if err == "timeout" then if err == "timeout" then
@ -113,7 +117,31 @@ function Queue:finish(timeout, no_destroy_on_timeout)
end end
return nil, err return nil, err
end end
return true
-- if we get here, the lock was destroyed, so the queue is empty, now wait for all workers to exit
if not next(self.workers) then
-- all workers already exited, we're done
return true
end
-- multiple threads can call this "finish" method, so we must check exiting workers
-- one by one.
while true do
local _, err = self.exit_semaphore:take(1, math.max(0, endtime - gettime()))
if err == "destroyed" then
return true -- someone else destroyed/finished it, so we're done
end
if err == "timeout" then
if not no_destroy_on_timeout then
self:destroy()
end
return nil, "timeout"
end
if not next(self.workers) then
self.exit_semaphore:destroy()
return true -- all workers exited, we're done
end
end
end end
@ -170,6 +198,9 @@ function Queue:add_worker(worker)
worker(item) -- TODO: wrap in errorhandling worker(item) -- TODO: wrap in errorhandling
end end
self.workers[coro] = nil self.workers[coro] = nil
if self.exit_semaphore then
self.exit_semaphore:give(1)
end
end) end)
self.workers[coro] = true self.workers[coro] = true

View file

@ -1,5 +1,10 @@
local copas = require("copas") local copas = require("copas")
local coroutine_running = coroutine.running
if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compatibility
coroutine_running = require("coxpcall").running
end
local DEFAULT_TIMEOUT = 10 local DEFAULT_TIMEOUT = 10
local semaphore = {} local semaphore = {}
@ -51,7 +56,7 @@ do
-- destroy a semaphore. -- destroy a semaphore.
-- Releases all waiting threads with `nil+"destroyed"` -- Releases all waiting threads with `nil+"destroyed"`
function semaphore:destroy() function semaphore:destroy()
self:give(math.huge) self:release_all()
self.destroyed = true self.destroyed = true
setmetatable(self, destroyed_semaphore_mt) setmetatable(self, destroyed_semaphore_mt)
return true return true
@ -60,12 +65,16 @@ end
-- Gives resources. -- Gives resources.
-- @param given (optional, default 1) number of resources to return. If more -- @param given (optional, default 1) number of resources to return. Must be
-- than the maximum are returned then it will be capped at the maximum and -- a finite number greater than or equal to 0. If more than the maximum are
-- error "too many" will be returned. -- returned then it will be capped at the maximum and error "too many" will
-- be returned.
function semaphore:give(given) function semaphore:give(given)
local err local err
given = given or 1 given = given or 1
if given < 0 or given == math.huge or given ~= given then
error("expected given resources (1st argument) to be a finite number greater than or equal to 0, got: " .. tostring(given), 2)
end
local count = self.count + given local count = self.count + given
--print("now at",count, ", after +"..given) --print("now at",count, ", after +"..given)
if count > self.max then if count > self.max then
@ -78,18 +87,26 @@ function semaphore:give(given)
local nxt = self.queue[i] -- there can be holes, so nxt might be nil local nxt = self.queue[i] -- there can be holes, so nxt might be nil
if not nxt then if not nxt then
self.q_tip = i + 1 self.q_tip = i + 1
elseif not copas.issleeping(nxt.co) then
-- stale entry; 'nxt.co' was canceled externally (eg. via
-- copas.removethread/future:cancel) and will never resume to
-- consume or give back resources. Drop it without touching 'count',
-- and regardless of how much it had requested, so it doesn't block
-- waiters behind it that request less than it did.
self.queue[i] = nil
self.to_flags[nxt.co] = nil
self.q_tip = i + 1
nxt.co = nil
elseif count >= nxt.requested then
-- release it
self.queue[i] = nil
self.to_flags[nxt.co] = nil
count = count - nxt.requested
self.q_tip = i + 1
copas.wakeup(nxt.co)
nxt.co = nil
else else
if count >= nxt.requested then break -- we ran out of resources
-- release it
self.queue[i] = nil
self.to_flags[nxt.co] = nil
count = count - nxt.requested
self.q_tip = i + 1
copas.wakeup(nxt.co)
nxt.co = nil
else
break -- we ran out of resources
end
end end
end end
@ -131,13 +148,17 @@ end
-- Requests resources from the semaphore. -- Requests resources from the semaphore.
-- Waits if there are not enough resources available before returning. -- Waits if there are not enough resources available before returning.
-- @param requested (optional, default 1) the number of resources requested -- @param requested (optional, default 1) the number of resources requested.
-- Must be a number greater than or equal to 1, and not NaN.
-- @param timeout (optional, defaults to semaphore timeout) timeout in -- @param timeout (optional, defaults to semaphore timeout) timeout in
-- seconds. If 0 it will either succeed or return immediately with error "timeout". -- seconds. If 0 it will either succeed or return immediately with error "timeout".
-- If `math.huge` it will wait forever. -- If `math.huge` it will wait forever.
-- @return true, or nil+"destroyed" -- @return true, or nil+"destroyed"
function semaphore:take(requested, timeout) function semaphore:take(requested, timeout)
requested = requested or 1 requested = requested or 1
if requested < 1 or requested ~= requested then
error("expected requested resources (1st argument) to be a number greater than or equal to 1, got: " .. tostring(requested), 2)
end
if self.q_tail == 1 and self.count >= requested then if self.q_tail == 1 and self.count >= requested then
-- nobody is waiting before us, and there is enough in store -- nobody is waiting before us, and there is enough in store
self.count = self.count - requested self.count = self.count - requested
@ -154,7 +175,7 @@ function semaphore:take(requested, timeout)
end end
-- get in line -- get in line
local co = coroutine.running() local co = coroutine_running()
self.to_flags[co] = nil self.to_flags[co] = nil
registry[co] = self registry[co] = self
copas.timeout(to, timeout_handler) copas.timeout(to, timeout_handler)
@ -193,10 +214,30 @@ end
function semaphore:get_wait() function semaphore:get_wait()
local wait = 0 local wait = 0
for i = self.q_tip, self.q_tail - 1 do for i = self.q_tip, self.q_tail - 1 do
wait = wait + ((self.queue[i] or {}).requested or 0) local item = self.queue[i]
if item and copas.issleeping(item.co) then
wait = wait + item.requested
end
end end
return wait - self.count return wait - self.count
end end
-- Releases every currently queued waiter, regardless of `max`.
-- Feeds `get_wait()` into `give()` in `max`-sized chunks, since a single
-- `give()` call is capped at `max` and would otherwise strand waiters
-- beyond it.
function semaphore:release_all()
local wait = self:get_wait()
while wait > 0 do
if wait > self.max then
self:give(self.max)
else
self:give(wait)
end
wait = wait - self.max
end
end
return semaphore return semaphore

View file

@ -25,11 +25,20 @@ end
do do
local function expire_func(self, initial_delay) local function expire_func(self, initial_delay)
local my_co = coroutine_running()
if self.errorhandler then if self.errorhandler then
copas.seterrorhandler(self.errorhandler) copas.seterrorhandler(self.errorhandler)
end end
copas.pause(initial_delay) copas.pause(initial_delay)
while true do while true do
if self.co ~= my_co then
-- This generation was replaced (eg. the callback cancelled and
-- rearmed the timer, synchronously, before returning). `self.co`
-- and `self.cancelled` now belong to the new generation, so this
-- coroutine must exit without touching them.
return
end
if not self.cancelled then if not self.cancelled then
if not self.recurring then if not self.recurring then
-- non-recurring timer -- non-recurring timer
@ -45,6 +54,11 @@ do
end end
end end
if self.co ~= my_co then
-- replaced while the callback was running, see above
return
end
if self.cancelled then if self.cancelled then
-- clean up and exit the thread -- clean up and exit the thread
self.co = nil self.co = nil
@ -75,6 +89,10 @@ end
--- Cancels a running timer. --- Cancels a running timer.
-- If the timer callback is currently in progress (eg. yielded on socket I/O),
-- it is allowed to run to completion, it just won't be rescheduled. Only a
-- timer that is idle, waiting for its next recurrence, is woken up and
-- stopped immediately.
-- @return timer object, or nil+error -- @return timer object, or nil+error
function timer:cancel() function timer:cancel()
if not self.co then if not self.co then
@ -86,8 +104,7 @@ function timer:cancel()
end end
self.cancelled = true self.cancelled = true
copas.wakeup(self.co) -- resume asap copas.wakeup(self.co) -- in case it's idle between recurrences, exit immediately
copas.removethread(self.co) -- will immediately drop the thread upon resuming
self.co = nil self.co = nil
return self return self
end end
@ -106,7 +123,7 @@ do
-- `opts.params` (optional) this value will be passed to the timer callback, `opts.initial_delay` (optional) the first delay to use, defaults to `delay`. -- `opts.params` (optional) this value will be passed to the timer callback, `opts.initial_delay` (optional) the first delay to use, defaults to `delay`.
-- @return timer object, or throws an error on bad input -- @return timer object, or throws an error on bad input
function timer.new(opts) function timer.new(opts)
assert(opts.delay or -1 >= 0, "delay must be greater than or equal to 0") assert((opts.delay or -1) >= 0, "delay must be greater than or equal to 0")
assert(type(opts.callback) == "function", "expected callback to be a function") assert(type(opts.callback) == "function", "expected callback to be a function")
local callback = function(timer_obj, params) local callback = function(timer_obj, params)

66
thirdparty/copas/tests/cancelall.lua vendored Normal file
View file

@ -0,0 +1,66 @@
-- make sure we are pointing to the local copas first
package.path = string.format("../src/?.lua;%s", package.path)
local copas = require "copas"
local socket = require "socket"
local timer = copas.timer
-- Test 1: cancelall() exits a loop that has a server, a sleeping thread, and a
-- thread blocked on a socket.
copas.loop(function()
-- register a server that will never get a connection
local srv = socket.tcp()
assert(srv:bind("127.0.0.1", 0, 5))
copas.addserver(srv, function()
copas.pause(60*60) -- don't handle incoming connections, let them wait
end)
-- register a thread that sleeps forever
copas.addthread(function()
copas.pause(60*60)
end)
-- register a thread that waits on a socket read (connect to our own server)
local _, port = srv:getsockname()
copas.addthread(function()
local client = copas.wrap(socket.tcp())
client:connect("127.0.0.1", port)
client:receive("*l") -- blocks waiting for data that never arrives
end)
-- fire cancelall after a short delay
timer.new({
delay = 0.5,
callback = function()
copas.cancelall()
end,
})
-- set up a timeout timer to make sure the loop exits within a reasonable time
timer.new({
delay = 5,
callback = function()
print("loop 1 did not exit within 5 seconds")
os.exit(1) -- exit with error code to indicate failure
end,
})
end)
print("loop 1 exited ok")
-- Test 2: after cancelall(), a fresh copas.loop() works correctly.
local loop2_done = false
copas.loop(function()
copas.addthread(function()
copas.pause(0.05)
loop2_done = true
end)
end)
assert(loop2_done, "loop 2 did not complete its task")
print("loop 2 exited ok")
print("all tests passed")

View file

@ -1,8 +1,8 @@
rem #!/bin/sh rem #!/bin/sh
openssl req -newkey rsa:1024 -sha1 -keyout clientAkey.pem -out clientAreq.pem -nodes -config ./clientA.cnf -days 365 -batch openssl req -newkey rsa:2048 -sha256 -keyout clientAkey.pem -out clientAreq.pem -nodes -config ./clientA.cnf -days 365 -batch
openssl x509 -req -in clientAreq.pem -sha1 -extfile ./clientA.cnf -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial -out clientAcert.pem -days 365 openssl x509 -req -in clientAreq.pem -sha256 -extfile ./clientA.cnf -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial -out clientAcert.pem -days 365
copy clientAcert.pem + rootA.pem clientA.pem copy clientAcert.pem + rootA.pem clientA.pem

View file

@ -1,9 +1,9 @@
#!/bin/sh #!/bin/sh
openssl req -newkey rsa:1024 -sha1 -keyout clientAkey.pem -out clientAreq.pem \ openssl req -newkey rsa:2048 -sha256 -keyout clientAkey.pem -out clientAreq.pem \
-nodes -config ./clientA.cnf -days 365 -batch -nodes -config ./clientA.cnf -days 365 -batch
openssl x509 -req -in clientAreq.pem -sha1 -extfile ./clientA.cnf \ openssl x509 -req -in clientAreq.pem -sha256 -extfile ./clientA.cnf \
-extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial \ -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial \
-out clientAcert.pem -days 365 -out clientAcert.pem -days 365

View file

@ -1,8 +1,8 @@
rem #!/bin/sh rem #!/bin/sh
openssl req -newkey rsa:1024 -sha1 -keyout clientBkey.pem -out clientBreq.pem -nodes -config ./clientB.cnf -days 365 -batch openssl req -newkey rsa:2048 -sha256 -keyout clientBkey.pem -out clientBreq.pem -nodes -config ./clientB.cnf -days 365 -batch
openssl x509 -req -in clientBreq.pem -sha1 -extfile ./clientB.cnf -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial -out clientBcert.pem -days 365 openssl x509 -req -in clientBreq.pem -sha256 -extfile ./clientB.cnf -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial -out clientBcert.pem -days 365
copy clientBcert.pem + rootB.pem clientB.pem copy clientBcert.pem + rootB.pem clientB.pem

View file

@ -1,9 +1,9 @@
#!/bin/sh #!/bin/sh
openssl req -newkey rsa:1024 -sha1 -keyout clientBkey.pem -out clientBreq.pem \ openssl req -newkey rsa:2048 -sha256 -keyout clientBkey.pem -out clientBreq.pem \
-nodes -config ./clientB.cnf -days 365 -batch -nodes -config ./clientB.cnf -days 365 -batch
openssl x509 -req -in clientBreq.pem -sha1 -extfile ./clientB.cnf \ openssl x509 -req -in clientBreq.pem -sha256 -extfile ./clientB.cnf \
-extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial \ -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial \
-out clientBcert.pem -days 365 -out clientBcert.pem -days 365

View file

@ -1,7 +1,7 @@
REM #!/bin/sh REM #!/bin/sh
openssl req -newkey rsa:1024 -sha1 -keyout rootAkey.pem -out rootAreq.pem -nodes -config ./rootA.cnf -days 365 -batch openssl req -newkey rsa:2048 -sha256 -keyout rootAkey.pem -out rootAreq.pem -nodes -config ./rootA.cnf -days 365 -batch
openssl x509 -req -in rootAreq.pem -sha1 -extfile ./rootA.cnf -extensions v3_ca -signkey rootAkey.pem -out rootA.pem -days 365 openssl x509 -req -in rootAreq.pem -sha256 -extfile ./rootA.cnf -extensions v3_ca -signkey rootAkey.pem -out rootA.pem -days 365
openssl x509 -subject -issuer -noout -in rootA.pem openssl x509 -subject -issuer -noout -in rootA.pem

View file

@ -1,7 +1,6 @@
#!/bin/sh #!/bin/sh
openssl req -newkey rsa:2048 -sha256 -keyout rootAkey.pem -out rootAreq.pem -nodes -config ./rootA.cnf -days 365 -batch
openssl req -newkey rsa:1024 -sha1 -keyout rootAkey.pem -out rootAreq.pem -nodes -config ./rootA.cnf -days 365 -batch openssl x509 -req -in rootAreq.pem -sha256 -extfile ./rootA.cnf -extensions v3_ca -signkey rootAkey.pem -out rootA.pem -days 365
openssl x509 -req -in rootAreq.pem -sha1 -extfile ./rootA.cnf -extensions v3_ca -signkey rootAkey.pem -out rootA.pem -days 365
openssl x509 -subject -issuer -noout -in rootA.pem openssl x509 -subject -issuer -noout -in rootA.pem

View file

@ -1,7 +1,7 @@
rem #!/bin/sh rem #!/bin/sh
openssl req -newkey rsa:1024 -sha1 -keyout rootBkey.pem -out rootBreq.pem -nodes -config ./rootB.cnf -days 365 -batch openssl req -newkey rsa:2048 -sha256 -keyout rootBkey.pem -out rootBreq.pem -nodes -config ./rootB.cnf -days 365 -batch
openssl x509 -req -in rootBreq.pem -sha1 -extfile ./rootB.cnf -extensions v3_ca -signkey rootBkey.pem -out rootB.pem -days 365 openssl x509 -req -in rootBreq.pem -sha256 -extfile ./rootB.cnf -extensions v3_ca -signkey rootBkey.pem -out rootB.pem -days 365
openssl x509 -subject -issuer -noout -in rootB.pem openssl x509 -subject -issuer -noout -in rootB.pem

View file

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
openssl req -newkey rsa:1024 -sha1 -keyout rootBkey.pem -out rootBreq.pem -nodes -config ./rootB.cnf -days 365 -batch openssl req -newkey rsa:2048 -sha256 -keyout rootBkey.pem -out rootBreq.pem -nodes -config ./rootB.cnf -days 365 -batch
openssl x509 -req -in rootBreq.pem -sha1 -extfile ./rootB.cnf -extensions v3_ca -signkey rootBkey.pem -out rootB.pem -days 365 openssl x509 -req -in rootBreq.pem -sha256 -extfile ./rootB.cnf -extensions v3_ca -signkey rootBkey.pem -out rootB.pem -days 365
openssl x509 -subject -issuer -noout -in rootB.pem openssl x509 -subject -issuer -noout -in rootB.pem

View file

@ -1,8 +1,8 @@
rem #!/bin/sh rem #!/bin/sh
openssl req -newkey rsa:1024 -keyout serverAkey.pem -out serverAreq.pem -config ./serverA.cnf -nodes -days 365 -batch openssl req -newkey rsa:2048 -keyout serverAkey.pem -out serverAreq.pem -config ./serverA.cnf -nodes -days 365 -batch
openssl x509 -req -in serverAreq.pem -sha1 -extfile ./serverA.cnf -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial -out serverAcert.pem -days 365 openssl x509 -req -in serverAreq.pem -sha256 -extfile ./serverA.cnf -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial -out serverAcert.pem -days 365
copy serverAcert.pem + rootA.pem serverA.pem copy serverAcert.pem + rootA.pem serverA.pem

View file

@ -1,9 +1,9 @@
#!/bin/sh #!/bin/sh
openssl req -newkey rsa:1024 -keyout serverAkey.pem -out serverAreq.pem \ openssl req -newkey rsa:2048 -keyout serverAkey.pem -out serverAreq.pem \
-config ./serverA.cnf -nodes -days 365 -batch -config ./serverA.cnf -nodes -days 365 -batch
openssl x509 -req -in serverAreq.pem -sha1 -extfile ./serverA.cnf \ openssl x509 -req -in serverAreq.pem -sha256 -extfile ./serverA.cnf \
-extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial \ -extensions usr_cert -CA rootA.pem -CAkey rootAkey.pem -CAcreateserial \
-out serverAcert.pem -days 365 -out serverAcert.pem -days 365

View file

@ -1,8 +1,8 @@
rem #!/bin/sh rem #!/bin/sh
openssl req -newkey rsa:1024 -keyout serverBkey.pem -out serverBreq.pem -config ./serverB.cnf -nodes -days 365 -batch openssl req -newkey rsa:2048 -keyout serverBkey.pem -out serverBreq.pem -config ./serverB.cnf -nodes -days 365 -batch
openssl x509 -req -in serverBreq.pem -sha1 -extfile ./serverB.cnf -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial -out serverBcert.pem -days 365 openssl x509 -req -in serverBreq.pem -sha256 -extfile ./serverB.cnf -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial -out serverBcert.pem -days 365
copy serverBcert.pem + rootB.pem serverB.pem copy serverBcert.pem + rootB.pem serverB.pem

View file

@ -1,9 +1,9 @@
#!/bin/sh #!/bin/sh
openssl req -newkey rsa:1024 -keyout serverBkey.pem -out serverBreq.pem \ openssl req -newkey rsa:2048 -keyout serverBkey.pem -out serverBreq.pem \
-config ./serverB.cnf -nodes -days 365 -batch -config ./serverB.cnf -nodes -days 365 -batch
openssl x509 -req -in serverBreq.pem -sha1 -extfile ./serverB.cnf \ openssl x509 -req -in serverBreq.pem -sha256 -extfile ./serverB.cnf \
-extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial \ -extensions usr_cert -CA rootB.pem -CAkey rootBkey.pem -CAcreateserial \
-out serverBcert.pem -days 365 -out serverBcert.pem -days 365

View file

@ -36,7 +36,7 @@ copas.loop(function()
-- wait in the read/write queues -- wait in the read/write queues
copas.pause(2) copas.pause(2)
-- now we're closing the connecting_socket -- now we're closing the connecting_socket
close_time = socket.gettime() close_time = copas.gettime()
print("closing client socket now, client receive and send operation should immediately error out now") print("closing client socket now, client receive and send operation should immediately error out now")
client_socket:close() client_socket:close()
@ -55,7 +55,7 @@ copas.loop(function()
copas.addthread(function() copas.addthread(function()
local data, err = client_socket:receive(1) local data, err = client_socket:receive(1)
print("receive result: ", tostring(data), tostring(err)) print("receive result: ", tostring(data), tostring(err))
receive_end_time = socket.gettime() receive_end_time = copas.gettime()
print("receive took: ", receive_end_time - close_time) print("receive took: ", receive_end_time - close_time)
check_exit() check_exit()
end) end)
@ -66,7 +66,7 @@ copas.loop(function()
ok, err = client_socket:send(("hello world"):rep(100)) ok, err = client_socket:send(("hello world"):rep(100))
end end
print("send result: ", tostring(ok), tostring(err)) print("send result: ", tostring(ok), tostring(err))
send_end_time = socket.gettime() send_end_time = copas.gettime()
print("send took: ", send_end_time - close_time) print("send took: ", send_end_time - close_time)
check_exit() check_exit()
end) end)

View file

@ -25,7 +25,7 @@ else
end end
done = false done = false
local server = socket.bind("*", 20000) local server = socket.bind("localhost", 20000)
local message = "Hello world!" local message = "Hello world!"
copas.addserver(server, function(skt) copas.addserver(server, function(skt)

View file

@ -72,3 +72,46 @@ copas.loop()
assert(testran == 6, "Test 6 was not executed!") assert(testran == 6, "Test 6 was not executed!")
print("6) success") print("6) success")
print("7) Testing exiting releasing the exitsemaphore (implicit, no call to copas.exit)")
copas.addthread(function()
print("","7 running...")
copas.addthread(function()
copas.waitforexit()
testran = 7
end)
end)
copas.loop()
assert(testran == 7, "Test 7 was not executed!")
print("7) success")
print("8) Testing schduling new tasks while exiting (explicit exit by calling copas.exit)")
testran = 0
copas.addthread(function()
print("","8 running...")
copas.addthread(function()
while true do
copas.pause(0.1)
testran = testran + 1
print("count...")
if testran == 3 then -- testran == 3
print("initiating exit...")
copas.exit()
break
end
end
end)
copas.addthread(function()
copas.waitforexit()
print("exit signal received...")
testran = testran + 1 -- testran == 4
copas.addthread(function()
print("running new task from exit handler...")
copas.pause(1)
testran = testran + 1 -- testran == 5
print("new task from exit handler done!")
end)
end)
end)
copas.loop()
assert(testran == 5, "Test 8 was not executed!")
print("8) success")

178
thirdparty/copas/tests/future.lua vendored Normal file
View file

@ -0,0 +1,178 @@
-- make sure we are pointing to the local copas first
package.path = string.format("../src/?.lua;%s", package.path)
local copas = require "copas"
local future = copas.future
local test_complete = false
copas.loop(function()
-- get(): waits for task, returns true + results on success (pcall-style)
do
local f = future.addthread(function()
copas.pause(0.05)
return "hello", "world"
end)
local ok, a, b = f:get()
assert(ok == true, "expected true on success")
assert(a == "hello" and b == "world", "unexpected results: "..tostring(a)..", "..tostring(b))
print("ok: get() success")
end
-- get(): returns false + errmsg when task errors
do
local f = future.addthread(function()
error("boom")
end)
local ok, err = f:get()
assert(ok == false, "expected false on error, got: "..tostring(ok))
assert(type(err) == "string" and err:find("boom"), "expected 'boom' in error, got: "..tostring(err))
print("ok: get() error propagation")
end
-- get() can be called multiple times on the same future
do
local f = future.addthread(function() return 42 end)
local ok1, v1 = f:get()
local ok2, v2 = f:get()
assert(ok1 == true and v1 == 42, "first get() failed")
assert(ok2 == true and v2 == 42, "second get() failed")
print("ok: get() idempotent")
end
-- callable future: f(...) is equivalent to f:get(...)
do
local f = future.addthread(function() return "via call" end)
local ok, v = f()
assert(ok == true and v == "via call", "callable future failed")
print("ok: future callable via __call")
end
-- multiple threads waiting on the same future all receive the result
do
local f = future.addthread(function()
copas.pause(0.1)
return "shared"
end)
local results = {}
for i = 1, 5 do
local idx = i
copas.addthread(function()
local ok, v = f:get()
results[idx] = ok and v
end)
end
copas.pause(0.3)
for i = 1, 5 do
assert(results[i] == "shared", "thread "..i.." did not get result, got: "..tostring(results[i]))
end
print("ok: multiple waiters on get()")
end
-- try(): returns PENDING while task is still running
do
local f = future.addthread(function()
copas.pause(0.2)
return "done"
end)
local status = f:try()
assert(status == future.PENDING, "expected PENDING, got: "..tostring(status))
f:get() -- wait for completion
print("ok: try() PENDING")
end
-- try(): returns SUCCESS + results after task completes
do
local f = future.addthread(function() return 1, 2, 3 end)
f:get()
local status, a, b, c = f:try()
assert(status == future.SUCCESS, "expected SUCCESS, got: "..tostring(status))
assert(a == 1 and b == 2 and c == 3, "unexpected results from try()")
print("ok: try() SUCCESS")
end
-- try(): returns ERROR + errmsg after task errors
do
local f = future.addthread(function()
error("oh no")
end)
f:get()
local status, err = f:try()
assert(status == future.ERROR, "expected ERROR, got: "..tostring(status))
assert(type(err) == "string" and err:find("oh no"), "unexpected error message: "..tostring(err))
print("ok: try() ERROR")
end
-- cancel(): cancels a pending task, get() returns false + "cancelled"
do
local task_ran = false
local f = future.addthread(function()
copas.pause(5)
task_ran = true
return "should not reach"
end)
local cancelled = f:cancel()
assert(cancelled == true, "expected cancel() to return true")
local ok, err = f:get()
assert(ok == false, "expected false after cancel, got: "..tostring(ok))
assert(err == "cancelled", "expected 'cancelled', got: "..tostring(err))
copas.pause(0.1)
assert(not task_ran, "task body should not have run after cancel()")
print("ok: cancel() pending task")
end
-- cancel(): try() returns ERROR + "cancelled" after cancel
do
local f = future.addthread(function()
copas.pause(5)
end)
f:cancel()
local status, err = f:try()
assert(status == future.ERROR, "expected ERROR after cancel, got: "..tostring(status))
assert(err == "cancelled", "expected 'cancelled', got: "..tostring(err))
print("ok: try() ERROR after cancel()")
end
-- cancel(): returns false if task already completed
do
local f = future.addthread(function() return "done" end)
f:get()
local cancelled = f:cancel()
assert(cancelled == false, "expected cancel() to return false when already done")
print("ok: cancel() on completed future returns false")
end
-- addnamedthread: returns a usable future
do
local f = future.addnamedthread("test-task", function() return "named" end)
local ok, v = f:get()
assert(ok == true and v == "named", "addnamedthread() failed")
print("ok: addnamedthread()")
end
-- typo guard: accessing an unknown field throws a meaningful error
do
local ok, err = pcall(function() return future.PNEDING end)
assert(not ok, "expected an error for unknown field")
assert(err:find("future%.PNEDING"), "expected field name in error message, got: "..tostring(err))
print("ok: typo guard on unknown field")
end
test_complete = true
end)
assert(test_complete, "test did not complete!")
print("test success!")

View file

@ -47,7 +47,7 @@ end, true) -- true: make it the default for all threads/coros
local function runtest() local function runtest()
local s1 = socket.bind('*', 49500) local s1 = socket.bind('localhost', 49500)
copas.addserver(s1, copas.handler(function(skt) copas.addserver(s1, copas.handler(function(skt)
-- HTTP server that will optionally do a timeout on the request, or on the response -- HTTP server that will optionally do a timeout on the request, or on the response
copas.setsocketname("Server 49500", skt) copas.setsocketname("Server 49500", skt)

View file

@ -35,13 +35,13 @@ local done = false
copas.addthread(function() copas.addthread(function()
local _, code, headers = doreq("https://goo.gl/UBCUc5") -- https --> https redirect local _, code, headers = doreq("https://goo.gl/UBCUc5") -- https --> https redirect
assert(tonumber(code)==200) assert(tonumber(code)==200, "unexpected status code: "..tostring(code))
assert(headers.location == "https://github.com/brunoos/luasec") assert(headers.location == "https://github.com/lunarmodules/luasec", "unexpected location header: "..tostring(headers.location))
print("https -> https redirect OK!") print("https -> https redirect OK!")
copas.addthread(function() copas.addthread(function()
local _, code, headers = doreq("http://goo.gl/UBCUc5") -- http --> https redirect local _, code, headers = doreq("http://goo.gl/UBCUc5") -- http --> https redirect
assert(tonumber(code)==200) assert(tonumber(code)==200, "unexpected status code: "..tostring(code))
assert(headers.location == "https://github.com/brunoos/luasec") assert(headers.location == "https://github.com/lunarmodules/luasec", "unexpected location header: "..tostring(headers.location))
print("http -> https redirect OK!") print("http -> https redirect OK!")
copas.addthread(function() copas.addthread(function()
--local result, code, headers, status = doreq("http://goo.gl/tBfqNu") -- http --> http redirect --local result, code, headers, status = doreq("http://goo.gl/tBfqNu") -- http --> http redirect
@ -49,7 +49,7 @@ copas.addthread(function()
-- initial redirect to the same url, over https, hence the final -- initial redirect to the same url, over https, hence the final
-- redirect is a downgrade which then errors out -- redirect is a downgrade which then errors out
-- so we set up a local http-server to deal with this -- so we set up a local http-server to deal with this
local server = assert(socket.bind("127.0.0.1", 9876)) local server = assert(socket.bind("localhost", 9876))
local crlf = string.char(13)..string.char(10) local crlf = string.char(13)..string.char(10)
copas.addserver(server, function(skt) copas.addserver(server, function(skt)
skt = copas.wrap(skt) skt = copas.wrap(skt)
@ -63,7 +63,7 @@ copas.addthread(function()
-- execute test request -- execute test request
local _, code, headers = doreq("http://localhost:9876/") -- http --> http redirect local _, code, headers = doreq("http://localhost:9876/") -- http --> http redirect
copas.removeserver(server) -- immediately close server again copas.removeserver(server) -- immediately close server again
assert(tonumber(code)==200) assert(tonumber(code)==200, "unexpected status code: "..tostring(code))
assert(headers.location == "http://www.httpvshttps.com") assert(headers.location == "http://www.httpvshttps.com")
print("http -> http redirect OK!") print("http -> http redirect OK!")
copas.addthread(function() copas.addthread(function()
@ -73,7 +73,7 @@ copas.addthread(function()
copas.addthread(function() copas.addthread(function()
redirect = "all" redirect = "all"
local _, code, headers = doreq("https://bit.ly/3vmhXhW") -- https --> http security test case local _, code, headers = doreq("https://bit.ly/3vmhXhW") -- https --> http security test case
assert(tonumber(code)==200) assert(tonumber(code)==200, "unexpected status code: "..tostring(code))
assert(headers.location == "http://www.httpvshttps.com/") assert(headers.location == "http://www.httpvshttps.com/")
print("https -> http redirect, while allowed OK!") print("https -> http redirect, while allowed OK!")
done = true done = true

104
thirdparty/copas/tests/httpvalidate.lua vendored Normal file
View file

@ -0,0 +1,104 @@
-- test scheme validation and normalization in copas/http.lua
--
-- Covers the fix that:
-- * rejects non-string/unsupported request schemes before a connection
-- is attempted (instead of silently falling back to plain HTTP), and
-- * normalizes the scheme to lowercase (from the url and from an
-- explicit override) so mixed-case "HTTPS" URLs select the TLS
-- transport instead of being silently treated as plain HTTP.
local copas = require("copas")
local http = copas.http
local ltn12 = require("ltn12")
local failures = 0
local function check(cond, msg)
if cond then
print("OK: "..msg)
else
print("FAILED: "..msg)
failures = failures + 1
end
end
-- helper: build a request table with a stub `create` so no real connection
-- is ever attempted; the stub records the (normalized) request table it
-- receives and aborts the request before any socket is opened.
local function stubrequest(url, overrides)
local captured
local reqt = {
url = url,
target = {},
create = function(nreqt)
captured = nreqt
return nil, "test-abort-before-connect"
end,
}
for k, v in pairs(overrides or {}) do
reqt[k] = v
end
reqt.sink = ltn12.sink.table(reqt.target)
local ok, err = http.request(reqt)
return ok, err, captured
end
copas.addthread(function()
-- non-string scheme (explicit override) is rejected before connecting
do
local ok, err, captured = stubrequest("http://localhost/", { scheme = 123 })
check(ok == nil and tostring(err):match("invalid scheme") ~= nil,
"non-string scheme is rejected, got: "..tostring(err))
check(captured == nil, "no connection is attempted for an invalid scheme")
end
-- unsupported (but well-formed) scheme is rejected before connecting
do
local ok, err, captured = stubrequest("ftp://localhost/")
check(ok == nil and tostring(err):match("unsupported scheme") ~= nil,
"unsupported scheme is rejected, got: "..tostring(err))
check(captured == nil, "no connection is attempted for an unsupported scheme")
end
-- mixed-case scheme from the url is normalized to lowercase
do
local _, _, captured = stubrequest("HTTPS://localhost/")
check(captured and captured.scheme == "https",
"mixed-case 'HTTPS' url scheme is normalized to lowercase, got: "..
tostring(captured and captured.scheme))
end
-- mixed-case scheme passed as an explicit override is normalized too
do
local _, _, captured = stubrequest("http://localhost/", { scheme = "HtTp" })
check(captured and captured.scheme == "http",
"mixed-case explicit scheme override is normalized to lowercase, got: "..
tostring(captured and captured.scheme))
end
-- once normalized, a "https" scheme must select the TLS transport,
-- not silently fall back to plain HTTP
do
local create = http.getcreatefunc()
local conn = create({ url = "https://localhost/", scheme = "https" })
check(conn.ssl_params.wrap ~= false, "normalized 'https' scheme selects the TLS transport")
end
-- a plain "http" scheme must not select the TLS transport
do
local create = http.getcreatefunc()
local conn = create({ url = "http://localhost/", scheme = "http" })
check(conn.ssl_params.wrap == false, "'http' scheme does not select the TLS transport")
end
if failures > 0 then
print(failures.." check(s) failed")
os.exit(1)
end
print("all checks passed")
os.exit(0)
end)
copas.loop()

View file

@ -12,12 +12,12 @@ local socket = require 'socket'
-- copas.debug.start() -- copas.debug.start()
local body = ("A"):rep(1024*1024*50) -- 50 mb string local body = ("A"):rep(1024*1024*50) -- 50 mb string
local start = socket.gettime() local start = copas.gettime()
local done = 0 local done = 0
local sparams, cparams local sparams, cparams
local function runtest() local function runtest()
local s1 = socket.bind('*', 49500) local s1 = socket.bind('localhost', 49500)
copas.addserver(s1, copas.handler(function(skt) copas.addserver(s1, copas.handler(function(skt)
copas.setsocketname("Server 49500", skt) copas.setsocketname("Server 49500", skt)
copas.setthreadname("Server 49500") copas.setthreadname("Server 49500")
@ -25,12 +25,12 @@ local function runtest()
local res, err, part = skt:receive('*a') local res, err, part = skt:receive('*a')
res = res or part res = res or part
if res ~= body then print("Received doesn't match send") end if res ~= body then print("Received doesn't match send") end
print("Server reading port 49500... Done!", socket.gettime()-start, err, #res) print("Server reading port 49500... Done!", copas.gettime()-start, err, #res)
copas.removeserver(s1) copas.removeserver(s1)
done = done + 1 done = done + 1
end, sparams)) end, sparams))
local s2 = socket.bind('*', 49501) local s2 = socket.bind('localhost', 49501)
copas.addserver(s2, copas.handler(function(skt) copas.addserver(s2, copas.handler(function(skt)
skt:settimeout(0) -- set, uses the `receivepartial` method skt:settimeout(0) -- set, uses the `receivepartial` method
copas.setsocketname("Server 49501", skt) copas.setsocketname("Server 49501", skt)
@ -38,7 +38,7 @@ local function runtest()
local res, err, part = skt:receive('*a') local res, err, part = skt:receive('*a')
res = res or part res = res or part
if res ~= body then print("Received doesn't match send") end if res ~= body then print("Received doesn't match send") end
print("Server reading port 49501... Done!", socket.gettime()-start, err, #res) print("Server reading port 49501... Done!", copas.gettime()-start, err, #res)
copas.removeserver(s2) copas.removeserver(s2)
done = done + 1 done = done + 1
end, sparams)) end, sparams))
@ -52,7 +52,7 @@ local function runtest()
repeat repeat
last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1)
until last_byte_sent == nil or last_byte_sent == #body until last_byte_sent == nil or last_byte_sent == #body
print("Client writing port 49500... Done!", socket.gettime()-start, err, #body) print("Client writing port 49500... Done!", copas.gettime()-start, err, #body)
-- we're not closing the socket, so the Copas GC-when-idle can kick-in to clean up -- we're not closing the socket, so the Copas GC-when-idle can kick-in to clean up
skt = nil -- luacheck: ignore skt = nil -- luacheck: ignore
done = done + 1 done = done + 1
@ -67,7 +67,7 @@ local function runtest()
repeat repeat
last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1) last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1)
until last_byte_sent == nil or last_byte_sent == #body until last_byte_sent == nil or last_byte_sent == #body
print("Client writing port 49501... Done!", socket.gettime()-start, err, #body) print("Client writing port 49501... Done!", copas.gettime()-start, err, #body)
-- we're not closing the socket, so the Copas GC-when-idle can kick-in to clean up -- we're not closing the socket, so the Copas GC-when-idle can kick-in to clean up
skt = nil -- luacheck: ignore skt = nil -- luacheck: ignore
done = done + 1 done = done + 1
@ -77,7 +77,7 @@ local function runtest()
local i = 1 local i = 1
while done ~= 4 do while done ~= 4 do
copas.pause(1) copas.pause(1)
print(i, "seconds:", socket.gettime()-start) print(i, "seconds:", copas.gettime()-start)
i = i + 1 i = i + 1
if i > 60 then if i > 60 then
print"timeout" print"timeout"
@ -97,22 +97,22 @@ runtest() -- run test using regular connection (s/cparams == nil)
-- set ssl parameters and do it again -- set ssl parameters and do it again
sparams = { sparams = {
mode = "server", mode = "server",
protocol = "tlsv1", protocol = "any",
key = "tests/certs/serverAkey.pem", key = "tests/certs/serverAkey.pem",
certificate = "tests/certs/serverA.pem", certificate = "tests/certs/serverA.pem",
cafile = "tests/certs/rootA.pem", cafile = "tests/certs/rootA.pem",
verify = {"peer", "fail_if_no_peer_cert"}, verify = {"peer", "fail_if_no_peer_cert"},
options = {"all", "no_sslv2"}, options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"},
} }
cparams = { cparams = {
mode = "client", mode = "client",
protocol = "tlsv1", protocol = "any",
key = "tests/certs/clientAkey.pem", key = "tests/certs/clientAkey.pem",
certificate = "tests/certs/clientA.pem", certificate = "tests/certs/clientA.pem",
cafile = "tests/certs/rootA.pem", cafile = "tests/certs/rootA.pem",
verify = {"peer", "fail_if_no_peer_cert"}, verify = {"peer", "fail_if_no_peer_cert"},
options = {"all", "no_sslv2"}, options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"},
} }
done = 0 done = 0
start = socket.gettime() start = copas.gettime()
runtest() runtest()

View file

@ -5,7 +5,7 @@ package.path = string.format("../src/?.lua;%s", package.path)
local copas = require "copas" local copas = require "copas"
local Lock = copas.lock local Lock = copas.lock
local gettime = require("socket").gettime local gettime = copas.gettime
local test_complete = false local test_complete = false
copas.loop(function() copas.loop(function()
@ -104,4 +104,106 @@ copas.loop(function()
end) end)
assert(test_complete, "test did not complete!") assert(test_complete, "test did not complete!")
-- Test 2: canceling a queued waiter must not permanently transfer ownership
-- to it, locking out every legitimate waiter behind it forever.
-- See https://github.com/lunarmodules/copas/issues/199
local test2_complete = false
copas.loop(function()
local lock2 = assert(Lock.new(5))
assert(lock2:get()) -- owned by this (the main) coroutine
-- queue a waiter that will be canceled (eg. via future:cancel()) while
-- it is still waiting in line for the lock
local canceled_co = copas.addthread(function()
lock2:get()
end)
-- queue a legitimate waiter behind it
local waiter_result
copas.addthread(function()
local ok, err = lock2:get()
waiter_result = ok and "got it" or err
if ok then
assert(lock2:release())
end
end)
copas.pause(0.1) -- let both threads enqueue behind the lock
copas.removethread(canceled_co) -- simulate external cancellation
assert(lock2:release()) -- should hand the lock to the legitimate waiter
copas.pause(0.1)
assert(waiter_result == "got it",
"expected the legitimate waiter to get the lock, got: "..tostring(waiter_result))
assert(lock2.owner == nil, "expected the lock to be free again")
test2_complete = true
end)
assert(test2_complete, "test 2 did not complete!")
-- Test 3: a failed zero-timeout acquisition must not remain queued.
-- See https://github.com/lunarmodules/copas/issues/201
local test3_complete = false
copas.loop(function()
-- 3a: the probe itself must not leave a stale entry behind
-- (must come from another coroutine: get(0) on the owning coroutine
-- itself takes the reentrant path, not the queuing one)
local lock3 = assert(Lock.new(5))
assert(lock3:get()) -- owner = this (the main) coroutine
local prober_err, prober_wait
copas.addthread(function()
local _, e, w = lock3:get(0) -- non-blocking probe while locked
prober_err, prober_wait = e, w
end)
copas.pause(0.1) -- let the prober run its probe
assert(prober_err == "timeout", "expected immediate timeout, got: "..tostring(prober_err))
assert(prober_wait == 0)
assert(lock3.q_tip == lock3.q_tail,
"expected the failed zero-timeout probe to not be queued, q_tip="
..tostring(lock3.q_tip)..", q_tail="..tostring(lock3.q_tail))
-- 3b: even if the prober coroutine happens to go to sleep afterwards for
-- an unrelated reason, a stale queue entry must not let it hijack the
-- lock next time it is released
local lock4 = assert(Lock.new(5))
assert(lock4:get()) -- owner = this (the main) coroutine
local prober_done = false
copas.addthread(function()
local _, prober_err = lock4:get(0) -- non-blocking probe while locked
assert(prober_err == "timeout", "expected immediate timeout, got: "..tostring(prober_err))
copas.pauseforever() -- unrelated sleep, nothing to do with the lock
prober_done = true
end)
copas.pause(0.1) -- let the prober run its probe and go to sleep
assert(lock4:release()) -- must not hand the lock to the sleeping prober
copas.pause(0.1)
assert(not prober_done, "the prober should still be asleep, unrelated to the lock")
assert(lock4.owner == nil, "expected the lock to be free, not hijacked by the prober")
local waiter_result
copas.addthread(function()
local ok, werr = lock4:get(1)
waiter_result = ok and "got it" or werr
end)
copas.pause(1.2)
assert(waiter_result == "got it",
"expected a legitimate waiter to get the freed lock, got: "..tostring(waiter_result))
test3_complete = true
end)
assert(test3_complete, "test 3 did not complete!")
print("test success!") print("test success!")

44
thirdparty/copas/tests/no_luasocket.lua vendored Normal file
View file

@ -0,0 +1,44 @@
-- make sure we are pointing to the local copas first
package.path = string.format("../src/?.lua;%s", package.path)
print([[
Testing to run Copas without LuaSocket, just LuaSystem
=============================================================================
]])
-- patch require to no longer load luasocket
local _require = require
_G.require = function(name)
if name == "socket" then
error("luasocket is not allowed in this test")
end
return _require(name)
end
local copas = require "copas"
local timer = copas.timer
local successes = 0
local t1 -- luacheck: ignore
copas.loop(function()
t1 = timer.new({
delay = 0.1,
recurring = true,
callback = function(timer_obj, params)
successes = successes + 1 -- 6 to come
if successes == 6 then
timer_obj:cancel()
end
end,
})
-- succes count = 6
end)
assert(successes == 6, "number of successes didn't match! got: "..successes)
print("test success!")

View file

@ -1,9 +1,9 @@
-- make sure we are pointing to the local copas first -- make sure we are pointing to the local copas first
package.path = string.format("../src/?.lua;%s", package.path) package.path = string.format("../src/?.lua;%s", package.path)
local now = require("socket").gettime
local copas = require "copas" local copas = require "copas"
local now = copas.gettime
local Queue = copas.queue local Queue = copas.queue
@ -134,5 +134,61 @@ copas.loop(function()
copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore
q:stop() -- this should exit the idle workers and exit the copas loop q:stop() -- this should exit the idle workers and exit the copas loop
end) end)
print("test 4 success!") print("test 4 success!")
-- finish a queue while workers are idle
copas.loop(function()
local q = Queue:new()
q:add_worker(function() end)
copas.pause(0.5) -- to activate the worker, which will now be blocked on the q semaphore
q:finish() -- this should exit the idle workers and exit the copas loop
end)
print("test 5 success!")
-- finish doesn't return until all workers are done (finished handling the last queue item)
local result = {}
local passed = true
copas.loop(function()
local q = Queue:new()
q:push(1)
q:push(2)
q:push(3)
for i = 1,2 do -- add 2 workers
q:add_worker(function(n)
table.insert(result, "start item " .. n)
copas.pause(0.5)
table.insert(result, "end item " .. n)
end)
end
-- local s = now()
table.insert(result, "start queue")
copas.pause(0.75)
table.insert(result, "start finish")
local ok, err = q:finish()
table.insert(result, "finished "..tostring(ok).." "..tostring(err))
copas.pause(1)
local expected = {
"start queue",
"start item 1",
"start item 2",
"end item 1",
"start item 3",
"end item 2",
"start finish",
"end item 3",
"finished true nil",
}
for i = 1, math.max(#result, #expected) do
if result[i] ~= expected[i] then
for n = 1, math.max(#result, #expected) do
print(n, result[n], expected[n], result[n] == expected[n] and "" or " <--- failed")
end
passed = false
break
end
end
end)
assert(passed, "test 6 failed!")
print("test 6 success!")

View file

@ -5,23 +5,35 @@
local copas = require("copas") local copas = require("copas")
local socket = require("socket") local socket = require("socket")
local wskt = socket.bind("*", 0) local wskt = socket.bind("localhost", 0)
local whost, wport = wskt:getsockname() local whost, wport = wskt:getsockname()
wport = tonumber(wport) wport = tonumber(wport)
-- set up a timeout to not hang on failure
local timeout_timer = copas.timer.new {
delay = 10,
callback = function()
print("timeout!")
os.exit(1)
end
}
local connection_handler = function(cskt)
print(tostring(cskt).." ("..type(cskt)..") received a connection")
local data, _, partial = cskt:receive()
if partial and not data then
data = partial
end
print("triggered", data)
copas.removeserver(wskt, true)
end
local function wait_for_trigger() local function wait_for_trigger()
copas.addserver(wskt, function(cskt) copas.addserver(wskt, copas.handler(connection_handler), "my_TCP_server")
local data, _, partial = cskt:receive()
if partial and not data then
data = partial
end
print("triggered", data)
copas.removeserver(wskt, true)
end)
end end
local function trigger_it(n) local function trigger_it(n)
local cskt = socket.tcp() local cskt = copas.wrap(socket.tcp())
local ok = cskt:connect(whost, wport) local ok = cskt:connect(whost, wport)
if ok then if ok then
cskt:send("hi "..n) cskt:send("hi "..n)
@ -35,6 +47,7 @@ copas.addthread(function()
trigger_it(i) trigger_it(i)
copas.pause(0.1) copas.pause(0.1)
end end
timeout_timer:cancel()
end) end)
copas.loop() copas.loop()

View file

@ -4,7 +4,7 @@
package.path = string.format("../src/?.lua;%s", package.path) package.path = string.format("../src/?.lua;%s", package.path)
local copas = require("copas") local copas = require("copas")
local now = require("socket").gettime local now = copas.gettime
-- Test 1: basic test -- Test 1: basic test

View file

@ -1,9 +1,9 @@
-- make sure we are pointing to the local copas first -- make sure we are pointing to the local copas first
package.path = string.format("../src/?.lua;%s", package.path) package.path = string.format("../src/?.lua;%s", package.path)
local now = require("socket").gettime
local copas = require "copas" local copas = require "copas"
local now = copas.gettime
local semaphore = copas.semaphore local semaphore = copas.semaphore
@ -143,4 +143,252 @@ copas.loop(function()
test_complete = true test_complete = true
end) end)
assert(test_complete, "test did not complete!") assert(test_complete, "test did not complete!")
-- Test 2: canceling a queued waiter must not permanently leak the
-- resources handed to it, nor starve legitimate waiters behind it.
-- See https://github.com/lunarmodules/copas/issues/199 (same root cause
-- as the copas.lock bug: `copas.wakeup()` failures were ignored).
local test2_complete = false
copas.loop(function()
-- 2a: a lone canceled waiter must not leak the resources given to it
local sema2 = semaphore.new(10, 0, 5)
local canceled_co = copas.addthread(function()
sema2:take(5)
end)
copas.pause(0.1) -- let it enqueue
copas.removethread(canceled_co) -- simulate external cancellation
assert(sema2:give(5))
copas.pause(0.1)
assert(sema2:get_count() == 5,
"expected the 5 given resources to remain available, got: "..tostring(sema2:get_count()))
-- 2b: a legitimate waiter behind a canceled one must still be served
local sema3 = semaphore.new(10, 0, 5)
local canceled_co2 = copas.addthread(function()
sema3:take(5)
end)
local waiter_result
copas.addthread(function()
local ok, err = sema3:take(5)
waiter_result = ok and "got it" or err
end)
copas.pause(0.1) -- let both enqueue
copas.removethread(canceled_co2) -- simulate external cancellation
assert(sema3:give(5))
copas.pause(0.1)
assert(waiter_result == "got it",
"expected the legitimate waiter to be served, got: "..tostring(waiter_result))
-- 2c: a canceled waiter requesting MORE than what's available must not
-- block a legitimate waiter behind it that requests less; the canceled
-- entry has to be dropped regardless of resource sufficiency, not just
-- when there happen to be enough resources to satisfy its own request.
local sema4 = semaphore.new(10, 0, 5)
local canceled_co3 = copas.addthread(function()
sema4:take(2) -- will be canceled while waiting for 2
end)
local waiter_result2
copas.addthread(function()
local ok, err = sema4:take(1) -- only needs 1
waiter_result2 = ok and "got it" or err
end)
copas.pause(0.1) -- let both enqueue
copas.removethread(canceled_co3) -- simulate external cancellation
assert(sema4:give(1)) -- not enough for the canceled request (2), plenty for the real one (1)
copas.pause(0.1)
assert(waiter_result2 == "got it",
"expected the smaller legitimate waiter to be served ahead of a stale bigger request, got: "
..tostring(waiter_result2))
assert(sema4:get_count() == 0,
"expected the 1 given resource to have gone to the waiter, got: "..tostring(sema4:get_count()))
test2_complete = true
end)
assert(test2_complete, "test 2 did not complete!")
-- Test 3: get_wait() must not count queued waiters that were canceled
-- externally (eg. via copas.removethread) -- they will never consume the
-- resources handed to them, so counting their `requested` overstates how
-- much is actually needed to release everyone still legitimately waiting.
local test3_complete = false
copas.loop(function()
local sema = semaphore.new(10, 0, 5)
local canceled_co = copas.addthread(function() sema:take(1) end)
copas.addthread(function() sema:take(1) end)
copas.addthread(function() sema:take(1) end)
copas.pause(0.1) -- let all 3 enqueue
assert(sema:get_wait() == 3, "expected all 3 live waiters to be counted")
copas.removethread(canceled_co) -- simulate external cancellation
assert(sema:get_wait() == 2,
"expected the canceled waiter to be excluded, got: "..tostring(sema:get_wait()))
test3_complete = true
end)
assert(test3_complete, "test 3 did not complete!")
-- Test 4: release_all() must release every waiter regardless of how many
-- times `max` it takes to do so (unlike give(math.huge), which is capped
-- at `max` and strands the rest). See https://github.com/lunarmodules/copas/issues/203.
local test4_complete = false
copas.loop(function()
local sema = semaphore.new(2, 0, 5) -- max = 2, well below the waiter count
local released = 0
for _ = 1, 5 do
copas.addthread(function()
assert(sema:take(1))
released = released + 1
end)
end
copas.pause(0.1) -- let all 5 enqueue
sema:release_all()
copas.pause(0.1)
assert(released == 5, "expected all 5 waiters to be released, got: "..tostring(released))
assert(sema:get_count() == 0,
"expected no leftover balance after releasing everyone, got: "..tostring(sema:get_count()))
-- release_all() combined with a canceled waiter mixed into the queue
-- must not leave a stray leftover balance either (that would be the
-- fixed get_wait() bug resurfacing through release_all()).
local sema2 = semaphore.new(2, 0, 5)
local canceled_co = copas.addthread(function() sema2:take(1) end)
local released2 = 0
for _ = 1, 3 do
copas.addthread(function()
assert(sema2:take(1))
released2 = released2 + 1
end)
end
copas.pause(0.1) -- let all 4 enqueue
copas.removethread(canceled_co) -- simulate external cancellation
sema2:release_all()
copas.pause(0.1)
assert(released2 == 3, "expected the 3 legitimate waiters to be released, got: "..tostring(released2))
assert(sema2:get_count() == 0,
"expected no leftover balance, got: "..tostring(sema2:get_count()))
test4_complete = true
end)
assert(test4_complete, "test 4 did not complete!")
-- Test 5: destroy() must release EVERY waiting thread, even when there are
-- more of them than `max`. It used to call give(math.huge), which is
-- capped at `max` and stranded the excess forever -- the same root cause
-- as https://github.com/lunarmodules/copas/issues/203.
local test5_complete = false
copas.loop(function()
local sema = semaphore.new(2, 0, 5) -- max = 2, well below the waiter count
local destroyed_count = 0
for _ = 1, 5 do
copas.addthread(function()
local ok, err = sema:take(1)
if not ok and err == "destroyed" then
destroyed_count = destroyed_count + 1
end
end)
end
copas.pause(0.1) -- let all 5 enqueue
assert(sema:destroy())
copas.pause(0.1)
assert(destroyed_count == 5,
"expected all 5 waiters to be released on destroy, got: "..tostring(destroyed_count))
test5_complete = true
end)
assert(test5_complete, "test 5 did not complete!")
-- Test 6: give() must reject invalid input instead of silently corrupting
-- the resource accounting.
local test6_complete = false
copas.loop(function()
local sema = semaphore.new(10, 0, 5)
-- a negative amount must error, not push `count` negative
local ok = pcall(function() sema:give(-1) end)
assert(not ok, "expected give(-1) to raise an error")
assert(sema:get_count() == 0, "count must be unaffected by the rejected call")
-- math.huge must error too; even on an uncapped semaphore (max = math.huge)
-- it would leave `count` permanently `inf`, since there is no cap left to
-- clamp it back down.
local uncapped = semaphore.new(math.huge, 0, 5)
ok = pcall(function() uncapped:give(math.huge) end)
assert(not ok, "expected give(math.huge) on an uncapped semaphore to raise an error")
assert(uncapped:get_count() == 0, "count must be unaffected by the rejected call")
-- NaN must error too
ok = pcall(function() sema:give(0/0) end)
assert(not ok, "expected give(NaN) to raise an error")
-- 0 remains a valid no-op
assert(sema:give(0))
assert(sema:get_count() == 0)
-- no argument still defaults to 1
assert(sema:give())
assert(sema:get_count() == 1)
test6_complete = true
end)
assert(test6_complete, "test 6 did not complete!")
-- Test 7: take() must reject invalid input instead of silently bypassing
-- `max` and corrupting the resource count.
local test7_complete = false
copas.loop(function()
local sema = semaphore.new(5, 0, 5)
-- a negative request must error, not add to `count` on the fast path
local ok = pcall(function() sema:take(-100) end)
assert(not ok, "expected take(-100) to raise an error")
assert(sema:get_count() == 0, "count must be unaffected by the rejected call, max was 5")
-- 0 must error too; it's not a meaningful amount of resources to request
ok = pcall(function() sema:take(0) end)
assert(not ok, "expected take(0) to raise an error")
-- NaN must error too; it would otherwise queue forever, since
-- `count >= NaN` is always false and can never be satisfied
ok = pcall(function() sema:take(0/0) end)
assert(not ok, "expected take(NaN) to raise an error")
-- no argument still defaults to 1 and works normally
assert(sema:give(1))
assert(sema:take())
assert(sema:get_count() == 0)
test7_complete = true
end)
assert(test7_complete, "test 7 did not complete!")
print("test success!") print("test success!")

View file

@ -15,13 +15,13 @@ local body = ("A"):rep(1024*1024*50) -- 50 mb string
local done = 0 local done = 0
local function runtest() local function runtest()
local s1 = socket.bind('*', 49500) local s1 = socket.bind('localhost', 49500)
copas.addserver(s1, copas.handler(function(skt) copas.addserver(s1, copas.handler(function(skt)
copas.setsocketname("Server 49500", skt) copas.setsocketname("Server 49500", skt)
copas.setthreadname("Server 49500") copas.setthreadname("Server 49500")
print "Server 49500 accepted incoming connection" print "Server 49500 accepted incoming connection"
local end_time = socket.gettime() + 30 -- we run for 30 seconds local end_time = copas.gettime() + 30 -- we run for 30 seconds
while end_time > socket.gettime() do while end_time > copas.gettime() do
local res, err, _ = skt:receive(1) -- single byte from 50mb chunks local res, err, _ = skt:receive(1) -- single byte from 50mb chunks
if res == nil and err ~= "timeout" then if res == nil and err ~= "timeout" then
print("Server 49500 returned: " .. err) print("Server 49500 returned: " .. err)
@ -61,11 +61,11 @@ local function runtest()
copas.addnamedthread("test timeout thread", function() copas.addnamedthread("test timeout thread", function()
local i = 0 local i = 0
local start = socket.gettime() local start = copas.gettime()
while done ~= 2 do while done ~= 2 do
copas.pause(1) -- delta sleep, so it slowly diverges if starved copas.pause(1) -- delta sleep, so it slowly diverges if starved
i = i + 1 i = i + 1
local time_passed = socket.gettime()-start local time_passed = copas.gettime()-start
print("slept "..i.." seconds, time passed: ".. time_passed.." seconds") print("slept "..i.." seconds, time passed: ".. time_passed.." seconds")
if math.abs(i - time_passed) > 2 then if math.abs(i - time_passed) > 2 then
print("timer diverged by more than 2 seconds: failed!") print("timer diverged by more than 2 seconds: failed!")

View file

@ -14,6 +14,7 @@ end
print("Testing platform: " .. platform) print("Testing platform: " .. platform)
_G._TEST = true -- mark as test, to export some internals for testing
local copas = require("copas") local copas = require("copas")
local socket = require("socket") local socket = require("socket")
@ -157,6 +158,189 @@ function tests.receive_timeout()
copas.loop() copas.loop()
end end
-- See issue https://github.com/lunarmodules/copas/issues/223
-- A 0-byte read on a TCP socket is a POSIX no-op: it must return
-- immediately without waiting for the socket to become readable, unlike a
-- normal (>0 byte) read which would sit in the `select` wait set.
function tests.receive_zero_bytes_returns_immediately()
local ip, port = singleuseechoserver()
copas.addthread(function()
local client = socket.tcp()
client = copas.wrap(client)
-- long enough that hitting it would mean the bug is back
client:settimeout(1)
local status, err = client:connect(ip, port)
assert(status, "failed to connect: "..tostring(err))
-- nothing is ever sent by the peer, so a normal read would time out
local start = copas.gettime()
local data, err = client:receive(0)
assert(data == "", "expected an immediate empty read, got: "..tostring(data)..", err: "..tostring(err))
assert(copas.gettime() - start < 0.5, "receive(0) waited on the socket instead of returning immediately")
client:close()
end)
copas.loop()
end
function tests.receive_timeout_clears_copas_timeout()
-- See issue https://github.com/lunarmodules/copas/issues/185
local server = socket.bind("127.0.0.1", 0)
local ip, port = server:getsockname()
local handler_co
copas.addserver(server, function(skt)
handler_co = coroutine.running()
copas.removeserver(server)
skt = copas.wrap(skt)
skt:settimeout(0.01)
local data, err = skt:receive()
assert(data == nil, "somehow recieved data without the client sending")
assert(err == "timeout", "failed with non-timeout error: "..tostring(err))
skt:close()
end)
copas.addthread(function()
local client = socket.tcp()
local status, err = client:connect(ip, port)
assert(status, "failed to connect: "..tostring(err))
copas.pause(0.25)
client:close()
end)
copas.loop()
assert(handler_co, "server handler did not run")
assert(copas._socket_register[handler_co] == nil, "socket_register kept the timed-out coroutine")
assert(copas._operation_register[handler_co] == nil, "operation_register kept the timed-out coroutine")
assert(copas._timeout_flags[handler_co] == nil, "timeout_flags kept the timed-out coroutine")
end
-- See issue https://github.com/lunarmodules/copas/issues/208
-- Two coroutines waiting on the same socket at the same time is a bug in
-- the caller, not something Copas should silently queue for: exactly one
-- of them must win the claim and run to completion, the other must get an
-- immediate "Operation already in progress" error back as a normal return
-- value (the same way it would see "timeout" or "closed"), not be silently
-- abandoned.
--
-- Note: copas.receive/send both have a built-in fairness mechanism (a
-- random chance, each retry, to yield via copas.pause() before re-trying)
-- to stop one busy coroutine from starving others. That means which of the
-- two racing coroutines below actually wins the claim is not deterministic,
-- so the assertions below check the invariant (one winner, one conflict),
-- not which specific coroutine ends up being which.
function tests.duplicate_read_waiter_errors()
local server = socket.bind("127.0.0.1", 0)
local ip, port = server:getsockname()
local results = {}
copas.addserver(server, function(skt)
copas.removeserver(server)
copas.settimeout(skt, 0.1)
local function waiter()
-- nothing is ever sent, so the winner only ever ends via its own
-- timeout; the loser gets the claim-conflict error immediately
local s, err = copas.receive(skt, 10)
results[#results + 1] = { s, err }
end
copas.addthread(waiter)
copas.addthread(waiter)
-- outlive both threads above, otherwise this handler returning would
-- trigger copas.autoclose and close `skt` out from under them before
-- either gets a chance to run
copas.pause(1)
end)
copas.addthread(function()
local client = socket.connect(ip, port)
-- comfortably past the server's read timeout, so there's no race
-- between the timeout and the connection closing
copas.pause(1)
client:close()
end)
copas.loop()
assert(#results == 2, "expected both waiters to finish, got: "..#results)
local conflicts, timeouts = 0, 0
for _, r in ipairs(results) do
if r[1] == nil and r[2] == "Operation already in progress" then
conflicts = conflicts + 1
elseif r[1] == nil and r[2] == "timeout" then
timeouts = timeouts + 1
end
end
assert(conflicts == 1, "expected exactly one claim-conflict result, got: "..conflicts)
assert(timeouts == 1, "expected exactly one normal timeout result, got: "..timeouts)
end
function tests.duplicate_write_waiter_errors()
-- a payload well past default OS socket buffers, sent to a peer that
-- never reads, so send() reliably has to wait rather than complete
-- in one non-blocking call
local body = ("A"):rep(1024 * 1024 * 8)
local server = socket.bind("127.0.0.1", 0)
local ip, port = server:getsockname()
local results = {}
copas.addserver(server, function(skt)
copas.removeserver(server)
-- deliberately never read: keeps the client's send() waiting
copas.pause(2)
copas.close(skt)
end)
copas.addthread(function()
local client = socket.connect(ip, port)
client = copas.wrap(client)
-- comfortably longer than the server's 2 second delay above, so the
-- connection closing is what ends the winner, not a race with its own
-- timeout
client:settimeout(10)
local function waiter()
-- the winner ends once the server above closes the connection; the
-- loser gets the claim-conflict error immediately
local status, err = client:send(body)
results[#results + 1] = { status, err }
end
copas.addthread(waiter)
copas.addthread(waiter)
end)
copas.loop()
assert(#results == 2, "expected both waiters to finish, got: "..#results)
local conflicts, closes = 0, 0
for _, r in ipairs(results) do
if r[1] == nil and r[2] == "Operation already in progress" then
conflicts = conflicts + 1
elseif r[1] == nil and r[2] == "closed" then
closes = closes + 1
end
end
assert(conflicts == 1, "expected exactly one claim-conflict result, got: "..conflicts)
assert(closes == 1, "expected exactly one normal closed result, got: "..closes)
end
-- test "framework" -- test "framework"
for name, test in pairs(tests) do for name, test in pairs(tests) do
print("testing: "..tostring(name)) print("testing: "..tostring(name))

View file

@ -4,7 +4,8 @@ package.path = string.format("../src/?.lua;%s", package.path)
local copas = require "copas" local copas = require "copas"
local gettime = require("socket").gettime local socket = require "socket"
local gettime = copas.gettime
local timer = copas.timer local timer = copas.timer
local successes = 0 local successes = 0
@ -121,7 +122,115 @@ copas.loop(function()
}) })
-- succes count = 15 -- succes count = 15
-- Regression test for https://github.com/lunarmodules/copas/issues/206
-- Cancelling a recurring timer while its callback is yielded on socket I/O
-- must let the in-progress callback resume and finish (just without
-- rescheduling), not abandon it mid-flight.
do
local io_invocations = 0
local io_callback_finished = false
local receiver = socket.udp()
receiver:setsockname("127.0.0.1", 0)
local ip, port = receiver:getsockname()
receiver = copas.wrap(receiver)
local io_timer
io_timer = timer.new({
delay = 0.1,
recurring = true,
callback = function()
io_invocations = io_invocations + 1
local data = receiver:receive() -- yields on socket I/O
assert(data == "wakeup", "expected to receive 'wakeup', got: "..tostring(data))
io_callback_finished = true
end,
})
copas.addthread(function()
-- wait until the timer callback is parked waiting on the socket read
while io_invocations == 0 do
copas.pause(0.01)
end
copas.pause(0.05) -- make sure it actually reached the yield point
assert(io_timer:cancel())
local sender = copas.wrap(socket.udp())
sender:sendto("wakeup", ip, port)
copas.pause(0.2) -- allow the in-progress callback to resume and finish
assert(io_callback_finished, "in-progress callback was abandoned after cancel()")
successes = successes + 1 -- 1 to come
assert(io_invocations == 1, "timer rescheduled after being cancelled")
successes = successes + 1 -- 1 to come
end)
end
-- succes count = 17
end) end)
assert(successes == 15, "number of successes didn't match! got: "..successes) assert(successes == 17, "number of successes didn't match! got: "..successes)
-- Regression test for https://github.com/lunarmodules/copas/issues/213
-- A recurring timer that cancels-and-rearms itself from within its own
-- callback must not leak the cancelled (old-generation) coroutine into the
-- sleeping heap. Each leaked coroutine would sit there for its full delay,
-- consuming a slot until it (eventually) expires -- forever, for a delay of
-- math.huge, as in the original report.
-- Run in its own isolated loop so the sleeping-heap count isn't affected by
-- other tests' timers.
local leaktest_done = false
copas.loop(function()
local rearm_count = 0
local max_rearms = 5
local baseline = copas.status().timer
local leak_timer
leak_timer = timer.new({
delay = 2, -- long enough that a leaked old-generation coroutine is still parked when checked
initial_delay = 0,
recurring = true,
callback = function(timer_obj)
rearm_count = rearm_count + 1
if rearm_count <= max_rearms then
assert(timer_obj:cancel())
assert(timer_obj:arm(0))
else
assert(timer_obj:cancel())
end
end,
})
-- watchdog: without the fix, the leaked coroutines keep the loop alive
-- until their delay expires (or forever, for math.huge). Bound the
-- failure mode instead of letting the test suite hang.
local watchdog
watchdog = timer.new({
delay = 5,
callback = function()
print("timer leak regression test (issue #213) did not complete within 5 seconds")
os.exit(1)
end,
})
copas.addthread(function()
while rearm_count <= max_rearms do
copas.pause(0.01)
end
copas.pause(0.05) -- let everything settle
assert(leak_timer.cancelled, "expected the timer to be cancelled")
local timer_count = copas.status().timer
assert(timer_count <= baseline + 1,
"leaked timer coroutines detected: expected around "..baseline..", got "..timer_count)
watchdog:cancel()
leaktest_done = true
end)
end)
assert(leaktest_done, "timer leak regression test (issue #213) did not complete")
print("test success!") print("test success!")

96
thirdparty/copas/tests/tls-params.lua vendored Normal file
View file

@ -0,0 +1,96 @@
-- test normalization of the 'sslparams' table passed to copas.wrap()/copas.handler()
--
-- Covers the fix for https://github.com/lunarmodules/copas/issues/210:
-- * an empty (or otherwise malformed) sslparams table used to silently
-- disable TLS -- identical to passing nil -- even though the doc-comment
-- claimed it enabled TLS "with defaults". No such defaults exist (a
-- certificate is mandatory), so normalize_sslt() now only recognizes the
-- current {wrap=..., sni=...} shape by the presence of those two keys;
-- anything else (an empty table, a garbage-key table, or the legacy flat
-- luasec-context table) is passed straight through as 'wrap' parameters,
-- which LuaSec itself validates and errors on when actually used for a
-- handshake. This never fails open into silent plaintext.
-- Also checks the other supported shapes still normalize as before: no
-- params at all, the legacy flat luasec-context table, and the current
-- {wrap=..., sni=...} table.
local copas = require("copas")
local socket = require("socket")
local failures = 0
local function check(cond, msg)
if cond then
print("OK: "..msg)
else
print("FAILED: "..msg)
failures = failures + 1
end
end
-- no ssl params at all: TLS fully disabled, no error
do
local skt = copas.wrap(socket.tcp())
check(skt.ssl_params.wrap == false, "nil sslparams: wrap is false")
check(skt.ssl_params.sni == false, "nil sslparams: sni is false")
end
-- empty table: has neither 'wrap' nor 'sni' keys, so it is treated as a
-- (malformed) flat luasec-context table rather than silently disabling TLS.
-- It must never normalize to wrap==false, and using it for a handshake must
-- error rather than silently proceed unencrypted.
do
local sslt = {}
local skt = copas.wrap(socket.tcp(), sslt)
check(skt.ssl_params.wrap == sslt, "empty sslparams table: wrap is the table itself, not false")
check(skt.ssl_params.sni == false, "empty sslparams table: sni is false")
local ok, err = pcall(skt.dohandshake, skt)
check(not ok, "empty sslparams table: handshake throws instead of silently succeeding")
check(tostring(err):match("create") ~= nil,
"empty sslparams table: LuaSec's own error surfaces, got: "..tostring(err))
end
-- table with unrelated/misspelled keys: same as the empty-table case, this
-- must not be mistaken for the current {wrap=..., sni=...} format either.
do
local sslt = { just_some_key = true }
local skt = copas.wrap(socket.tcp(), sslt)
check(skt.ssl_params.wrap == sslt, "garbage-key sslparams table: wrap is the table itself, not false")
check(skt.ssl_params.sni == false, "garbage-key sslparams table: sni is false")
local ok, err = pcall(skt.dohandshake, skt)
check(not ok, "garbage-key sslparams table: handshake throws instead of silently succeeding")
check(tostring(err):match("create") ~= nil,
"garbage-key sslparams table: LuaSec's own error surfaces, got: "..tostring(err))
end
-- legacy flat table (luasec context params directly, no wrap/sni keys)
do
local sslt = {
mode = "client",
protocol = "any",
}
local skt = copas.wrap(socket.tcp(), sslt)
check(skt.ssl_params.wrap == sslt, "legacy sslparams: wrap is the sslparams table itself")
check(skt.ssl_params.sni == false, "legacy sslparams: sni is false")
end
-- current-style table with both wrap and sni set
do
local sslt = {
wrap = { mode = "client", protocol = "any" },
sni = { names = "myhost.com", strict = true },
}
local skt = copas.wrap(socket.tcp(), sslt)
check(skt.ssl_params.wrap == sslt.wrap, "current sslparams: wrap matches provided table")
check(skt.ssl_params.sni == sslt.sni, "current sslparams: sni matches provided table")
end
if failures > 0 then
print(failures.." check(s) failed")
os.exit(1)
end
print("all checks passed")
os.exit(0)

View file

@ -16,15 +16,16 @@ end
local server_params = { local server_params = {
wrap = { wrap = {
mode = "server", mode = "server",
protocol = "tlsv1", protocol = "any",
key = "tests/certs/serverAkey.pem", key = "tests/certs/serverAkey.pem",
certificate = "tests/certs/serverA.pem", certificate = "tests/certs/serverA.pem",
cafile = "tests/certs/rootA.pem", cafile = "tests/certs/rootA.pem",
verify = {"peer", "fail_if_no_peer_cert"}, verify = {"peer", "fail_if_no_peer_cert"},
options = {"all", "no_sslv2"}, options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"},
}, },
sni = { sni = {
strict = true, -- only allow connection 'myhost.com' strict = true, -- reject client-supplied SNI names other than 'myhost.com';
-- clients that send no SNI still connect using the default context
names = {} names = {}
} }
} }
@ -33,12 +34,12 @@ server_params.sni.names["myhost.com"] = ssl.newcontext(server_params.wrap)
local client_params = { local client_params = {
wrap = { wrap = {
mode = "client", mode = "client",
protocol = "tlsv1", protocol = "any",
key = "tests/certs/clientAkey.pem", key = "tests/certs/clientAkey.pem",
certificate = "tests/certs/clientA.pem", certificate = "tests/certs/clientA.pem",
cafile = "tests/certs/rootA.pem", cafile = "tests/certs/rootA.pem",
verify = {"peer", "fail_if_no_peer_cert"}, verify = {"peer", "fail_if_no_peer_cert"},
options = {"all", "no_sslv2"}, options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"},
}, },
sni = { sni = {
names = "" -- will be added in test below names = "" -- will be added in test below
@ -63,11 +64,11 @@ local function echoHandler(skt)
return -- close this client connection, after stopping the server return -- close this client connection, after stopping the server
end end
skt:send(data) skt:send(data.."\n")
end end
end end
server = assert(socket.bind("*", port)) server = assert(socket.bind("localhost", port))
copas.addserver(server, copas.handler(echoHandler, server_params)) copas.addserver(server, copas.handler(echoHandler, server_params))
copas.addthread(function() copas.addthread(function()
@ -92,12 +93,16 @@ copas.addthread(function()
local skt = copas.wrap(socket.tcp(), client_params) local skt = copas.wrap(socket.tcp(), client_params)
local success, ok = pcall(skt.connect, skt, "localhost", port) local success, ok = pcall(skt.connect, skt, "localhost", port)
if not (success and ok) then if not (success and ok) then
print "expected connection to be completed" print("expected connection to be completed", success, ok)
os.exit(1) os.exit(1)
end end
assert(skt:send("hello world\n"))
assert(skt:receive() == "hello world")
print "succesfully completed test" print "succesfully completed test"
os.exit(0)
-- send exit signal to server
skt:send("exit\n")
end) end)
-- no ugly errors please, comment out when debugging -- no ugly errors please, comment out when debugging

53
thirdparty/copas/tests/wakeup.lua vendored Normal file
View file

@ -0,0 +1,53 @@
-- Test for copas.wakeup() return value.
-- See https://github.com/lunarmodules/copas/issues/199
--
-- copas.wakeup() must report whether it actually woke a coroutine, so
-- callers (eg. copas.lock) can tell a stale/canceled coroutine from a
-- successfully woken one instead of assuming success unconditionally.
-- make sure we are pointing to the local copas first
package.path = string.format("../src/?.lua;%s", package.path)
local copas = require "copas"
local test_complete = false
copas.loop(function()
-- waking a coroutine that is actually paused succeeds
local resumed = false
local co = copas.addthread(function()
copas.pauseforever()
resumed = true
end)
copas.pause() -- let 'co' actually reach pauseforever() before waking it
local ok, err = copas.wakeup(co)
assert(ok == true, "expected wakeup to return true, got: "..tostring(ok)..", "..tostring(err))
copas.pause(0.1)
assert(resumed, "expected the woken thread to have resumed")
-- waking it again (it's no longer sleeping) fails
local ok2, err2 = copas.wakeup(co)
assert(ok2 == nil, "expected re-waking a finished coroutine to fail")
assert(type(err2) == "string", "expected an error message, got: "..tostring(err2))
-- waking a coroutine that was canceled (eg. copas.removethread, or
-- future:cancel()) while sleeping fails, instead of silently no-op'ing
local co2 = copas.addthread(function()
copas.pauseforever()
end)
copas.removethread(co2)
local ok3, err3 = copas.wakeup(co2)
assert(ok3 == nil, "expected wakeup of a canceled coroutine to fail")
assert(type(err3) == "string", "expected an error message, got: "..tostring(err3))
-- waking a coroutine copas doesn't know about at all fails
local co3 = coroutine.create(function() end)
local ok4, err4 = copas.wakeup(co3)
assert(ok4 == nil, "expected wakeup of an unrelated coroutine to fail")
assert(type(err4) == "string", "expected an error message, got: "..tostring(err4))
test_complete = true
end)
assert(test_complete, "test did not complete!")
print("test success!")

View file

@ -1,2 +0,0 @@
*.pnm -diff -text
tests/ref/fate/sub-scc eol=crlf

View file

@ -1,43 +0,0 @@
*.a
*.o
*.o.*
*.d
*.def
*.dll
*.dylib
*.exe
*.exp
*.gcda
*.gcno
*.h.c
*.ilk
*.lib
*.pc
*.pdb
*.so
*.so.*
*.swp
*.ver
*.version
*.metal.air
*.metallib
*.metallib.c
*.ptx
*.ptx.c
*.ptx.gz
*_g
\#*
.\#*
/.config
/.version
/ffmpeg
/ffplay
/ffprobe
/config.asm
/config.h
/config_components.h
/coverage.info
/lcov/
/src
/mapfile
/tools/python/__pycache__/

View file

@ -1,8 +1,8 @@
<jeebjp@gmail.com> <jan.ekstrom@aminocom.com> <jeebjp@gmail.com> <jan.ekstrom@aminocom.com>
<sw@jkqxz.net> <mrt@jkqxz.net> <sw@jkqxz.net> <mrt@jkqxz.net>
<u@pkh.me> <cboesch@gopro.com> <u@pkh.me> <cboesch@gopro.com>
<zhilizhao@tencent.com> <quinkblack@foxmail.com> <quinkblack@foxmail.com> <wantlamy@gmail.com>
<zhilizhao@tencent.com> <wantlamy@gmail.com> <quinkblack@foxmail.com> <zhilizhao@tencent.com>
<modmaker@google.com> <modmaker-at-google.com@ffmpeg.org> <modmaker@google.com> <modmaker-at-google.com@ffmpeg.org>
<stebbins@jetheaddev.com> <jstebbins@jetheaddev.com> <stebbins@jetheaddev.com> <jstebbins@jetheaddev.com>
<barryjzhao@tencent.com> <mypopydev@gmail.com> <barryjzhao@tencent.com> <mypopydev@gmail.com>
@ -17,8 +17,14 @@
<atomnuker@gmail.com> <rpehlivanov@obe.tv> <atomnuker@gmail.com> <rpehlivanov@obe.tv>
<lizhong1008@gmail.com> <zhong.li@intel.com> <lizhong1008@gmail.com> <zhong.li@intel.com>
<lizhong1008@gmail.com> <zhongli_dev@126.com> <lizhong1008@gmail.com> <zhongli_dev@126.com>
<andreas.rheinhardt@gmail.com> <andreas.rheinhardt@googlemail.com> <andreas.rheinhardt@outlook.com> <andreas.rheinhardt@gmail.com>
<andreas.rheinhardt@outlook.com> <andreas.rheinhardt@googlemail.com>
rcombs <rcombs@rcombs.me> <rodger.combs@gmail.com> rcombs <rcombs@rcombs.me> <rodger.combs@gmail.com>
<thilo.borgmann@mail.de> <thilo.borgmann@googlemail.com> <thilo.borgmann@mail.de> <thilo.borgmann@googlemail.com>
<liuqi05@kuaishou.com> <lq@chinaffmpeg.org> <lq@chinaffmpeg.org> <liuqi05@kuaishou.com>
<ruiling.song83@gmail.com> <ruiling.song@intel.com> <ruiling.song83@gmail.com> <ruiling.song@intel.com>
Cosmin Stejerean <cosmin@cosmin.at> Cosmin Stejerean via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
<wutong1208@outlook.com> <tong1.wu-at-intel.com@ffmpeg.org>
<wutong1208@outlook.com> <tong1.wu@intel.com>
<toqsxw@outlook.com> <jianhua.wu-at-intel.com@ffmpeg.org>
<toqsxw@outlook.com> <jianhua.wu@intel.com>

View file

@ -1,30 +0,0 @@
language: c
sudo: false
os:
- linux
- osx
addons:
apt:
packages:
- nasm
- diffutils
compiler:
- clang
- gcc
matrix:
exclude:
- os: osx
compiler: gcc
cache:
directories:
- ffmpeg-samples
before_install:
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update; fi
install:
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install nasm; fi
script:
- mkdir -p ffmpeg-samples
- ./configure --samples=ffmpeg-samples --cc=$CC
- make -j 8
- make fate-rsync
- make check -j 8

View file

@ -1,4 +1,5 @@
# Note to Github users # Note to Github users
Patches should be submitted to the [ffmpeg-devel mailing list](https://ffmpeg.org/mailman/listinfo/ffmpeg-devel) using `git format-patch` or `git send-email`. Github pull requests should be avoided because they are not part of our review process and **will be ignored**.
Patches should be submitted to [Forgejo](https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls) or the [ffmpeg-devel mailing list](https://ffmpeg.org/mailman/listinfo/ffmpeg-devel) using `git format-patch` or `git send-email`. Github pull requests should be avoided because they are not part of our review process and **will be ignored**.
See [https://ffmpeg.org/developer.html#Contributing](https://ffmpeg.org/developer.html#Contributing) for more information. See [https://ffmpeg.org/developer.html#Contributing](https://ffmpeg.org/developer.html#Contributing) for more information.

View file

@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be author's reputation will not be affected by problems that might be
introduced by others. introduced by others.
Finally, software patents pose a constant threat to the existence of Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a effectively restrict the users of a free program by obtaining a
@ -111,7 +111,7 @@ modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The "work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must former contains code derived from the library, whereas the latter must
be combined with the library in order to run. be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
@ -158,7 +158,7 @@ Library.
You may charge a fee for the physical act of transferring a copy, You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a and you may at your option offer warranty protection in exchange for a
fee. fee.
2. You may modify your copy or copies of the Library or any portion 2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1 distribute such modifications or work under the terms of Section 1
@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in that version instead if you wish.) Do not make any other change in
these notices. these notices.
Once this change is made in a given copy, it is irreversible for Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy. subsequent copies and derivative works made from that copy.
@ -267,7 +267,7 @@ Library will still fall under Section 6.)
distribute the object code for the work under the terms of Section 6. distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6, Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself. whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or 6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work work containing portions of the Library, and distribute that work
@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you use both them and the Library together in an executable that you
distribute. distribute.
7. You may place library facilities that are a work based on the 7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined facilities not covered by this License, and distribute such a combined
@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein. restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with You are not responsible for enforcing compliance by third parties with
this License. this License.
11. If, as a consequence of a court judgment or allegation of patent 11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues), infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or conditions are imposed on you (whether by court order, agreement or
@ -422,7 +422,7 @@ conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by license version number, you may choose any version ever published by
the Free Software Foundation. the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free 14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these, programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is write to the author to ask for permission. For software which is
@ -456,7 +456,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES. DAMAGES.
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest If you develop a new library, and you want it to be of the greatest

View file

@ -1,7 +1,275 @@
Entries are sorted chronologically from oldest to youngest within each release, Entries are sorted chronologically from oldest to youngest within each release,
releases are sorted from youngest to oldest. releases are sorted from youngest to oldest.
version <next>: version 9.0.1:
Bump for 9.0.1
avcodec/lcldec: clear what the multithread chunks leave undecoded
avformat/mlv: end the LJ92 packet where its data ends
avformat/mpegts: reject a max_packet_size below one TS payload
avformat/mpegts: keep the PES payload within max_packet_size
avfilter/spectrumsynth: negotiate one pixel format for both inputs
avfilter/afir: bound the crossfades by the samples of the input frame
Update for 9.0.1
avformat/webp_anim_dec: use ffio_read_size for ICCP and EXIF chunk
avformat/webp_anim_dec: use ffio_read_size for ICCP and EXIF chunks
avformat/rtpenc_av1: do not narrow the OBU size to (long)
avformat/rtpenc_av1: Check num_lebs
avformat/rtpenc_av1: bound OBU size in the keyframe search loop
avformat/gopher: Fix CRLF injection
avformat/rtpenc_vc2hq: reject data units larger than the RTP payload buffer
avformat/dashdec: reject a negative fragment index
avformat/hevc: reject hvcC NAL arrays that overflow the 16-bit count
avformat/mpegenc: reject stream counts that overflow the system header
avformat/mpegenc: pass buffer size into put_system_header()
avformat/librist: honor the caller buffer size in librist_read
avformat/scd: reject zero-channel tracks
avcodec/dolby_e: Add error recovery when parse_mantissas run out of bits
avfilter/af_pan: check the id of named input channels before use
swscale: avoid overflow in fast bilinear edge handling
avformat/shared: Use correct printf specifier
Revert "lavfi/bwdif: fix heap-buffer-overflow with small height videos"
avfilter/vf_bwdif: fix line boundary checks for >8 bits content
avformat/rawutils: reject raw RGB frames that do not fit an AVPacket
avcodec/nvenc: write AV1 timecode metadata in AV1 syntax
avcodec/utils: add ff_alloc_timecode_metadata_av1()
avcodec/utils: factor the timecode fields out of ff_alloc_timecode_sei
vulkan_encode_av1: set primary_ref_frame to a reference name, not a slot
avformat/mov: reject a trun sample count the input cannot hold
RELEASE_NOTES: Based on the version from 8.0
avformat/mp3enc: fix underflow of the LAME encoder delay
avformat/os_support: fix return value of win32_rename
vulkan_encode: fix leak and swallowed errors in init_base_units()
avfilter/dnn: fix async teardown race condition in all backends
avcodec/tiff: reject inflate output shorter than the strip
avformat/dashdec: check NULL pointer before use str_end_offset
avformat/dashdec: check NULL pointer of av_strtok value before use it
avfilter/vf_scale_cuda: fix non-scaling format conversion
fftools/opt_common: fix format string bug in print_program_info
avformat/dashdec: fix integer truncation in calc_max_seg_no()
swscale/x86: fix SIGILL in u8 SCALE on SSE4-only CPUs
avdevice/android_camera: fix OOB read in metadata parsing
avformat/dashdec: don't stop at the first input EOF
avformat/iamf_parse: check that num_sub_mixes and num_audio_elements in Mix Presentations are not zero
avformat/iamf_parse: bound the output mix gain duration by the audio elements
avcodec/cbs_h266: size vps_direct_ref_layer_flag for the full layer range
avcodec/vulkan/ffv1_dec_setup: act on the slice header rejection
avcodec/ffv1dec: reject a remap that produces zero entries
avcodec/ffv1dec: mark the slice damaged when its remap fails
avcodec/vulkan/ffv1_dec_setup: bound the fltmap write
avcodec/vulkan/ffv1_dec_setup: test mul_count as unsigned
avcodec/vulkan/ffv1_dec_setup: reject a remap that produces zero entries
avcodec/pgssubdec: always give an output rect a palette
scale_d3d11: Fix hw_frame_ctx reference leak
avcodec/cfhd: reject transform-2 output wider than the plane
avformat/rtsp: clear authentication on cross-origin redirects
avcodec/dovi_rpuenc: normalize vdr_dm_metadata_present to 0/1
avcodec/dovi_rpuenc: validate vdr_rpu_id from the input metadata
avcodec/bsf/dovi_rpu: handle update_rpu() returning no RPU
avcodec/dovi_rpudec: bound num_x/y_partitions
avcodec/dovi_rpuenc: validate the data mapping before generation
avfilter/af_arnndn: pad the DCT input buffers to the read length
doc/nut.texi: point at the latest spec in the git repository
avformat/mov: bound sgpd sync entry_count by the atom size
avcodec/rscc: do not leave uninitilized data when the input is too short
avcodec/dvbsub_parser: avoid signed overflow in the capacity check
avformat/tls_openssl: bind peer identity for numeric-IP verify
avformat/codec2: avoid integer overflow in packet size and duration
avfilter/vf_xpsnr: avoid a zero block size on small frames
avcodec/cbs_av1: pad the ITU-T T.35 payload buffer
avcodec/screenpresso: reject deflate output shorter than the frame
avformat/hls: Enforce protocol checks when opening child playlists
avformat/hlsenc: Fix heap buffer overflow in parse_playlist()
avformat/hlsenc: Handle extensionless URIs in extract_segment_number()
avfilter/vf_lut3d: do not compute size*size before the size is validated
avfilter/vf_hqdn3d: support dynamic frame sizes
avfilter/vf_hqdn3d: reject unsupported frame parameter changes
avcodec/bsf/truehd_core: clear profile value on init()
avcodec/bsf/eac3_core: clear profile value on init()
avformat/iamf_parse: fix inverted subblock duration validation
avcodec/get_buffer: use frame pixel format instead of context
avformat/lcevc: add a log context parameter to all functions
version 9.0:
- Extend AMF Color Converter (vf_vpp_amf) HDR capabilities
- LCEVC track muxing support in MP4 muxer
- Playdate video encoder and muxer
- Add v360_vulkan filter
- HE-AAC 960 decoding (DAB+)
- transpose_cuda filter
- Add AMF Frame Rate Converter (vf_frc_amf) filter
- SMPTE 2094-50 metadata support and passthrough
- ProRes RAW VideoToolbox hwaccel
- APV Vulkan hwaccel
- Animated WebP decoder
- Animated WebP demuxer
- Remove CELT decoding support (doesn't affect Opus CELT)
- Remove ogg/celt parsing
- Bitstream filter to split Dolby Vision multi-layer HEVC
- Add AMF hardware memory mapping support.
- ONNX Runtime DNN backend with GPU execution provider support
- Remove deprecated NVENC options and support for pre-11.1 SDK versions
version 8.1:
- ffprobe -codec option
- EXIF Metadata Parsing
- gfxcapture: Windows.Graphics.Capture based window/monitor capture
- hxvs demuxer for HXVS/HXVT IP camera format
- MPEG-H 3D Audio decoding via mpeghdec
- D3D12 H.264 encoder
- drawvg filter via libcairo
- ffmpeg CLI tiled HEIF support
- D3D12 AV1 encoder
- ProRes Vulkan hwaccel
- DPX Vulkan hwaccel
- Rockchip H.264/HEVC hardware encoder
- Add vf_scale_d3d12 filter
- JPEG-XS parser
- JPEG-XS decoder and encoder through libsvtjpegxs
- JPEG-XS raw bitstream muxer and demuxer
- IAMF Projection mode Ambisonic Audio Elements muxing and demuxing
- Add vf_mestimate_d3d12 filter
- xHE-AAC Mps212 decoding support (experimental)
- Remove the old HLS protocol handler
- Vulkan compute codec optimizations
- swscale Vulkan support
- LCEVC metadata bitstream filter
- Add vf_deinterlace_d3d12 filter
- ffprobe: only show refs field in stream section when reading frames
- ProRes Vulkan encoder
- LCEVC parser
- LCEVC enhancement layer exporting in MPEG-TS
version 8.0:
- Whisper filter
- Drop support for OpenSSL < 1.1.0
- Enable TLS peer certificate verification by default (on next major version bump)
- Drop support for OpenSSL < 1.1.1
- yasm support dropped, users need to use nasm
- VVC VAAPI decoder
- RealVideo 6.0 decoder
- OpenMAX encoders deprecated
- libx265 alpha layer encoding
- ADPCM IMA Xbox decoder
- Enhanced FLV v2: Multitrack audio/video, modern codec support
- Animated JPEG XL encoding (via libjxl)
- VVC in Matroska
- CENC AV1 support in MP4 muxer
- pngenc: set default prediction method to PAETH
- APV decoder and APV raw bitstream muxing and demuxing
- APV parser
- APV encoding support through a libopenapv wrapper
- VVC decoder supports all content of SCC (Screen Content Coding):
IBC (Inter Block Copy), Palette Mode and ACT (Adaptive Color Transform
- G.728 decoder
- pad_cuda filter
- Sanyo LD-ADPCM decoder
- APV in MP4/ISOBMFF muxing and demuxing
- OpenHarmony hardware decoder/encoder
- Colordetect filter
- Add vf_scale_d3d11 filter
- No longer disabling GCC autovectorization, on X86, ARM and AArch64
- VP9 Vulkan hwaccel
- AV1 Vulkan encoder
- ProRes RAW decoder
- ProRes RAW Vulkan hwaccel
- ffprobe -codec option
- HDR10+ metadata passthrough when decoding/encoding with libaom-av1
version 7.1:
- Raw Captions with Time (RCWT) closed caption demuxer
- LC3/LC3plus decoding/encoding using external library liblc3
- ffmpeg CLI filtergraph chaining
- LC3/LC3plus demuxer and muxer
- pad_vaapi, drawbox_vaapi filters
- vf_scale supports secondary ref input and framesync options
- vf_scale2ref deprecated
- qsv_params option added for QSV encoders
- VVC decoder compatible with DVB test content
- xHE-AAC decoder
- removed DEC Alpha DSP and support code
- VVC encoding support via libvvenc
- perlin video source
- D3D12VA HEVC encoder
- Cropping metadata parsing and writing in Matroska and MP4/MOV de/muxers
- Intel QSV-accelerated VVC decoding
- MediaCodec AAC/AMR-NB/AMR-WB/MP3 decoding
- YUV colorspace negotiation for codecs and filters, obsoleting the
YUVJ pixel format
- Vulkan H.264 encoder
- Vulkan H.265 encoder
- stream specifiers in fftools can now match by stream disposition
- LCEVC enhancement data exporting in H.26x and MP4/ISOBMFF
- LCEVC filter
- MV-HEVC decoding
- minor stream specifier syntax changes:
- when matching by metadata (:m:<key>:<val>), the colon character
in keys or values now has to be backslash-escaped
- in optional maps (-map ....?) with a metadata-matching stream specifier,
the value has to be separated from the question mark by a colon, i.e.
-map ....:m:<key>:<val>:? (otherwise it would be ambiguous whether the
question mark is a part of <val> or not)
- multiple stream types in a single specifier (e.g. :s:s:0) now cause an
error, as such a specifier makes no sense
- Mastering Display and Content Light Level metadata support in hevc_nvenc
and av1_nvenc encoders
- libswresample now accepts custom order channel layouts as input, with some
constrains
- FFV1 parser
version 7.0:
- DXV DXT1 encoder
- LEAD MCMP decoder
- EVC decoding using external library libxevd
- EVC encoding using external library libxeve
- QOA decoder and demuxer
- aap filter
- demuxing, decoding, filtering, encoding, and muxing in the
ffmpeg CLI now all run in parallel
- enable gdigrab device to grab a window using the hwnd=HANDLER syntax
- IAMF raw demuxer and muxer
- D3D12VA hardware accelerated H264, HEVC, VP9, AV1, MPEG-2 and VC1 decoding
- tiltandshift filter
- qrencode filter and qrencodesrc source
- quirc filter
- lavu/eval: introduce randomi() function in expressions
- VVC decoder (experimental)
- fsync filter
- Raw Captions with Time (RCWT) closed caption muxer
- ffmpeg CLI -bsf option may now be used for input as well as output
- ffmpeg CLI options may now be used as -/opt <path>, which is equivalent
to -opt <contents of file <path>>
- showinfo bitstream filter
- a C11-compliant compiler is now required; note that this requirement
will be bumped to C17 in the near future, so consider updating your
build environment if it lacks C17 support
- Change the default bitrate control method from VBR to CQP for QSV encoders.
- removed deprecated ffmpeg CLI options -psnr and -map_channel
- DVD-Video demuxer, powered by libdvdnav and libdvdread
- ffprobe -show_stream_groups option
- ffprobe (with -export_side_data film_grain) now prints film grain metadata
- AEA muxer
- ffmpeg CLI loopback decoders
- Support PacketTypeMetadata of PacketType in enhanced flv format
- ffplay with hwaccel decoding support (depends on vulkan renderer via libplacebo)
- dnn filter libtorch backend
- Android content URIs protocol
- AOMedia Film Grain Synthesis 1 (AFGS1)
- RISC-V optimizations for AAC, FLAC, JPEG-2000, LPC, RV4.0, SVQ, VC1, VP8, and more
- Loongarch optimizations for HEVC decoding
- Important AArch64 optimizations for HEVC
- IAMF support inside MP4/ISOBMFF
- Support for HEIF/AVIF still images and tiled still images
- Dolby Vision profile 10 support in AV1
- Support for Ambient Viewing Environment metadata in MP4/ISOBMFF
- HDR10 metadata passthrough when encoding with libx264, libx265, and libsvtav1
version 6.1:
- libaribcaption decoder - libaribcaption decoder
- Playdate video decoder and demuxer - Playdate video decoder and demuxer
- Extend VAAPI support for libva-win32 on Windows - Extend VAAPI support for libva-win32 on Windows
@ -35,7 +303,11 @@ version <next>:
- CRI USM demuxer - CRI USM demuxer
- ffmpeg CLI '-top' option deprecated in favor of the setfield filter - ffmpeg CLI '-top' option deprecated in favor of the setfield filter
- VAAPI AV1 encoder - VAAPI AV1 encoder
- ffprobe XML output schema changed to account for multiple
variable-fields elements within the same parent element
- ffprobe -output_format option added as an alias of -of
# codespell:off
version 6.0: version 6.0:
- Radiance HDR image support - Radiance HDR image support

7
thirdparty/ffmpeg/FUNDING.json vendored Normal file
View file

@ -0,0 +1,7 @@
{
"drips": {
"ethereum": {
"ownedBy": "0x2f3900e7064eE63D30d749971265858612AA7139"
}
}
}

View file

@ -1,4 +1,7 @@
## Installing FFmpeg # Installing FFmpeg
0. If you like to include source plugins, merge them before configure
for example run tools/merge-all-source-plugins
1. Type `./configure` to create the configuration. A list of configure 1. Type `./configure` to create the configuration. A list of configure
options is printed by running `configure --help`. options is printed by running `configure --help`.
@ -11,7 +14,11 @@ path when launching `configure`, e.g. `/ffmpegdir/ffmpeg/configure`.
3. Type `make install` to install all binaries and libraries you built. 3. Type `make install` to install all binaries and libraries you built.
NOTICE ## NOTICE
------
- Non system dependencies (e.g. libx264, libvpx) are disabled by default. - Non system dependencies (e.g. libx264, libvpx) are disabled by default.
## NOTICE for Package Maintainers
- It is recommended to build FFmpeg twice, first with minimal external dependencies so that 3rd party packages, which depend on FFmpegs libavutil/libavfilter/libavcodec/libavformat
can then be built. And last build FFmpeg with full dependencies (which may in turn depend on some of these 3rd party packages). This avoids circular dependencies during build.

View file

@ -12,7 +12,6 @@ configure to activate them. In this case, FFmpeg's license changes to GPL v2+.
Specifically, the GPL parts of FFmpeg are: Specifically, the GPL parts of FFmpeg are:
- libpostproc
- optional x86 optimization in the files - optional x86 optimization in the files
- `libavcodec/x86/flac_dsp_gpl.asm` - `libavcodec/x86/flac_dsp_gpl.asm`
- `libavcodec/x86/idct_mmx.c` - `libavcodec/x86/idct_mmx.c`
@ -45,7 +44,6 @@ Specifically, the GPL parts of FFmpeg are:
- `vf_owdenoise.c` - `vf_owdenoise.c`
- `vf_perspective.c` - `vf_perspective.c`
- `vf_phase.c` - `vf_phase.c`
- `vf_pp.c`
- `vf_pp7.c` - `vf_pp7.c`
- `vf_pullup.c` - `vf_pullup.c`
- `vf_repeatfields.c` - `vf_repeatfields.c`

View file

@ -6,10 +6,26 @@ FFmpeg code.
Please try to keep entries where you are the maintainer up to date! Please try to keep entries where you are the maintainer up to date!
Names in () mean that the maintainer currently has no time to maintain the code. *Status*, one of the following:
[X] Old code. Something tagged obsolete generally means it has been replaced by a better system and you should be using that.
[0] No current maintainer [but maybe you could take the role as you write your new code].
[1] It has a maintainer but they don't have time to do much other than throw the odd patch in.
[2] Someone actually looks after it.
A (CC <address>) after the name means that the maintainer prefers to be CC-ed on A (CC <address>) after the name means that the maintainer prefers to be CC-ed on
patches and related discussions. patches and related discussions.
(L <address>) *Mailing list* that is relevant to this area
(W <address>) *Web-page* with status/info
(B <address>) URI for where to file *bugs*. A web-page with detailed bug
filing info, a direct bug tracker link, or a mailto: URI.
(P <address>) *Subsystem Profile* document for more details submitting
patches to the given subsystem. This is either an in-tree file,
or a URI. See Documentation/maintainer/maintainer-entry-profile.rst
for details.
(T <address>) *SCM* tree type and location.
Type is one of: git, hg, quilt, stgit, topgit
Applications Applications
============ ============
@ -18,10 +34,10 @@ ffmpeg:
ffmpeg.c Michael Niedermayer, Anton Khirnov ffmpeg.c Michael Niedermayer, Anton Khirnov
ffplay: ffplay:
ffplay.c Marton Balint ffplay.c [2] Marton Balint
ffprobe: ffprobe:
ffprobe.c Stefano Sabatini ffprobe.c [2] Stefano Sabatini
Commandline utility code: Commandline utility code:
cmdutils.c, cmdutils.h Michael Niedermayer cmdutils.c, cmdutils.h Michael Niedermayer
@ -29,31 +45,33 @@ Commandline utility code:
QuickTime faststart: QuickTime faststart:
tools/qt-faststart.c Baptiste Coudurier tools/qt-faststart.c Baptiste Coudurier
Execution Graph Printing
fftools/graph, fftools/resources [2] softworkz
Miscellaneous Areas Miscellaneous Areas
=================== ===================
documentation Stefano Sabatini, Mike Melanson, Timothy Gu, Gyan Doshi documentation Stefano Sabatini, Mike Melanson, Timothy Gu, Gyan Doshi
project server day to day operations Árpád Gereöffy, Michael Niedermayer, Reimar Doeffinger, Alexander Strasser, Nikolay Aleksandrov project server day to day operations (L: root@ffmpeg.org) Michael Niedermayer, Reimar Doeffinger, Alexander Strasser, Nikolay Aleksandrov, Timo Rothenpieler
project server emergencies Árpád Gereöffy, Reimar Doeffinger, Alexander Strasser, Nikolay Aleksandrov project server emergencies (L: root@ffmpeg.org) Reimar Doeffinger, Alexander Strasser, Nikolay Aleksandrov, Timo Rothenpieler
presets Robert Swain presets [0]
metadata subsystem Aurelien Jacobs metadata subsystem Aurelien Jacobs
release management Michael Niedermayer release management Michael Niedermayer
API tests Ludmila Glinskih API tests [0]
samples-request [2] Thilo Borgmann, James Almer, Ben Littler
Communication Communication
============= =============
website (T: https://git.ffmpeg.org/ffmpeg-web) Deby Barbara Lepage
website Deby Barbara Lepage fate.ffmpeg.org (L: fate-admin@ffmpeg.org) (W: https://fate.ffmpeg.org) (P: https://ffmpeg.org/fate.html) (S: https://git.ffmpeg.org/fateserver) Timo Rothenpieler
fate.ffmpeg.org Timothy Gu Trac bug tracker [X] (W: https://trac.ffmpeg.org) Alexander Strasser, Michael Niedermayer, Carl Eugen Hoyos
Trac bug tracker Alexander Strasser, Michael Niedermayer, Carl Eugen Hoyos Forgejo issue tracker (W: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues)
Patchwork Andriy Gelman Patchwork [2] (W: https://patchwork.ffmpeg.org) Andriy Gelman
mailing lists Baptiste Coudurier mailing lists (W: https://ffmpeg.org/contact.html#MailingLists) Baptiste Coudurier
Twitter Reynaldo H. Verdejo Pinochet Twitter Reynaldo H. Verdejo Pinochet
Launchpad Timothy Gu Launchpad Timothy Gu
ffmpeg-security Andreas Cadhalpun, Carl Eugen Hoyos, Clément Bœsch, Michael Niedermayer, Reimar Doeffinger, rcombs, wm4 ffmpeg-security [2] (L: ffmpeg-security@ffmpeg.org) (W: https://ffmpeg.org/security.html) Michael Niedermayer, Reimar Doeffinger
libavutil libavutil
========= =========
@ -67,24 +85,27 @@ Other:
aes_ctr.c, aes_ctr.h Eran Kornblau aes_ctr.c, aes_ctr.h Eran Kornblau
bprint Nicolas George bprint Nicolas George
bswap.h bswap.h
csp.c, csp.h Leo Izen, Ronald S. Bultje
des Reimar Doeffinger des Reimar Doeffinger
dynarray.h Nicolas George dynarray.h Nicolas George
eval.c, eval.h Michael Niedermayer eval.c, eval.h [2] Michael Niedermayer
float_dsp Loren Merritt float_dsp Loren Merritt
hash Reimar Doeffinger hash Reimar Doeffinger
hwcontext_cuda* Timo Rothenpieler hwcontext_cuda* Timo Rothenpieler
hwcontext_vulkan* Lynne hwcontext_d3d12va* Wu Jianhua
hwcontext_oh* Zhao Zhili
hwcontext_vulkan* [2] Lynne
intfloat* Michael Niedermayer intfloat* Michael Niedermayer
integer.c, integer.h Michael Niedermayer integer.c, integer.h Michael Niedermayer
lzo Reimar Doeffinger lzo Reimar Doeffinger
mathematics.c, mathematics.h Michael Niedermayer mathematics.c, mathematics.h [2] Michael Niedermayer
mem.c, mem.h Michael Niedermayer mem.c, mem.h [2] Michael Niedermayer
opencl.c, opencl.h Wei Gao opencl.c, opencl.h Wei Gao
opt.c, opt.h Michael Niedermayer opt.c, opt.h Michael Niedermayer
rational.c, rational.h Michael Niedermayer rational.c, rational.h [2] Michael Niedermayer
rc4 Reimar Doeffinger rc4 Reimar Doeffinger
ripemd.c, ripemd.h James Almer ripemd.c, ripemd.h James Almer
tx* Lynne tx* [2] Lynne
libavcodec libavcodec
@ -106,20 +127,18 @@ Generic Parts:
DSP utilities: DSP utilities:
dsputils.c, dsputils.h Michael Niedermayer dsputils.c, dsputils.h Michael Niedermayer
entropy coding: entropy coding:
rangecoder.c, rangecoder.h Michael Niedermayer rangecoder.c, rangecoder.h [2] Michael Niedermayer
lzw.* Michael Niedermayer lzw.* Michael Niedermayer
floating point AAN DCT: floating point AAN DCT:
faandct.c, faandct.h Michael Niedermayer faandct.c, faandct.h [2] Michael Niedermayer
Golomb coding: Golomb coding:
golomb.c, golomb.h Michael Niedermayer golomb.c, golomb.h [2] Michael Niedermayer
motion estimation: motion estimation:
motion* Michael Niedermayer motion* Michael Niedermayer
rate control: rate control:
ratecontrol.c Michael Niedermayer ratecontrol.c [2] Michael Niedermayer
simple IDCT: simple IDCT:
simple_idct.c, simple_idct.h Michael Niedermayer simple_idct.c, simple_idct.h [2] Michael Niedermayer
postprocessing:
libpostproc/* Michael Niedermayer
table generation: table generation:
tableprint.c, tableprint.h Reimar Doeffinger tableprint.c, tableprint.h Reimar Doeffinger
fixed point FFT: fixed point FFT:
@ -127,24 +146,23 @@ Generic Parts:
Text Subtitles Clément Bœsch Text Subtitles Clément Bœsch
Codecs: Codecs:
4xm.c Michael Niedermayer 4xm.c [2] Michael Niedermayer
8bps.c Roberto Togni 8bps.c Roberto Togni
8svx.c Jaikrishnan Menon 8svx.c Jaikrishnan Menon
aacenc*, aaccoder.c Rostislav Pehlivanov aacenc*, aaccoder.c Rostislav Pehlivanov
adpcm.c Zane van Iperen adpcm.c Zane van Iperen
alacenc.c Jaikrishnan Menon alacenc.c Jaikrishnan Menon
alsdec.c Thilo Borgmann, Umair Khan alsdec.c Thilo Borgmann, Umair Khan
amfenc* Dmitrii Ovchinnikov amfdec*,amfenc* [2] Dmitrii Ovchinnikov, Araz Iusubov
aptx.c Aurelien Jacobs aptx.c Aurelien Jacobs
ass* Aurelien Jacobs ass* Aurelien Jacobs
asv* Michael Niedermayer asv* Michael Niedermayer
atrac3plus* Maxim Poliakovski atrac3plus* Maxim Poliakovski
audiotoolbox* rcombs audiotoolbox* rcombs
avs2* Huiwen Ren avs2* Huiwen Ren, Zhao Zhili
bgmc.c, bgmc.h Thilo Borgmann bgmc.c, bgmc.h Thilo Borgmann
binkaudio.c Peter Ross binkaudio.c Peter Ross
cavs* Stefan Gehrer cavs* Stefan Gehrer
cdxl.c Paul B Mahol
celp_filters.* Vitor Sessak celp_filters.* Vitor Sessak
cinepak.c Roberto Togni cinepak.c Roberto Togni
cinepakenc.c Rl / Aetey G.T. AB cinepakenc.c Rl / Aetey G.T. AB
@ -162,10 +180,10 @@ Codecs:
dss_sp.c Oleksij Rempel dss_sp.c Oleksij Rempel
dv.c Roman Shaposhnik dv.c Roman Shaposhnik
dvbsubdec.c Anshul Maheshwari dvbsubdec.c Anshul Maheshwari
dxv.*, dxvenc.* Emma Worley
eacmv*, eaidct*, eat* Peter Ross eacmv*, eaidct*, eat* Peter Ross
evrc* Paul B Mahol
exif.c, exif.h Thilo Borgmann exif.c, exif.h Thilo Borgmann
ffv1* Michael Niedermayer ffv1* [2] Michael Niedermayer
ffwavesynth.c Nicolas George ffwavesynth.c Nicolas George
fifo.c Jan Sebechlebsky fifo.c Jan Sebechlebsky
flicvideo.c Mike Melanson flicvideo.c Mike Melanson
@ -176,6 +194,7 @@ Codecs:
h263* Michael Niedermayer h263* Michael Niedermayer
h264* Loren Merritt, Michael Niedermayer h264* Loren Merritt, Michael Niedermayer
hap* Tom Butterworth hap* Tom Butterworth
hevc/* Anton Khirnov
huffyuv* Michael Niedermayer huffyuv* Michael Niedermayer
idcinvideo.c Mike Melanson idcinvideo.c Mike Melanson
interplayvideo.c Mike Melanson interplayvideo.c Mike Melanson
@ -192,9 +211,8 @@ Codecs:
libgsm.c Michel Bardiaux libgsm.c Michel Bardiaux
libkvazaar.c Arttu Ylä-Outinen libkvazaar.c Arttu Ylä-Outinen
libopenh264enc.c Martin Storsjo, Linjie Fu libopenh264enc.c Martin Storsjo, Linjie Fu
libopenjpeg.c Jaikrishnan Menon
libopenjpegenc.c Michael Bradshaw libopenjpegenc.c Michael Bradshaw
libtheoraenc.c David Conrad libtheoraenc.c [0]
libvorbis.c David Conrad libvorbis.c David Conrad
libvpx* James Zern libvpx* James Zern
libxavs.c Stefan Gehrer libxavs.c Stefan Gehrer
@ -215,9 +233,8 @@ Codecs:
msvideo1.c Mike Melanson msvideo1.c Mike Melanson
nuv.c Reimar Doeffinger nuv.c Reimar Doeffinger
nvdec*, nvenc* Timo Rothenpieler nvdec*, nvenc* Timo Rothenpieler
omx.c Martin Storsjo, Aman Gupta oh* Zhao Zhili
opus* Rostislav Pehlivanov opus* Rostislav Pehlivanov
paf.* Paul B Mahol
pcx.c Ivo van Poorten pcx.c Ivo van Poorten
pgssubdec.c Reimar Doeffinger pgssubdec.c Reimar Doeffinger
ptx.c Ivo van Poorten ptx.c Ivo van Poorten
@ -227,28 +244,25 @@ Codecs:
qtrle.c Mike Melanson qtrle.c Mike Melanson
ra144.c, ra144.h, ra288.c, ra288.h Roberto Togni ra144.c, ra144.h, ra288.c, ra288.h Roberto Togni
resample2.c Michael Niedermayer resample2.c Michael Niedermayer
rkmppenc* Zhao Zhili
rl2.c Sascha Sommer rl2.c Sascha Sommer
rpza.c Roberto Togni rpza.c Roberto Togni
rtjpeg.c, rtjpeg.h Reimar Doeffinger rtjpeg.c, rtjpeg.h Reimar Doeffinger
rv10.c Michael Niedermayer rv10.c Michael Niedermayer
s3tc* Ivo van Poorten sanm.c Manuel Lauss
smc.c Mike Melanson smc.c Mike Melanson
snow* Michael Niedermayer, Loren Merritt snow* Michael Niedermayer, Loren Merritt
sonic.c Alex Beregszaszi
speedhq.c Steinar H. Gunderson speedhq.c Steinar H. Gunderson
srt* Aurelien Jacobs srt* Aurelien Jacobs
sunrast.c Ivo van Poorten sunrast.c Ivo van Poorten
svq3.c Michael Niedermayer svq3.c Michael Niedermayer
tak* Paul B Mahol
truemotion1* Mike Melanson truemotion1* Mike Melanson
tta.c Alex Beregszaszi, Jaikrishnan Menon tta.c Alex Beregszaszi, Jaikrishnan Menon
ttaenc.c Paul B Mahol
txd.c Ivo van Poorten txd.c Ivo van Poorten
v4l2_* Jorge Ramirez-Ortiz v4l2_* Jorge Ramirez-Ortiz
vc2* Rostislav Pehlivanov vc2* Rostislav Pehlivanov
vcr1.c Michael Niedermayer vcr1.c Michael Niedermayer
videotoolboxenc.c Rick Kern, Aman Gupta videotoolboxenc.c Rick Kern, Aman Gupta
vima.c Paul B Mahol
vorbisdec.c Denes Balatoni, David Conrad vorbisdec.c Denes Balatoni, David Conrad
vorbisenc.c Oded Shimon vorbisenc.c Oded Shimon
vp3* Mike Melanson vp3* Mike Melanson
@ -257,22 +271,24 @@ Codecs:
vp8 David Conrad, Ronald Bultje vp8 David Conrad, Ronald Bultje
vp9 Ronald Bultje vp9 Ronald Bultje
vqavideo.c Mike Melanson vqavideo.c Mike Melanson
vvc [2] Nuo Mi, Wu Jianhua, Frank Plowman
wmaprodec.c Sascha Sommer wmaprodec.c Sascha Sommer
wmavoice.c Ronald S. Bultje wmavoice.c Ronald S. Bultje
wmv2.c Michael Niedermayer wmv2.c Michael Niedermayer
xan.c Mike Melanson xan.c Mike Melanson
xbm* Paul B Mahol
xface Stefano Sabatini xface Stefano Sabatini
xwd* Paul B Mahol
Hardware acceleration: Hardware acceleration:
amf* [2] Dmitrii Ovchinnikov, Araz Iusubov
dxva2* Hendrik Leppkes, Laurent Aimar, Steve Lhomme dxva2* Hendrik Leppkes, Laurent Aimar, Steve Lhomme
d3d11va* Steve Lhomme d3d11va* Steve Lhomme
mediacodec* Matthieu Bouron, Aman Gupta d3d12va* Wu Jianhua
d3d12va_encode* Tong Wu
mediacodec* Matthieu Bouron, Aman Gupta, Zhao Zhili
vaapi* Haihao Xiang vaapi* Haihao Xiang
vaapi_encode* Mark Thompson, Haihao Xiang vaapi_encode* Mark Thompson, Haihao Xiang
vdpau* Philip Langdale, Carl Eugen Hoyos vdpau* Philip Langdale, Carl Eugen Hoyos
videotoolbox* Rick Kern, Aman Gupta videotoolbox* Rick Kern, Aman Gupta, Zhao Zhili
libavdevice libavdevice
@ -302,72 +318,49 @@ libavfilter
=========== ===========
Generic parts: Generic parts:
Framework and orphaned filters Nicolas George
(except hardware acceleration)
graphdump.c Nicolas George graphdump.c Nicolas George
motion_estimation.c Davinder Singh motion_estimation.c Davinder Singh
Filters: Filters:
f_drawgraph.c Paul B Mahol
af_adelay.c Paul B Mahol
af_aecho.c Paul B Mahol
af_afade.c Paul B Mahol
af_amerge.c Nicolas George af_amerge.c Nicolas George
af_aphaser.c Paul B Mahol
af_aresample.c Michael Niedermayer af_aresample.c Michael Niedermayer
af_astats.c Paul B Mahol
af_atempo.c Pavel Koshevoy af_atempo.c Pavel Koshevoy
af_biquads.c Paul B Mahol
af_chorus.c Paul B Mahol
af_compand.c Paul B Mahol
af_firequalizer.c Muhammad Faiz af_firequalizer.c Muhammad Faiz
af_hdcd.c Burt P. af_hdcd.c Burt P.
af_ladspa.c Paul B Mahol
af_loudnorm.c Kyle Swanson af_loudnorm.c Kyle Swanson
af_pan.c Nicolas George af_pan.c Nicolas George
af_sidechaincompress.c Paul B Mahol
af_silenceremove.c Paul B Mahol
avf_aphasemeter.c Paul B Mahol
avf_avectorscope.c Paul B Mahol
avf_showcqt.c Muhammad Faiz avf_showcqt.c Muhammad Faiz
vf_blend.c Paul B Mahol
vf_bwdif Thomas Mundt (CC <thomas.mundt@hr.de>) vf_bwdif Thomas Mundt (CC <thomas.mundt@hr.de>)
vf_chromakey.c Timo Rothenpieler vf_chromakey.c Timo Rothenpieler
vf_colorchannelmixer.c Paul B Mahol
vf_colorconstancy.c Mina Sami (CC <minas.gorgy@gmail.com>) vf_colorconstancy.c Mina Sami (CC <minas.gorgy@gmail.com>)
vf_colorbalance.c Paul B Mahol
vf_colorkey.c Timo Rothenpieler vf_colorkey.c Timo Rothenpieler
vf_colorlevels.c Paul B Mahol
vf_coreimage.m Thilo Borgmann vf_coreimage.m Thilo Borgmann
vf_deband.c Paul B Mahol
vf_dejudder.c Nicholas Robbins vf_dejudder.c Nicholas Robbins
vf_delogo.c Jean Delvare (CC <jdelvare@suse.com>) vf_delogo.c Jean Delvare (CC <jdelvare@suse.com>)
vf_drawbox.c/drawgrid Andrey Utkin vf_drawbox.c/drawgrid Andrey Utkin
vf_extractplanes.c Paul B Mahol vf_fsync.c Thilo Borgmann
vf_histogram.c Paul B Mahol
vf_hqx.c Clément Bœsch vf_hqx.c Clément Bœsch
vf_idet.c Pascal Massimino vf_idet.c Pascal Massimino
vf_il.c Paul B Mahol
vf_(t)interlace Thomas Mundt (CC <thomas.mundt@hr.de>) vf_(t)interlace Thomas Mundt (CC <thomas.mundt@hr.de>)
vf_lenscorrection.c Daniel Oberhoff vf_lenscorrection.c Daniel Oberhoff
vf_libplacebo.c Niklas Haas vf_libplacebo.c Niklas Haas
vf_mergeplanes.c Paul B Mahol
vf_mestimate.c Davinder Singh vf_mestimate.c Davinder Singh
vf_minterpolate.c Davinder Singh vf_minterpolate.c Davinder Singh
vf_neighbor.c Paul B Mahol
vf_psnr.c Paul B Mahol
vf_random.c Paul B Mahol
vf_readvitc.c Tobias Rapp (CC t.rapp at noa-archive dot com) vf_readvitc.c Tobias Rapp (CC t.rapp at noa-archive dot com)
vf_scale.c Michael Niedermayer vf_scale.c [2] Michael Niedermayer
vf_separatefields.c Paul B Mahol vf_scale_vt.c Zhao Zhili
vf_ssim.c Paul B Mahol
vf_stereo3d.c Paul B Mahol
vf_telecine.c Paul B Mahol
vf_tonemap_opencl.c Ruiling Song vf_tonemap_opencl.c Ruiling Song
vf_yadif.c Michael Niedermayer vf_transpose_vt.c Zhao Zhili
vf_zoompan.c Paul B Mahol vf_yadif.c [2] Michael Niedermayer
vf_xfade_vulkan.c [2] Marvin Scholz (CC <epirat07@gmail.com>)
Sources: Sources:
vsrc_mandelbrot.c Michael Niedermayer vsrc_mandelbrot.c [2] Michael Niedermayer
dnn Yejun Guo dnn Yejun Guo
@ -385,8 +378,7 @@ Generic parts:
Muxers/Demuxers: Muxers/Demuxers:
4xm.c Mike Melanson 4xm.c Mike Melanson
aadec.c Vesselin Bontchev (vesselin.bontchev at yandex dot com) aadec.c Vesselin Bontchev (vesselin.bontchev at yandex dot com)
adtsenc.c Robert Swain adtsenc.c [0]
afc.c Paul B Mahol
aiffdec.c Baptiste Coudurier, Matthieu Bouron aiffdec.c Baptiste Coudurier, Matthieu Bouron
aiffenc.c Baptiste Coudurier, Matthieu Bouron aiffenc.c Baptiste Coudurier, Matthieu Bouron
alp.c Zane van Iperen alp.c Zane van Iperen
@ -397,16 +389,12 @@ Muxers/Demuxers:
argo_brp.c Zane van Iperen argo_brp.c Zane van Iperen
argo_cvg.c Zane van Iperen argo_cvg.c Zane van Iperen
ass* Aurelien Jacobs ass* Aurelien Jacobs
astdec.c Paul B Mahol
astenc.c James Almer astenc.c James Almer
avi* Michael Niedermayer avi* Michael Niedermayer
avisynth.c Stephen Hutchinson avisynth.c Stephen Hutchinson
avr.c Paul B Mahol
bink.c Peter Ross bink.c Peter Ross
boadec.c Michael Niedermayer boadec.c Michael Niedermayer
brstm.c Paul B Mahol
caf* Peter Ross caf* Peter Ross
cdxl.c Paul B Mahol
codec2.c Tomas Härdin codec2.c Tomas Härdin
crc.c Michael Niedermayer crc.c Michael Niedermayer
dashdec.c Steven Liu dashdec.c Steven Liu
@ -415,10 +403,9 @@ Muxers/Demuxers:
dfpwmdec.c Jack Bruienne dfpwmdec.c Jack Bruienne
dss.c Oleksij Rempel dss.c Oleksij Rempel
dtsdec.c foo86 dtsdec.c foo86
dtshddec.c Paul B Mahol
dv.c Roman Shaposhnik dv.c Roman Shaposhnik
dvdvideodec.c [2] Marth64
electronicarts.c Peter Ross electronicarts.c Peter Ross
epafdec.c Paul B Mahol
evc* Samsung (Dawid Kozinski) evc* Samsung (Dawid Kozinski)
ffm* Baptiste Coudurier ffm* Baptiste Coudurier
flic.c Mike Melanson flic.c Mike Melanson
@ -426,14 +413,16 @@ Muxers/Demuxers:
flvenc.c Michael Niedermayer, Steven Liu flvenc.c Michael Niedermayer, Steven Liu
gxf.c Reimar Doeffinger gxf.c Reimar Doeffinger
gxfenc.c Baptiste Coudurier gxfenc.c Baptiste Coudurier
hls.c Kacper Michajłow
hlsenc.c Christian Suloway, Steven Liu hlsenc.c Christian Suloway, Steven Liu
hxvs.c Zhao Zhili
iamf* [2] James Almer
idcin.c Mike Melanson idcin.c Mike Melanson
idroqdec.c Mike Melanson idroqdec.c Mike Melanson
iff.c Jaikrishnan Menon iff.c Jaikrishnan Menon
imf* Pierre-Anthony Lemieux imf* Pierre-Anthony Lemieux
img2*.c Michael Niedermayer img2*.c Michael Niedermayer
ipmovie.c Mike Melanson ipmovie.c Mike Melanson
ircam* Paul B Mahol
iss.c Stefan Gehrer iss.c Stefan Gehrer
jpegxl* Leo Izen jpegxl* Leo Izen
jvdec.c Peter Ross jvdec.c Peter Ross
@ -441,14 +430,12 @@ Muxers/Demuxers:
libmodplug.c Clément Bœsch libmodplug.c Clément Bœsch
libopenmpt.c Josh de Kock libopenmpt.c Josh de Kock
lmlm4.c Ivo van Poorten lmlm4.c Ivo van Poorten
lvfdec.c Paul B Mahol
lxfdec.c Tomas Härdin lxfdec.c Tomas Härdin
matroska.c Aurelien Jacobs, Andreas Rheinhardt matroska.c Andreas Rheinhardt
matroskadec.c Aurelien Jacobs, Andreas Rheinhardt matroskadec.c Andreas Rheinhardt
matroskaenc.c David Conrad, Andreas Rheinhardt matroskaenc.c Andreas Rheinhardt
matroska subtitles (matroskaenc.c) John Peebles matroska subtitles (matroskaenc.c) John Peebles
metadata* Aurelien Jacobs metadata* Aurelien Jacobs
mgsts.c Paul B Mahol
microdvd* Aurelien Jacobs microdvd* Aurelien Jacobs
mm.c Peter Ross mm.c Peter Ross
mov.c Baptiste Coudurier mov.c Baptiste Coudurier
@ -461,21 +448,20 @@ Muxers/Demuxers:
msnwc_tcp.c Ramiro Polla msnwc_tcp.c Ramiro Polla
mtv.c Reynaldo H. Verdejo Pinochet mtv.c Reynaldo H. Verdejo Pinochet
mxf* Baptiste Coudurier, Tomas Härdin mxf* Baptiste Coudurier, Tomas Härdin
nistspheredec.c Paul B Mahol
nsvdec.c Francois Revol nsvdec.c Francois Revol
nut* Michael Niedermayer nut* Michael Niedermayer
nuv.c Reimar Doeffinger nuv.c Reimar Doeffinger
oggdec.c, oggdec.h David Conrad oggdec.c, oggdec.h David Conrad, Romain Beauxis
oggenc.c Baptiste Coudurier oggenc.c Baptiste Coudurier, Romain Beauxis
oggparse*.c David Conrad oggparse*.c David Conrad, Romain Beauxis
oma.c Maxim Poliakovski oma.c Maxim Poliakovski
paf.c Paul B Mahol
pp_bnk.c Zane van Iperen pp_bnk.c Zane van Iperen
psxstr.c Mike Melanson psxstr.c Mike Melanson
pva.c Ivo van Poorten pva.c Ivo van Poorten
pvfdec.c Paul B Mahol
r3d.c Baptiste Coudurier r3d.c Baptiste Coudurier
raw.c Michael Niedermayer raw.c Michael Niedermayer
rcwtdec.c [2] Marth64
rcwtenc.c [2] Marth64
rdt.c Ronald S. Bultje rdt.c Ronald S. Bultje
rl2.c Sascha Sommer rl2.c Sascha Sommer
rmdec.c, rmenc.c Ronald S. Bultje rmdec.c, rmenc.c Ronald S. Bultje
@ -494,11 +480,10 @@ Muxers/Demuxers:
sdp.c Martin Storsjo sdp.c Martin Storsjo
segafilm.c Mike Melanson segafilm.c Mike Melanson
segment.c Stefano Sabatini segment.c Stefano Sabatini
smjpeg* Paul B Mahol smush.c Manuel Lauss
spdif* Anssi Hannula spdif* Anssi Hannula
srtdec.c Aurelien Jacobs srtdec.c Aurelien Jacobs
swf.c Baptiste Coudurier swf.c Baptiste Coudurier
takdec.c Paul B Mahol
tta.c Alex Beregszaszi tta.c Alex Beregszaszi
txd.c Ivo van Poorten txd.c Ivo van Poorten
voc.c Aurelien Jacobs voc.c Aurelien Jacobs
@ -507,8 +492,8 @@ Muxers/Demuxers:
webm dash (matroskaenc.c) Vignesh Venkatasubramanian webm dash (matroskaenc.c) Vignesh Venkatasubramanian
webvtt* Matthew J Heaney webvtt* Matthew J Heaney
westwood.c Mike Melanson westwood.c Mike Melanson
whip.c [2] Jack Lau
wtv.c Peter Ross wtv.c Peter Ross
wvenc.c Paul B Mahol
Protocols: Protocols:
async.c Zhang Rui async.c Zhang Rui
@ -520,37 +505,38 @@ Protocols:
libzmq.c Andriy Gelman libzmq.c Andriy Gelman
mms*.c Ronald S. Bultje mms*.c Ronald S. Bultje
udp.c Luca Abeni udp.c Luca Abeni
icecast.c Marvin Scholz icecast.c [2] Marvin Scholz (CC <epirat07@gmail.com>)
libswresample libswresample
============= =============
Generic parts: Generic parts:
audioconvert.c Michael Niedermayer audioconvert.c [2] Michael Niedermayer
dither.c Michael Niedermayer dither.c [2] Michael Niedermayer
rematrix*.c Michael Niedermayer rematrix*.c [2] Michael Niedermayer
swresample*.c Michael Niedermayer swresample*.c [2] Michael Niedermayer
Resamplers: Resamplers:
resample*.c Michael Niedermayer resample*.c [2] Michael Niedermayer
soxr_resample.c Rob Sykes soxr_resample.c Rob Sykes
Operating systems / CPU architectures Operating systems / CPU architectures
===================================== =====================================
Alpha Falk Hueffner *BSD [2] Brad Smith
Alpha [0]
MIPS Manojkumar Bhosale, Shiyou Yin MIPS Manojkumar Bhosale, Shiyou Yin
LoongArch Shiyou Yin LoongArch [2] Shiyou Yin
Mac OS X / PowerPC Romain Dolbeau, Guillaume Poirier Darwin (macOS, iOS) [2] Marvin Scholz
Mac OS X / PowerPC [0]
Amiga / PowerPC Colin Ward Amiga / PowerPC Colin Ward
Linux / PowerPC Lauri Kasanen Linux / PowerPC [2] Sean McGovern (CC <gseanmcg@gmail.com>), Lauri Kasanen
RISC-V Rémi Denis-Courmont RISC-V [2] Rémi Denis-Courmont
Windows MinGW Alex Beregszaszi, Ramiro Polla Windows MinGW Alex Beregszaszi, Ramiro Polla
Windows Cygwin Victor Paesa Windows Cygwin Victor Paesa
Windows MSVC Matthew Oliver, Hendrik Leppkes Windows MSVC Hendrik Leppkes
Windows ICL Matthew Oliver
ADI/Blackfin DSP Marc Hoffman ADI/Blackfin DSP Marc Hoffman
Sparc Roman Shaposhnik Sparc Roman Shaposhnik
OS/2 KO Myung-Hun OS/2 KO Myung-Hun
@ -566,6 +552,7 @@ Benjamin Larsson
Bobby Bingham Bobby Bingham
Daniel Verkamp Daniel Verkamp
Derek Buitenhuis Derek Buitenhuis
Fei Wang
Ganesh Ajjanagadde Ganesh Ajjanagadde
Henrik Gramner Henrik Gramner
Ivan Uskov Ivan Uskov
@ -573,6 +560,7 @@ James Darnley
Jan Ekström Jan Ekström
Joakim Plate Joakim Plate
Jun Zhao Jun Zhao
Kacper Michajłow
Kieran Kunhya Kieran Kunhya
Kirill Gavrilov Kirill Gavrilov
Limin Wang Limin Wang
@ -588,10 +576,12 @@ wm4
Releases Releases
======== ========
7.0 Michael Niedermayer
6.1 Michael Niedermayer
5.1 Michael Niedermayer
4.4 Michael Niedermayer
3.4 Michael Niedermayer
2.8 Michael Niedermayer 2.8 Michael Niedermayer
2.7 Michael Niedermayer
2.6 Michael Niedermayer
2.5 Michael Niedermayer
If you want to maintain an older release, please contact us If you want to maintain an older release, please contact us
@ -612,6 +602,7 @@ Benoit Fouet B22A 4F4F 43EF 636B BB66 FCDC 0023 AE1E 2985 49C8
Clément Bœsch 52D0 3A82 D445 F194 DB8B 2B16 87EE 2CB8 F4B8 FCF9 Clément Bœsch 52D0 3A82 D445 F194 DB8B 2B16 87EE 2CB8 F4B8 FCF9
Daniel Verkamp 78A6 07ED 782C 653E C628 B8B9 F0EB 8DD8 2F0E 21C7 Daniel Verkamp 78A6 07ED 782C 653E C628 B8B9 F0EB 8DD8 2F0E 21C7
FFmpeg release signing key FCF9 86EA 15E6 E293 A564 4F10 B432 2F04 D676 58D8 FFmpeg release signing key FCF9 86EA 15E6 E293 A564 4F10 B432 2F04 D676 58D8
Frank Plowman 34E2 48D6 B7DF 4769 70C7 3304 03A8 4C6A 098F 2C6B
Ganesh Ajjanagadde C96A 848E 97C3 CEA2 AB72 5CE4 45F9 6A2D 3C36 FB1B Ganesh Ajjanagadde C96A 848E 97C3 CEA2 AB72 5CE4 45F9 6A2D 3C36 FB1B
Gwenole Beauchesne 2E63 B3A6 3E44 37E2 017D 2704 53C7 6266 B153 99C4 Gwenole Beauchesne 2E63 B3A6 3E44 37E2 017D 2704 53C7 6266 B153 99C4
Haihao Xiang (haihao) 1F0C 31E8 B4FE F7A4 4DC1 DC99 E0F5 76D4 76FC 437F Haihao Xiang (haihao) 1F0C 31E8 B4FE F7A4 4DC1 DC99 E0F5 76D4 76FC 437F
@ -619,6 +610,7 @@ Jaikrishnan Menon 61A1 F09F 01C9 2D45 78E1 C862 25DC 8831 AF70 D368
James Almer 7751 2E8C FD94 A169 57E6 9A7A 1463 01AD 7376 59E0 James Almer 7751 2E8C FD94 A169 57E6 9A7A 1463 01AD 7376 59E0
Jean Delvare 7CA6 9F44 60F1 BDC4 1FD2 C858 A552 6B9B B3CD 4E6A Jean Delvare 7CA6 9F44 60F1 BDC4 1FD2 C858 A552 6B9B B3CD 4E6A
Leo Izen (Traneptora) B6FD 3CFC 7ACF 83FC 9137 6945 5A71 C331 FD2F A19A Leo Izen (Traneptora) B6FD 3CFC 7ACF 83FC 9137 6945 5A71 C331 FD2F A19A
Leo Izen (Traneptora) 1D83 0A0B CE46 709E 203B 26FC 764E 48EA 4822 1833
Loren Merritt ABD9 08F4 C920 3F65 D8BE 35D7 1540 DAA7 060F 56DE Loren Merritt ABD9 08F4 C920 3F65 D8BE 35D7 1540 DAA7 060F 56DE
Lynne FE50 139C 6805 72CA FD52 1F8D A2FE A5F0 3F03 4464 Lynne FE50 139C 6805 72CA FD52 1F8D A2FE A5F0 3F03 4464
Michael Niedermayer 9FF2 128B 147E F673 0BAD F133 611E C787 040B 0FAB Michael Niedermayer 9FF2 128B 147E F673 0BAD F133 611E C787 040B 0FAB
@ -636,6 +628,7 @@ Reinhard Tartler 9300 5DC2 7E87 6C37 ED7B CA9A 9808 3544 9453 48A4
Reynaldo H. Verdejo Pinochet 6E27 CD34 170C C78E 4D4F 5F40 C18E 077F 3114 452A Reynaldo H. Verdejo Pinochet 6E27 CD34 170C C78E 4D4F 5F40 C18E 077F 3114 452A
Robert Swain EE7A 56EA 4A81 A7B5 2001 A521 67FA 362D A2FC 3E71 Robert Swain EE7A 56EA 4A81 A7B5 2001 A521 67FA 362D A2FC 3E71
Sascha Sommer 38A0 F88B 868E 9D3A 97D4 D6A0 E823 706F 1E07 0D3C Sascha Sommer 38A0 F88B 868E 9D3A 97D4 D6A0 E823 706F 1E07 0D3C
Sean McGovern (Sean_McG) 6D03 BC60 3A33 E615 6E2E 06AD 8C06 8175 6F59 8684
Stefano Sabatini 0D0B AD6B 5330 BBAD D3D6 6A0C 719C 2839 FC43 2D5F Stefano Sabatini 0D0B AD6B 5330 BBAD D3D6 6A0C 719C 2839 FC43 2D5F
Steinar H. Gunderson C2E9 004F F028 C18E 4EAD DB83 7F61 7561 7797 8F76 Steinar H. Gunderson C2E9 004F F028 C18E 4EAD DB83 7F61 7561 7797 8F76
Stephan Hilb 4F38 0B3A 5F39 B99B F505 E562 8D5C 5554 4E17 8863 Stephan Hilb 4F38 0B3A 5F39 B99B F505 E562 8D5C 5554 4E17 8863

View file

@ -11,6 +11,8 @@ vpath %.asm $(SRC_PATH)
vpath %.rc $(SRC_PATH) vpath %.rc $(SRC_PATH)
vpath %.v $(SRC_PATH) vpath %.v $(SRC_PATH)
vpath %.texi $(SRC_PATH) vpath %.texi $(SRC_PATH)
vpath %.glsl $(SRC_PATH)
vpath %.spv $(SRC_PATH)
vpath %.cu $(SRC_PATH) vpath %.cu $(SRC_PATH)
vpath %.ptx $(SRC_PATH) vpath %.ptx $(SRC_PATH)
vpath %.metal $(SRC_PATH) vpath %.metal $(SRC_PATH)
@ -19,14 +21,20 @@ vpath %/fate_config.sh.template $(SRC_PATH)
TESTTOOLS = audiogen videogen rotozoom tiny_psnr tiny_ssim base64 audiomatch TESTTOOLS = audiogen videogen rotozoom tiny_psnr tiny_ssim base64 audiomatch
HOSTPROGS := $(TESTTOOLS:%=tests/%) doc/print_options HOSTPROGS := $(TESTTOOLS:%=tests/%) doc/print_options
ALLFFLIBS = avcodec avdevice avfilter avformat avutil postproc swscale swresample ALLFFLIBS = \
avcodec \
avdevice \
avfilter \
avformat \
avutil \
swscale \
swresample \
# $(FFLIBS-yes) needs to be in linking order # $(FFLIBS-yes) needs to be in linking order
FFLIBS-$(CONFIG_AVDEVICE) += avdevice FFLIBS-$(CONFIG_AVDEVICE) += avdevice
FFLIBS-$(CONFIG_AVFILTER) += avfilter FFLIBS-$(CONFIG_AVFILTER) += avfilter
FFLIBS-$(CONFIG_AVFORMAT) += avformat FFLIBS-$(CONFIG_AVFORMAT) += avformat
FFLIBS-$(CONFIG_AVCODEC) += avcodec FFLIBS-$(CONFIG_AVCODEC) += avcodec
FFLIBS-$(CONFIG_POSTPROC) += postproc
FFLIBS-$(CONFIG_SWRESAMPLE) += swresample FFLIBS-$(CONFIG_SWRESAMPLE) += swresample
FFLIBS-$(CONFIG_SWSCALE) += swscale FFLIBS-$(CONFIG_SWSCALE) += swscale
@ -47,23 +55,31 @@ FF_DEP_LIBS := $(DEP_LIBS)
FF_STATIC_DEP_LIBS := $(STATIC_DEP_LIBS) FF_STATIC_DEP_LIBS := $(STATIC_DEP_LIBS)
$(TOOLS): %$(EXESUF): %.o $(TOOLS): %$(EXESUF): %.o
$(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(filter-out $(FF_DEP_LIBS), $^) $(EXTRALIBS-$(*F)) $(EXTRALIBS) $(ELIBS) $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(filter-out $(FF_DEP_LIBS), $^) $(EXTRALIBS-$(*F)) $(EXTRALIBS) $(ELIBS))
target_dec_%_fuzzer$(EXESUF): target_dec_%_fuzzer.o $(FF_DEP_LIBS) target_dec_%_fuzzer$(EXESUF): target_dec_%_fuzzer.o $(FF_DEP_LIBS)
$(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH))
target_enc_%_fuzzer$(EXESUF): target_enc_%_fuzzer.o $(FF_DEP_LIBS)
$(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH))
tools/target_bsf_%_fuzzer$(EXESUF): tools/target_bsf_%_fuzzer.o $(FF_DEP_LIBS) tools/target_bsf_%_fuzzer$(EXESUF): tools/target_bsf_%_fuzzer.o $(FF_DEP_LIBS)
$(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH))
target_dem_%_fuzzer$(EXESUF): target_dem_%_fuzzer.o $(FF_DEP_LIBS) target_dem_%_fuzzer$(EXESUF): target_dem_%_fuzzer.o $(FF_DEP_LIBS)
$(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH))
tools/target_dem_fuzzer$(EXESUF): tools/target_dem_fuzzer.o $(FF_DEP_LIBS) tools/target_dem_fuzzer$(EXESUF): tools/target_dem_fuzzer.o $(FF_DEP_LIBS)
$(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH))
tools/target_io_dem_fuzzer$(EXESUF): tools/target_io_dem_fuzzer.o $(FF_DEP_LIBS) tools/target_io_dem_fuzzer$(EXESUF): tools/target_io_dem_fuzzer.o $(FF_DEP_LIBS)
$(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH))
tools/target_sws_fuzzer$(EXESUF): tools/target_sws_fuzzer.o $(FF_DEP_LIBS)
$(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH))
tools/target_swr_fuzzer$(EXESUF): tools/target_swr_fuzzer.o $(FF_DEP_LIBS)
$(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH))
tools/enum_options$(EXESUF): ELIBS = $(FF_EXTRALIBS) tools/enum_options$(EXESUF): ELIBS = $(FF_EXTRALIBS)
tools/enum_options$(EXESUF): $(FF_DEP_LIBS) tools/enum_options$(EXESUF): $(FF_DEP_LIBS)
@ -93,10 +109,11 @@ ffbuild/.config: $(CONFIGURABLE_COMPONENTS)
SUBDIR_VARS := CLEANFILES FFLIBS HOSTPROGS TESTPROGS TOOLS \ SUBDIR_VARS := CLEANFILES FFLIBS HOSTPROGS TESTPROGS TOOLS \
HEADERS ARCH_HEADERS BUILT_HEADERS SKIPHEADERS \ HEADERS ARCH_HEADERS BUILT_HEADERS SKIPHEADERS \
ARMV5TE-OBJS ARMV6-OBJS ARMV8-OBJS VFP-OBJS NEON-OBJS \ ARMV5TE-OBJS ARMV6-OBJS ARMV8-OBJS VFP-OBJS NEON-OBJS \
ALTIVEC-OBJS VSX-OBJS RVV-OBJS MMX-OBJS X86ASM-OBJS \ ALTIVEC-OBJS VSX-OBJS X86ASM-OBJS \
MIPSFPU-OBJS MIPSDSPR2-OBJS MIPSDSP-OBJS MSA-OBJS \ MIPSFPU-OBJS MIPSDSPR2-OBJS MIPSDSP-OBJS MSA-OBJS \
MMI-OBJS LSX-OBJS LASX-OBJS OBJS SLIBOBJS SHLIBOBJS \ MMI-OBJS LSX-OBJS LASX-OBJS RV-OBJS RVV-OBJS RVVB-OBJS \
STLIBOBJS HOSTOBJS TESTOBJS OBJS SHLIBOBJS STLIBOBJS HOSTOBJS TESTOBJS SIMD128-OBJS \
SVE-OBJS SVE2-OBJS SME-OBJS SME2-OBJS
define RESET define RESET
$(1) := $(1) :=
@ -130,16 +147,21 @@ else
endif endif
%$(PROGSSUF)_g$(EXESUF): $(FF_DEP_LIBS) %$(PROGSSUF)_g$(EXESUF): $(FF_DEP_LIBS)
$(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(OBJS-$*) $(FF_EXTRALIBS) $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(OBJS-$*) $(FF_EXTRALIBS))
VERSION_SH = $(SRC_PATH)/ffbuild/version.sh VERSION_SH = $(SRC_PATH)/ffbuild/version.sh
ifeq ($(VERSION_TRACKING),yes)
GIT_LOG = $(SRC_PATH)/.git/logs/HEAD GIT_LOG = $(SRC_PATH)/.git/logs/HEAD
endif
.version: $(wildcard $(GIT_LOG)) $(VERSION_SH) ffbuild/config.mak .version: $(wildcard $(GIT_LOG)) $(VERSION_SH) ffbuild/config.mak
.version: M=@ .version: M=@
ifneq ($(VERSION_TRACKING),yes)
libavutil/ffversion.h .version: REVISION=unknown
endif
libavutil/ffversion.h .version: libavutil/ffversion.h .version:
$(M)$(VERSION_SH) $(SRC_PATH) libavutil/ffversion.h $(EXTRA_VERSION) $(M)revision=$(REVISION) $(VERSION_SH) $(SRC_PATH) libavutil/ffversion.h $(EXTRA_VERSION)
$(Q)touch .version $(Q)touch .version
# force version.sh to run whenever version might have changed # force version.sh to run whenever version might have changed
@ -165,7 +187,8 @@ clean::
$(RM) -rf coverage.info coverage.info.in lcov $(RM) -rf coverage.info coverage.info.in lcov
distclean:: clean distclean:: clean
$(RM) .version config.asm config.h config_components.h mapfile \ $(RM) .version config.asm config.h config_components.* mapfile \
checkasm_config_generated.* checkasm_header_config_generated.h \
ffbuild/.config ffbuild/config.* libavutil/avconfig.h \ ffbuild/.config ffbuild/config.* libavutil/avconfig.h \
version.h libavutil/ffversion.h libavcodec/codec_names.h \ version.h libavutil/ffversion.h libavcodec/codec_names.h \
libavcodec/bsf_list.c libavformat/protocol_list.c \ libavcodec/bsf_list.c libavformat/protocol_list.c \

View file

@ -1,5 +1,4 @@
FFmpeg README # FFmpeg README
=============
FFmpeg is a collection of libraries and tools to process multimedia content FFmpeg is a collection of libraries and tools to process multimedia content
such as audio, video, subtitles and related metadata. such as audio, video, subtitles and related metadata.

View file

@ -1 +1 @@
5.1.git 9.0.1

15
thirdparty/ffmpeg/RELEASE_NOTES vendored Normal file
View file

@ -0,0 +1,15 @@
┌────────────────────────────────────┐
│ RELEASE NOTES for FFmpeg 9.0 "Lei" │
└────────────────────────────────────┘
The FFmpeg Project proudly presents FFmpeg 9.0 "Lei", about 4
months after the release of FFmpeg 8.1.
A complete Changelog is available at the root of the project, and the
complete Git history on https://git.ffmpeg.org/gitweb/ffmpeg.git
We hope you will like this release as much as we enjoyed working on it, and
as usual, if you have any questions about it, or any FFmpeg related topic,
feel free to join us on the #ffmpeg IRC channel (on irc.libera.chat) or ask
on the mailing-lists.

1
thirdparty/ffmpeg/VERSION vendored Normal file
View file

@ -0,0 +1 @@
9.0.1

Some files were not shown because too many files have changed in this diff Show more