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
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
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

View file

@ -246,6 +246,7 @@ singeEmbedLua(thirdparty/luasec/src/ssl.lua "")
singeEmbedLua(thirdparty/librs232/bindings/lua/rs232.lua "")
singeEmbedLua(thirdparty/copas/src/copas.lua "")
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/smtp.lua "copas_")
singeEmbedLua(thirdparty/copas/src/copas/lock.lua "copas_")
@ -310,11 +311,14 @@ set(SINGE_SOURCE
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
thirdparty/sqlite/sqlite3.c
thirdparty/sqlite/sqlite3.h
thirdparty/lsqlite3/lsqlite3.c
)
set_source_files_properties(thirdparty/lsqlite3/lsqlite3.c PROPERTIES COMPILE_DEFINITIONS "LSQLITE_VERSION=\"${LSQLITE_VERSION}\"")
set(ARG_PARSER_SOURCE
thirdparty/arg_parser/carg_parser.c
@ -627,7 +631,6 @@ set(STATIC_LIBS
${BUILD_DIR}/lib/libopus.a
${BUILD_DIR}/lib/libopusfile.a
${BUILD_DIR}/lib/libogg.a
${BUILD_DIR}/lib/libpostproc.a
${BUILD_DIR}/lib/libSDL3.a
${BUILD_DIR}/lib/libSDL3_image.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
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
binaryheap.lua MIT http://tieske.github.io/binaryheap.lua
cgltf MIT https://github.com/jkuhlmann/cgltf
DirectXShaderCompiler NCSA https://github.com/microsoft/DirectXShaderCompiler (prebuilt, downloaded at build time; host tool only)
copas MIT https://lunarmodules.github.io/copas
ffmpeg LGPL-2.1 https://ffmpeg.org
freetype FTL https://freetype.org
JoltPhysics MIT https://github.com/jrouwe/JoltPhysics
json.lua MIT https://github.com/rxi/json.lua
libjpeg-turbo IJG https://libjpeg-turbo.org
libogg BSD-3-Clause https://xiph.org/ogg
libpng libpng-2.0 http://www.libpng.org
librs232 MIT https://github.com/srdgame/librs232
libwebp BSD-3-Clause https://developers.google.com/speed/webp
libxmp MIT https://github.com/libxmp/libxmp
lsqlite3 MIT http://lua.sqlite.org
lua MIT https://www.lua.org
luafilesystem MIT https://lunarmodules.github.io/luafilesystem
luasec MIT https://github.com/lunarmodules/luasec
luasocket MIT https://lunarmodules.github.io/luasocket
manymouse Zlib https://icculus.org/manymouse
openssl Apache-2.0 https://www.openssl.org
opus BSD-3-Clause https://opus-codec.org
opusfile BSD-3-Clause https://opus-codec.org
recastnavigation Zlib https://github.com/recastnavigation/recastnavigation
RmlUi MIT https://github.com/mikke89/RmlUi
SDL3 Zlib https://www.libsdl.org
SDL_shadercross Zlib https://github.com/libsdl-org/SDL_shadercross (host tool only)
SDL3_image Zlib https://www.libsdl.org
SDL3_mixer Zlib https://www.libsdl.org
SDL3_ttf Zlib https://www.libsdl.org
SPIRV-Cross Apache-2.0 https://github.com/KhronosGroup/SPIRV-Cross (host tool only)
sqlite Public-Domain https://sqlite.org
timerwheel.lua MIT https://tieske.github.io/timerwheel.lua
uthash BSD-1-Clause https://troydhanson.github.io/uthash
vlc GPL-2.0 https://www.videolan.org/vlc
wavpack BSD-3-Clause https://www.wavpack.com
zlib Zlib https://zlib.net
zstd BSD-3-Clause https://facebook.github.io/zstd
arg_parser 1.21 BSD-2-Clause http://savannah.nongnu.org/projects/arg-parser
basis_universal 2.50 Apache-2.0 https://github.com/BinomialLLC/basis_universal
binaryheap.lua 0.4 MIT http://tieske.github.io/binaryheap.lua
cgltf 1.15 MIT https://github.com/jkuhlmann/cgltf
copas 4.12.0 MIT https://lunarmodules.github.io/copas
DirectXShaderCompiler 1.10.2605.37 NCSA https://github.com/microsoft/DirectXShaderCompiler (prebuilt, downloaded at build time; host tool only)
ffmpeg 9.0.1 LGPL-2.1 https://ffmpeg.org
freetype 2.13.2 FTL https://freetype.org (bundled with SDL3_ttf)
JoltPhysics 5.6.0 MIT https://github.com/jrouwe/JoltPhysics
json.lua 0.1.2 MIT https://github.com/rxi/json.lua
libjpeg 9f IJG https://ijg.org (bundled with SDL3_image)
libogg 1.3.5 BSD-3-Clause https://xiph.org/ogg (bundled with SDL3_mixer)
libpng 1.6.58 libpng-2.0 http://www.libpng.org (bundled with SDL3_image)
librs232 1.0.4 MIT https://github.com/srdgame/librs232
libwebp 1.3.2 BSD-3-Clause https://developers.google.com/speed/webp (bundled with SDL3_image)
libxmp 4.7.0 MIT https://github.com/libxmp/libxmp (bundled with SDL3_mixer)
lsqlite3 0.9.7 MIT http://lua.sqlite.org
lua 5.4.9 MIT https://www.lua.org
luafilesystem 1.9.0 MIT https://lunarmodules.github.io/luafilesystem
luasec 1.3.2 MIT https://github.com/lunarmodules/luasec
luasocket 3.1.0 MIT https://lunarmodules.github.io/luasocket
manymouse 0.0.3 Zlib https://icculus.org/manymouse
openssl 3.5.8 Apache-2.0 https://www.openssl.org
opus 1.4 BSD-3-Clause https://opus-codec.org (bundled with SDL3_mixer)
opusfile 0.12 BSD-3-Clause https://opus-codec.org (bundled with SDL3_mixer)
recastnavigation 1.6.0 Zlib https://github.com/recastnavigation/recastnavigation
RmlUi 6.3 MIT https://github.com/mikke89/RmlUi
SDL3 3.4.16 Zlib https://www.libsdl.org
SDL_shadercross main 2026-09 Zlib https://github.com/libsdl-org/SDL_shadercross (host tool only)
SDL3_image 3.4.6 Zlib https://www.libsdl.org
SDL3_mixer 3.2.4 Zlib https://www.libsdl.org
SDL3_ttf 3.2.2 Zlib https://www.libsdl.org
SPIRV-Cross main 2026-09 Apache-2.0 https://github.com/KhronosGroup/SPIRV-Cross (host tool only)
sqlite 3.53.4 Public-Domain https://sqlite.org
timerwheel.lua 1.0.2 MIT https://tieske.github.io/timerwheel.lua
uthash 2.4.0 BSD-1-Clause https://troydhanson.github.io/uthash
vlc 3.0 headers LGPL-2.1 https://www.videolan.org/vlc (two language-code headers only)
wavpack 5.9.0 BSD-3-Clause https://www.wavpack.com (bundled with SDL3_mixer)
zlib 1.3.2 Zlib https://zlib.net
zstd 1.5.7 BSD-3-Clause https://facebook.github.io/zstd
Fonts

View file

@ -134,12 +134,12 @@ function(singeCmakeProject name source deps args env)
singeRebuildTarget(${name})
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.
function(singeRebuildTarget name)
ExternalProject_Get_Property(${name} STAMP_DIR)
ExternalProject_Get_Property(${name} STAMP_DIR BINARY_DIR)
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}
COMMENT "Rebuilding ${name}"
)
@ -240,7 +240,7 @@ elseif(KANGAROO_OS STREQUAL "pi")
if(KANGAROO_ARCH STREQUAL "aarch64")
set(opensslTarget linux-aarch64)
else()
set(opensslTarget linux-arm4)
set(opensslTarget linux-armv4)
endif()
elseif(KANGAROO_OS STREQUAL "macos")
if(KANGAROO_ARCH STREQUAL "aarch64")
@ -316,7 +316,7 @@ set(ffmpegBinary ${SB_PREFIX}/build/ffmpeg)
ExternalProject_Add(ffmpeg
SOURCE_DIR ${SB_THIRDPARTY}/ffmpeg
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
INSTALL_COMMAND ${SB_ENV} make install
LOG_CONFIGURE ON LOG_BUILD ON LOG_INSTALL ON LOG_OUTPUT_ON_FAILURE ON

View file

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

View file

@ -1067,6 +1067,7 @@ static const LuaModuleT _luaModules[] = {
// Copas
MODL("copas", copas_lua),
MODL("copas.ftp", copas_ftp_lua),
MODL("copas.future", copas_future_lua),
MODL("copas.http", copas_http_lua),
MODL("copas.lock", copas_lock_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.
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>
* Version 1.17 released.
@ -75,14 +109,13 @@
* Version 1.5 released.
* Minor fixes.
* C version: C99 style comments '//' have been converted to C89
style comments '/* */'.
* C version: Convert C99 style comments '//' to C89 style '/* */'.
* New files testsuite/check.sh, testsuite/test.txt.
2010-03-11 Antonio Diaz Diaz <ant_diaz@teleline.es>
* 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.
* The manual has been improved.
* Makefile.in: Add option '--name' to help2man invocation.
@ -112,15 +145,14 @@
2006-11-04 Antonio Diaz Diaz <ant_diaz@teleline.es>
* Version 0.2 released.
* Some fixes made to 'configure' script.
* configure: Some fixes.
2006-03-29 Antonio Diaz Diaz <ant_diaz@teleline.es>
* 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,
but just in case, you have unlimited permission to copy, distribute, and
modify it.
This file is a collection of facts, and thus it is not copyrightable, but just
in case, you have unlimited permission to copy, distribute, and 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).
I use gcc 6.1.0 and 3.3.6, but the code should compile with any standards
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
@ -15,8 +16,8 @@ Procedure
or
lzip -cd arg_parser[version].tar.lz | tar -xf -
This creates the directory ./arg_parser[version] containing the source from
the main archive.
This creates the directory ./arg_parser[version] containing the source code
extracted from the archive.
2. Change to arg_parser directory and run configure.
(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.
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
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
in the directory that 'configure' is in.
'configure' recognizes the option '--srcdir=DIR' to control where to
look for the sources. Usually 'configure' can determine that directory
'configure' recognizes the option '--srcdir=DIR' to control where to look
for the source code. Usually 'configure' can determine that directory
automatically.
After running 'configure', you can run 'make' and 'make install' as
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,
distribute, and modify it.

View file

@ -2,8 +2,8 @@
DISTNAME = $(pkgname)-$(pkgversion)
INSTALL = install
INSTALL_PROGRAM = $(INSTALL) -m 755
INSTALL_DATA = $(INSTALL) -m 644
INSTALL_DIR = $(INSTALL) -d -m 755
INSTALL_DATA = $(INSTALL) -m 644
SHELL = /bin/sh
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
$(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
$(cobjs) : Makefile carg_parser.h
doc : info man
info : $(VPATH)/doc/$(pkgname).info
$(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
@ -54,7 +58,8 @@ $(VPATH)/doc/$(progname).1 : $(progname)
help2man -n 'test program for Arg_parser' -o $@ ./$(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
./config.status
@ -132,7 +137,7 @@ dist : doc
$(DISTNAME)/*.c \
$(DISTNAME)/*.cc \
$(DISTNAME)/testsuite/check.sh \
$(DISTNAME)/testsuite/test.txt
$(DISTNAME)/testsuite/test[1-4].txt
rm -f $(DISTNAME)
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
added. They return the full name of the option parsed (short or long) to
help produce more accurate diagnostic messages.
The last argument of the C++ constructor and of the C function 'ap_init' has
been changed from 'bool in_order' to 'int flags'. This change is
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
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
implemented as a single struct plus associated functions. Both are simpler,
easier to use, and safer than 'getopt_long'.
For maximum stability, Arg_parser is self-contained. It extracts all the
information it needs from its arguments to avoid refering to them later.
This avoids index-out-of-bounds errors.
information it needs from its arguments to avoid referring to them later.
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
you may create more than one parser in your program if you need or want to.
Arg_parser does not modify its arguments (argc, argv), nor uses any global
variables.
The C++ version of Arg_parser can also parse options from configuration
files.
To use Arg_parser in your own programs simply copy the files 'arg_parser.h'
and 'arg_parser.cc' (or 'carg_parser.h' and 'carg_parser.c' for the
C version) in your source tree. See the file 'main.cc' ('cmain.c') for an
example of use.
The C++ version of Arg_parser is provided in the files 'arg_parser.h' and
'arg_parser.cc'. To learn how to use Arg_parser in your C++ programs, see
the C++ example in the manual and the example file 'main.cc' in the source
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
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.
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,
distribute, and modify it.
The file Makefile.in is a data file used by configure to produce the
Makefile. It has the same copyright owner and permissions that configure
itself.
The file Makefile.in is a data file used by configure to produce the Makefile.
It has the same copyright owner and permissions that configure itself.

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C++ version)
Copyright (C) 2006-2022 Antonio Diaz Diaz.
/* Arg_parser - POSIX/GNU command-line argument parser. (C++ version)
Copyright (C) 2006-2026 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and
binary forms, with or without modification, are permitted provided
@ -17,12 +17,24 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <cctype>
#include <cstring>
#include <string>
#include <vector>
#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,
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";
return false;
}
data.back().argument = &opt[len+3];
data.back().argument = &opt[len+3]; // argument may be empty
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_ += "' requires an argument";
return false;
}
++argind; data.back().argument = arg;
++argind; data.back().argument = arg; // argument may be empty
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;
}
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_ += '\'';
return false;
}
data.back().argument = arg; ++argind; cind = 0;
++argind; cind = 0;
data.back().argument = arg; // argument may be empty
}
}
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[],
const Option options[], const bool in_order )
const Option options[], const int flags )
: argv_index_( argc )
{
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 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;
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( in_order ) data.push_back( Record( argv[argind++] ) );
else if( flags & (in_order_stop | in_order_skip) ) break;
else if( flags & in_order ) data.push_back( Record( argv[argind++] ) );
else non_options.push_back( argv[argind++] );
}
}
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 )
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,
const Option options[] )
: argv_index_( 0 )
{
if( !opt || !opt[0] || !options ) return;

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C++ version)
Copyright (C) 2006-2022 Antonio Diaz Diaz.
/* Arg_parser - POSIX/GNU command-line argument parser. (C++ version)
Copyright (C) 2006-2026 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and
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.
Arg_parser normally makes it appear as if all the option arguments
were specified before all the non-option arguments for the purposes
of parsing, even if the user of your program intermixed option and
non-option arguments. If you want the arguments in the exact order
the user typed them, call 'Arg_parser' with 'in_order' = true.
Arg_parser normally makes it appear as if all the options were specified
before all the non-option arguments for the purposes of parsing, even if
the user of your program intermixed options and non-option arguments. If
you want the arguments in the exact order the user typed them, call
'Arg_parser' with 'flags' = 'in_order'.
The argument '--' terminates all options; any following arguments are
treated as non-option arguments, even if they begin with a hyphen.
The syntax for optional option arguments is '-<short_option><argument>'
(without whitespace), or '--<long_option>=<argument>'.
The syntax of options with an optional argument is
'-<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
{
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
{
@ -68,6 +74,7 @@ private:
const std::string empty_arg;
std::string error_;
std::vector< Record > data;
int argv_index_;
bool parse_long_option( const char * const opt, const char * const arg,
const Option options[], int & argind );
@ -76,13 +83,14 @@ private:
public:
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).
Arg_parser( const char * const opt, const char * const arg,
const Option options[] );
const std::string & error() const { return error_; }
int argv_index() const { return argv_index_; }
// The number of arguments parsed. May be different from argc.
int arguments() const { return data.size(); }

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C version)
Copyright (C) 2006-2022 Antonio Diaz Diaz.
/* Arg_parser - POSIX/GNU command-line argument parser. (C version)
Copyright (C) 2006-2026 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and
binary forms, with or without modification, are permitted provided
@ -17,12 +17,21 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.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' */
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,
const char * const long_name,
const char * const argument )
static char set_argument( Arg_parser * const ap, const char * const argument )
{
struct ap_Record * p;
void * tmp = ap_resize_buffer( ap->data,
( ap->data_size + 1 ) * sizeof (struct ap_Record) );
ap_Record * const p = &(ap->data[ap->data_size-1]);
const int len = strlen( argument );
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;
ap->data = (struct ap_Record *)tmp;
p = &(ap->data[ap->data_size]);
ap->data = (ap_Record *)tmp;
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->argument = 0;
if( long_name )
{
const int len = strlen( long_name );
p->parsed_name = (char *)malloc( len + 2 + 1 );
if( !p->parsed_name ) return 0;
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 );
if( !p->parsed_name ) return 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;
}
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 );
void * tmp = ap_resize_buffer( ap->error, ap->error_size + len + 1 );
ap_Record * const p = push_back_record( ap );
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;
ap->error = (char *)tmp;
strncpy( ap->error + ap->error_size, msg, len + 1 );
ap->error_size += len;
memcpy( ap->error, s1, l1 );
memcpy( ap->error + l1, s2, l2 );
memcpy( ap->error + l1 + l2, s3, l3 + 1 );
return 1;
}
static void free_data( struct Arg_parser * const ap )
static void free_data( Arg_parser * const ap )
{
int 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. */
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 struct ap_Option options[],
int * const argindp )
const ap_Option options[], int * const argindp )
{
unsigned len;
int index = -1, i;
@ -119,62 +147,43 @@ static char parse_long_option( struct Arg_parser * const ap,
}
if( ambig && !exact )
{
add_error( ap, "option '" ); add_error( ap, opt );
add_error( ap, "' is ambiguous" );
return 1;
}
return set_error( ap, "option '", opt, "' is ambiguous" );
if( index < 0 ) /* nothing found */
{
add_error( ap, "unrecognized option '" ); add_error( ap, opt );
add_error( ap, "'" );
return 1;
}
return set_error( ap, "unrecognized option '", opt, "'" );
++*argindp;
if( !push_back_option( ap, options[index].code, options[index].long_name ) )
return 0;
if( opt[len+2] ) /* '--<long_option>=<argument>' syntax */
{
if( options[index].has_arg == ap_no )
{
add_error( ap, "option '--" ); add_error( ap, options[index].long_name );
add_error( ap, "' doesn't allow an argument" );
return 1;
}
return set_error( ap, "option '--", options[index].long_name,
"' doesn't allow an argument" );
if( options[index].has_arg == ap_yes && !opt[len+3] )
{
add_error( ap, "option '--" ); add_error( ap, options[index].long_name );
add_error( ap, "' requires an argument" );
return 1;
}
return push_back_record( ap, options[index].code,
options[index].long_name, &opt[len+3] );
return set_error( ap, "option '--", options[index].long_name,
"' requires an argument" );
return set_argument( ap, &opt[len+3] ); /* argument may be empty */
}
if( options[index].has_arg == ap_yes )
if( options[index].has_arg == ap_yes || options[index].has_arg == ap_yesme )
{
if( !arg || !arg[0] )
{
add_error( ap, "option '--" ); add_error( ap, options[index].long_name );
add_error( ap, "' requires an argument" );
return 1;
}
if( !arg || ( options[index].has_arg == ap_yes && !arg[0] ) )
return set_error( ap, "option '--", options[index].long_name,
"' requires an argument" );
++*argindp;
return push_back_record( ap, options[index].code,
options[index].long_name, arg );
return set_argument( ap, arg ); /* argument may be empty */
}
return push_back_record( ap, options[index].code,
options[index].long_name, 0 );
return 1;
}
/* 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 struct ap_Option options[],
int * const argindp )
const ap_Option options[], int * const argindp )
{
int cind = 1; /* character index in opt */
@ -191,49 +200,42 @@ static char parse_short_option( struct Arg_parser * const ap,
{ index = i; break; }
if( index < 0 )
{
add_error( ap, "invalid option -- '" ); add_error( ap, code_str );
add_error( ap, "'" );
return 1;
}
return set_error( ap, "invalid option -- '", code_str, "'" );
if( !push_back_option( ap, c, 0 ) ) return 0;
if( opt[++cind] == 0 ) { ++*argindp; cind = 0; } /* opt finished */
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;
}
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] )
{
add_error( ap, "option requires an argument -- '" );
add_error( ap, code_str ); add_error( ap, "'" );
return 1;
}
if( !arg || ( options[index].has_arg == ap_yes && !arg[0] ) )
return set_error( ap, "option requires an argument -- '", code_str, "'" );
++*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;
}
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 struct ap_Option options[], const char in_order )
const ap_Option options[], const int flags )
{
const char ** non_options = 0; /* skipped non-options */
int non_options_size = 0; /* number of skipped non-options */
int argind = 1; /* index in argv */
char done = 0; /* false until success */
char done = 0; /* false until success or error */
ap->data = 0;
ap->error = 0;
ap->data_size = 0;
ap->error_size = 0;
ap->argv_index = argc;
if( argc < 2 || !argv || !options ) return 1;
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 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;
if( ch2 == '-' )
{
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;
}
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
{
if( in_order )
{ if( !push_back_record( ap, 0, 0, argv[argind++] ) ) goto out; }
else
{
void * tmp = ap_resize_buffer( non_options,
( non_options_size + 1 ) * sizeof *non_options );
if( !tmp ) goto out;
const int min_size = ( non_options_size + 1 ) * sizeof non_options[0];
void * tmp = ap_resize_buffer( non_options, min_size );
if( !tmp ) goto oom;
non_options = (const char **)tmp;
non_options[non_options_size++] = argv[argind++];
}
}
}
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;
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 )
if( !push_back_record( ap, 0, 0, argv[argind++] ) ) goto out;
if( !push_back_argument( ap, argv[argind++] ) ) goto oom;
}
done = 1;
out: if( non_options ) free( non_options );
oom: if( non_options ) free( non_options );
return done;
}
void ap_free( struct Arg_parser * const ap )
void ap_free( Arg_parser * const ap )
{
free_data( ap );
if( ap->error ) { free( ap->error ); ap->error = 0; }
ap->error_size = 0;
}
const char * ap_error( const struct Arg_parser * const ap )
{ return ap->error; }
const char * ap_error( const Arg_parser * const ap ) { 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 )
{ return ap->data_size; }
int ap_arguments( const Arg_parser * const ap ) { return ap->data_size; }
int ap_code( const struct Arg_parser * const ap, const int i )
int ap_code( const Arg_parser * const ap, const int i )
{
if( i < 0 || i >= ap_arguments( ap ) ) return 0;
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 "";
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 "";
return ap->data[i].argument;

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C version)
Copyright (C) 2006-2022 Antonio Diaz Diaz.
/* Arg_parser - POSIX/GNU command-line argument parser. (C version)
Copyright (C) 2006-2026 Antonio Diaz Diaz.
This library is free software. Redistribution and use in source and
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
option codes, option arguments, and non-option arguments.
In case of error, 'ap_error' returns a non-null pointer to an error
message.
In case of error, 'ap_error' returns a pointer to an error message.
'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
option. A code value outside the unsigned char range means a long-only
option.
Arg_parser normally makes it appear as if all the option arguments
were specified before all the non-option arguments for the purposes
of parsing, even if the user of your program intermixed option and
non-option arguments. If you want the arguments in the exact order
the user typed them, call 'ap_init' with 'in_order' = true.
Arg_parser normally makes it appear as if all the options were specified
before all the non-option arguments for the purposes of parsing, even if
the user of your program intermixed options and non-option arguments. If
you want the arguments in the exact order the user typed them, call
'ap_init' with 'flags' = 'ap_in_order'.
The argument '--' terminates all options; any following arguments are
treated as non-option arguments, even if they begin with a hyphen.
The syntax for optional option arguments is '-<short_option><argument>'
(without whitespace), or '--<long_option>=<argument>'.
The syntax of options with an optional argument is
'-<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
extern "C" {
#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 ) */
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;
char * parsed_name;
char * argument;
};
} ap_Record;
struct Arg_parser
typedef struct Arg_parser
{
struct ap_Record * data;
ap_Record * data;
char * error;
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 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. */
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.
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). */
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
}

View file

@ -1,5 +1,5 @@
/* Arg_parser - POSIX/GNU command line argument parser. (C version)
Copyright (C) 2006-2022 Antonio Diaz Diaz.
/* Arg_parser - POSIX/GNU command-line argument parser. (C version)
Copyright (C) 2006-2026 Antonio Diaz Diaz.
This program is free software: you have unlimited permission to
copy, distribute, and modify it.
@ -10,9 +10,9 @@
*/
/*
Exit status: 0 for a normal exit, 1 for environmental problems
(file not found, invalid flags, I/O errors, etc), 2 to indicate a
corrupt or invalid input file, 3 for an internal consistency error
(e.g., bug) which caused carg_parser to panic.
(file not found, invalid command-line options, I/O errors, etc), 2 to
indicate a corrupt or invalid input file, 3 for an internal consistency
error (e.g., bug) which caused carg_parser to panic.
*/
#include <limits.h>
@ -24,54 +24,61 @@
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 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(
"Arg_parser - POSIX/GNU command-line argument parser. (C version)\n"
"\nArg_parser is an argument parser that follows POSIX and GNU conventions for\n"
"command line arguments. There exist C++ and C versions of Arg_parser. The\n"
"command-line arguments. There exist C++ and C versions of Arg_parser. The\n"
"C++ version is implemented as a C++ class, while the C version is\n"
"implemented as a single struct plus associated functions. Both are simpler,\n"
"easier to use, and safer than 'getopt_long'.\n"
"\nFor maximum stability, Arg_parser is self-contained. It extracts all the\n"
"information it needs from its arguments to avoid refering to them later.\n"
"This avoids index-out-of-bounds errors.\n"
"\nArg_parser does not modify its arguments, nor uses any global variables. So\n"
"you may create more than one parser in your program if you need or want to.\n"
"information it needs from its arguments to avoid referring to them later.\n"
"This avoids index-out-of-bounds errors and allows the parser object to be\n"
"passed as argument to other functions for further analysis.\n"
"\nArg_parser does not modify its arguments (argc, argv), nor uses any global\n"
"variables.\n"
"\nThe C++ version of Arg_parser can also parse options from configuration\n"
"files.\n"
"\nTo use Arg_parser in your own programs simply copy the files 'carg_parser.h'\n"
"and 'carg_parser.c' in your source tree. See the file 'cmain.c' for an\n"
"example of use.\n"
"\nUsage: %s [options]\n", invocation_name );
printf( "\nOptions:\n"
"\nThe C version of Arg_parser is provided in the files 'carg_parser.h' and\n"
"'carg_parser.c'. To learn how to use Arg_parser in your C programs, see the\n"
"C example in the manual and the example file 'cmain.c' in the source\n"
"tarball.\n", stdout );
printf( "\nUsage: %s [options]\n", invocation_name );
fputs( "\nOptions:\n"
" -h, --help display this help and exit\n"
" -V, --version output version information and exit\n"
" -a, --append example of option with no argument\n"
" -b, --block=<arg> example of option with required argument\n"
" -c, --casual[=<arg>] example of option with optional argument\n"
" -e, --empty=<arg> example of option with maybe empty argument\n"
" -o <arg> example of short only option\n"
" --orphan example of long only option\n"
" --long-only example of long only option\n"
" -q, --quiet quiet operation\n"
" -u, --uncaught example of intentional bug\n"
" -v, --verbose verbose operation\n" );
if( verbose )
printf( " -H, --hidden example of hidden option (shown with -v -h)\n" );
printf( "\nReport bugs to arg-parser-bug@nongnu.org\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( "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"
"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[] )
{
char verbose = 0;
const struct ap_Option options[] =
const ap_Option options[] =
{
/* code, long_name, has_arg (no/yes/maybe/yesme) */
{ '.', 0, ap_maybe },
{ '0', 0, ap_maybe },
{ '1', 0, ap_maybe },
{ '2', 0, ap_maybe },
{ '3', 0, ap_maybe },
{ '4', 0, ap_maybe },
{ '5', 0, ap_maybe },
{ '6', 0, ap_maybe },
{ '7', 0, ap_maybe },
{ '8', 0, ap_maybe },
{ '9', 0, ap_maybe },
{ 'a', "append", 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 },
{ 256, "orphan", 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;
int argind;
Arg_parser parser;
int flags;
if( argc > 0 ) invocation_name = argv[0];
if( !ap_init( &parser, argc, argv, options, 0 ) )
{ show_error( "Not enough memory.", 0, 0 ); return 1; }
if( ap_error( &parser ) ) /* bad option */
{ 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 )
{
const int code = ap_code( &parser, argind );
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 = 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 */
if( !ap_init( &parser, argc, argv, options, ap_neg_non_opt ) )
{ 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 );
fputs( "(options reordered"
" and negative numbers treated as non-options)\n", stdout );
if( verbosity == 1 ) return 0;
for( argind = 0; argind < ap_arguments( &parser ); ++argind )
for( flags = 1; flags < 16; ++flags )
{
const int code = ap_code( &parser, argind );
const char * const arg = ap_argument( &parser, argind );
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 );
int i;
if( flags == ap_neg_non_opt ) continue; /* done above */
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 );
return 0;
}

View file

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

View file

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

@ -12,20 +12,22 @@ File: arg_parser.info, Node: Top, Next: Introduction, Up: (dir)
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:
* Introduction:: Purpose and features of Arg_parser
* Argument syntax:: By convention, options start with a hyphen
* Initialization:: Parsing arguments and reporting errors
* 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
* 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,
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
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
implemented as a single struct plus associated functions. Both are simpler,
easier to use, and safer than 'getopt_long'.
For maximum stability, Arg_parser is self-contained. It extracts all the
information it needs from its arguments to avoid refering to them later.
This avoids index-out-of-bounds errors.
information it needs from its arguments to avoid referring to them later.
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 you may create more than one parser in your program if you need or want
to.
Arg_parser does not modify its arguments (argc, argv), nor uses any global
variables.
The C++ version of Arg_parser can also parse options from configuration
files.
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, including GNU ddrescue, GNU ed, lzip, GNU ocrad, tarlz, and
zutils, with excellent results.
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,
including GNU ddrescue, GNU ed, lzip, GNU ocrad, tarlz, and zutils, with
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.
* 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'.
* A command-line argument is an option if it begins with a hyphen ('-').
* Option names are single alphanumeric characters.
* Certain options require an argument.
* An option and its argument may or may not appear as separate tokens.
(In other words, the whitespace separating them is optional). Thus,
'-o foo' and '-ofoo' are equivalent.
(In other words, the whitespace separating them is optional, unless the
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.
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
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
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
treated as non-option arguments, even if they begin with a hyphen.
* A token consisting of a single hyphen character is interpreted as an
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
interpretation is left up to the particular application program.
* GNU adds "long options" to these conventions. Long options consist of
'--' followed by a name made of alphanumeric characters and hyphens.
Option names are typically one to three words long, with hyphens to
separate words. Users can abbreviate the option names as long as the
abbreviations are unique.
GNU adds "long options" to these conventions:
* A long option consists of two hyphens ('--') followed by a name 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.
* 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 '='.
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
'--<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'
and 'arg_parser.cc' in your source tree. See the file 'main.cc' for an
example of use.
The C++ version of Arg_parser is provided in the files 'arg_parser.h' and
'arg_parser.cc'. To learn how to use Arg_parser in your C++ programs, *note
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
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'.
The argument OPTIONS must be an array of these structures, one for
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:
'int code'
-- Member: 'int' code
This member is the code that identifies the option, normally the
short-option character. Must be different from 0. A code value
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
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
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
Option OPTIONS[], const bool IN_ORDER = false )
Option OPTIONS[], const int FLAGS = 0 )
Constructor. Reads the arguments in ARGV and parses all options,
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,
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
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.
In case of error, 'error().size()' will be non-zero.
In case of error, 'error().size()' returns nonzero.
-- Function: const std::string & error () const
Use this funtion to verify that the arguments have been correctly
parsed by the constructor. If there was an error parsing the arguments,
'error' returns a non-empty error message explaining the cause.
Use this funtion to check that the arguments have been correctly
parsed by the constructor. If there was an error parsing the
arguments, 'error' returns an error message explaining the cause, else
it returns an empty string.

File: arg_parser.info, Node: Using Arg_parser, Next: C version, Prev: Initialization, Up: Top
-- Function: int argv_index () const
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
calling 'error', the options and arguments parsed can be accessed by means
of the following functions:
After a successful call to the constructor, which must be checked by calling
'error', the options and arguments parsed can be accessed by means of the
following functions:
-- Function: int arguments () const
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
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
is non-zero, 'argument(I)' is the option's argument (or is empty if
the option does not have an argument). If the code returned is zero,
is nonzero, 'argument(I)' is the option's argument (or is empty if the
option does not have an argument). If the code returned is zero,
'argument(I)' is a non-option argument.
-- 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
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
'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
'carg_parser.h' and 'carg_parser.c' in your source tree. See the file
'cmain.c' for an example of use.
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, *note C
example::, and the file 'cmain.c' in the source tarball.
Then you need to declare a variable of type 'struct Arg_parser', pass
its address to 'ap_init' to initialize it, and verify that 'ap_error'
returns 0.
4.1 Parsing arguments and reporting errors
==========================================
'struct ap_Option' is identical to 'struct Option', except that
'Has_arg' becomes 'ap_Has_arg', and the names of its three values are also
prefixed with 'ap_'. *Note struct Option::, for details about the members.
You need to declare a variable of type 'Arg_parser', pass its address to
'ap_init' to initialize it, and check that 'ap_error' returns 0.
-- Function: char ap_init ( struct Arg_parser * const AP, const int ARGC,
const char * const ARGV[], const struct ap_Option OPTIONS[],
const char IN_ORDER )
'struct ap_Option' is identical to 'struct Option', except that 'Has_arg'
becomes 'ap_Has_arg', and the names of its four values are also prefixed
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,
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
non-null pointer.
enough memory, else 1 (even if errors are found). In case of error,
'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.
-- Function: const char * ap_error ( const struct Arg_parser * const AP )
Use this funtion to verify that the arguments have been correctly
-- Function: const char * ap_error ( const Arg_parser * const AP )
Use this funtion to check that the arguments have been correctly
parsed by 'ap_init'. If there was an error parsing the arguments,
'ap_error' returns a pointer to an error message explaining the cause,
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
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
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
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
is zero, 'ap_argument(I)' is a non-option argument.
-- Function: const char * ap_parsed_name ( const struct Arg_parser * const
AP, const int I )
-- Function: const char * ap_parsed_name ( const Arg_parser * const AP,
const int I )
This function returns the full name of the option parsed (short or
long) at position I. It may be useful to produce more accurate
diagnostic messages. For non-option arguments it returns the empty
string.
-- Function: const char * ap_argument ( const struct Arg_parser * const
AP, const int I )
-- Function: const char * ap_argument ( const Arg_parser * const AP, const
int I )
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
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
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
@ -285,7 +583,7 @@ all eternity, if not longer.
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
*************
@ -295,24 +593,68 @@ Concept index
* argument syntax: Argument syntax. (line 6)
* bugs: Problems. (line 6)
* C example: C example. (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)
* initialization: Initialization. (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:
Node: Top224
Node: Introduction951
Node: Argument syntax2085
Node: Initialization4504
Ref: struct Option5034
Node: Using Arg_parser6979
Node: C version8461
Node: Problems11640
Node: Concept index12193
Node: Introduction1048
Node: Argument syntax3879
Node: C++ version6484
Ref: struct Option7131
Ref: enum Flags8551
Node: C version13221
Node: C++ example17007
Node: C example21870
Node: Problems24803
Node: Concept index25353
Node: Function index26117

End Tag Table

View file

@ -6,19 +6,19 @@
@finalout
@c %**end of header
@set UPDATED 5 February 2022
@set VERSION 1.17
@set UPDATED 10 March 2026
@set VERSION 1.21
@dircategory Libraries
@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
@ifnothtml
@titlepage
@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}
@author by Antonio Diaz Diaz
@ -38,15 +38,17 @@ This manual is for Arg_parser (version @value{VERSION}, @value{UPDATED}).
@menu
* Introduction:: Purpose and features of Arg_parser
* Argument syntax:: By convention, options start with a hyphen
* Initialization:: Parsing arguments and reporting errors
* 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
* Concept index:: Index of concepts
* Function index:: Index of functions, constants, and variables
@end menu
@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,
distribute, and modify it.
@ -59,17 +61,18 @@ distribute, and modify it.
@uref{http://www.nongnu.org/arg-parser/arg_parser.html,,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
implemented as a single struct plus associated functions. Both are simpler,
easier to use, and safer than @samp{getopt_long}.
For maximum stability, Arg_parser is self-contained. It extracts all the
information it needs from its arguments to avoid refering to them later.
This avoids index-out-of-bounds errors.
information it needs from its arguments to avoid referring to them later.
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
you may create more than one parser in your program if you need or want to.
Arg_parser does not modify its arguments (argc, argv), nor uses any global
variables.
The C++ version of Arg_parser can also parse options from configuration
files.
@ -83,132 +86,226 @@ including
@uref{http://www.nongnu.org/lzip/lzip.html,,lzip},
@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/zutils/zutils.html,,zutils}, with
excellent results.
@uref{http://www.nongnu.org/zutils/zutils.html,,zutils}, with excellent
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
@chapter Syntax of command line arguments
@chapter Syntax of command-line arguments
@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.
@itemize @bullet
@item A command line argument is an option if it begins with a hyphen
delimiter (@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 A command-line argument is an option if it begins with a hyphen
(@samp{-}).
@item Option names are single alphanumeric characters.
@item Certain options require an argument.
@item An option and its argument may or may not appear as separate
tokens. (In other words, the whitespace separating them is optional).
Thus, @w{@samp{-o foo}} and @samp{-ofoo} are equivalent.
@item An option and its argument may or may not appear as separate tokens.
(In other words, the whitespace separating them is optional, unless the
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.
Arg_parser normally makes it appear as if all the option arguments were
specified before all the non-option arguments for the purposes of parsing,
even if the user of your program intermixed option and non-option arguments.
If you want the arguments in the exact order the user typed them, call
@samp{Arg_parser} with @w{@var{in_order} = true}.
Arg_parser normally makes it appear as if all the options were specified
before all the non-option arguments for the purposes of parsing, even if the
user of your program intermixed options and non-option arguments. If you
want the arguments in the exact order the user typed them, call
@samp{Arg_parser} with @w{@var{flags} = @samp{in_order}}.
@item The argument @samp{--} terminates all options; any following arguments
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
an ordinary non-option argument. By convention, it is used to specify
input from or output to the standard input and output streams.
@item A token consisting of a single hyphen character is interpreted as an
ordinary non-option argument. By convention, it is used to specify standard
input, standard output, or a file named @samp{-}.
@item Options may be supplied in any order, or appear multiple times.
The interpretation is left up to the particular application program.
@end itemize
@item GNU adds @dfn{long options} to these conventions. Long options
consist of @samp{--} followed by a name made of alphanumeric characters
and hyphens. Option names are typically one to three words long, with
hyphens to separate words. Users can abbreviate the option names as long
as the abbreviations are unique.
GNU adds @dfn{long options} to these conventions:
@itemize @bullet
@item A long option consists of two hyphens (@samp{--}) followed by a name
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
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.
@item The syntax for optional option arguments is
@samp{-<short_option><argument>} (without whitespace), or
@samp{--<long_option>=<argument>}.
Thus, @w{@option{--foo bar}} and @option{--foo=bar} are equivalent.
@end itemize
The syntax of options with an optional argument is
@option{-<short_option><argument>} (without whitespace), or
@option{--<long_option>=<argument>}.
@node Initialization
@chapter Parsing arguments and reporting errors
@cindex initialization
The syntax of options with an empty argument is @option{-<short_option> ""},
@option{--<long_option> ""}, or @option{--<long_option>=""}.
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
configuration file or other source.
@anchor{struct Option}
@deffn {Data Type} struct Option
This structure describes a single option for the sake of
@samp{Arg_parser}. The argument @var{options} must be an array of these
structures, one for each option. Terminate the array with an element
containing a code which is zero.
@deftp {Data Type} struct Option
This structure describes a single option for the sake of @samp{Arg_parser}.
The argument @var{options} must be an array of these structures, one for
each option. Terminate the array with an element containing a code which is
zero. @samp{struct Option} has the following members:
@samp{struct Option} has the following members:
@table @code
@item int code
@deftypevr Member @code{int} code
This member is the code that identifies the option, normally the
short-option character. Must be different from 0. A code value outside
the unsigned char range means a long-only option.
short-option character. Must be different from 0. A code value outside the
unsigned char range means a long-only option.
@end deftypevr
@item const char * long_name
This member is the long option name. It is a zero-terminated string. A
null or empty long_name means a short-only option.
@deftypevr Member @code{const char *} long_name
This member is the long option name. It is a zero-terminated string. A null
or empty long_name means a short-only option.
@end deftypevr
@item enum Has_arg has_arg
This member says whether the option takes an argument. It has three
valid values: @samp{no}, @samp{yes}, and @samp{maybe}.
@end table
@end deffn
@deftypevr Member @code{enum Has_arg} has_arg
This member says whether the option takes an argument. It has four valid
values: @samp{no}, @samp{yes}, @samp{maybe}, and @samp{yesme}, meaning
respectively @samp{no argument}, @samp{non-empty argument required},
@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,
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
@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
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 that a
single token may produce an undefined number of short options. In case
of error, @samp{error().size()} will be non-zero.
Restricted constructor. Parses a single token (plus an optional second 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 that a single
token may produce an undefined number of short options. In case of error,
@samp{error().size()} returns nonzero.
@end defun
@deftypefun {const std::string &} error () const
Use this funtion to verify that the arguments have been correctly parsed
by the constructor. If there was an error parsing the arguments,
@samp{error} returns a non-empty error message explaining the cause.
Use this funtion to check that the arguments have been correctly parsed by
the constructor. If there was an error parsing the arguments, @samp{error}
returns an error message explaining the cause, else it returns an empty
string.
@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
@chapter Using the class @samp{Arg_parser}
@cindex using Arg_parser
@section Reading the options and arguments parsed
After a successful call to the constructor, which must be verified by
calling @samp{error}, the options and arguments parsed can be accessed
by means of the following functions:
After a successful call to the constructor, which must be checked by calling
@samp{error}, the options and arguments parsed can be accessed by means of
the following functions:
@deftypefun int arguments () const
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
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
code returned is non-zero, @samp{argument(@var{i})} is the option's
argument (or is empty if the option does not have an argument). If the
code returned is zero, @samp{argument(@var{i})} is a non-option argument.
values for @var{i} range from 0 to @w{@samp{arguments() - 1}}. If the code
returned is nonzero, @samp{argument(@var{i})} is the option's argument (or
is empty if the option does not have an argument). If the code returned is
zero, @samp{argument(@var{i})} is a non-option argument.
@end deftypefun
@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
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
returned by @samp{code(@var{i})}. Valid values for @var{i} range from 0
to @w{@samp{arguments() - 1}}.
returned by @samp{code(@var{i})}. Valid values for @var{i} range from 0 to
@w{@samp{arguments() - 1}}. If the argument does not exist, the empty string
is returned.
@end deftypefun
@ -241,71 +339,303 @@ to @w{@samp{arguments() - 1}}.
@chapter Using the C version of Arg_parser
@cindex C version
To use the C version of Arg_parser in your own programs first copy the files
@samp{carg_parser.h} and @samp{carg_parser.c} in your source tree. See the
file @samp{cmain.c} for an example of use.
The C version of Arg_parser is provided in the files @file{carg_parser.h}
and @file{carg_parser.c}. To learn how to use Arg_parser in your C programs,
@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},
pass its address to @samp{ap_init} to initialize it, and verify that
@samp{ap_error} returns 0.
@section Parsing arguments and reporting errors
@samp{struct ap_Option} is identical to @samp{struct Option}, except
that @samp{Has_arg} becomes @samp{ap_Has_arg}, and the names of its
three values are also prefixed with @samp{ap_}. @xref{struct Option},
for details about the members.
You need to declare a variable of type @samp{Arg_parser}, pass its address
to @samp{ap_init} to initialize it, and check that @samp{ap_error} returns 0.
@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} )
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. In case of error, @samp{ap_error} will
return a non-null pointer.
@samp{struct ap_Option} is identical to @samp{struct Option}, except that
@samp{Has_arg} becomes @samp{ap_Has_arg}, and the names of its four values
are also prefixed with @samp{ap_}. @xref{struct Option}, for details about
the members.
@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
@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.
@end deftypefun
@deftypefun {const char *} ap_error ( const struct Arg_parser * const @var{ap} )
Use this funtion to verify that the arguments have been correctly parsed
by @samp{ap_init}. If there was an error parsing the arguments,
@samp{ap_error} returns a pointer to an error message explaining the
cause, else it returns a null pointer.
@deftypefun {const char *} ap_error ( const Arg_parser * const @var{ap} )
Use this funtion to check that the arguments have been correctly parsed by
@samp{ap_init}. If there was an error parsing the arguments, @samp{ap_error}
returns a pointer to an error message explaining the cause, else it returns
a null pointer.
@end deftypefun
After a successful call to @samp{ap_init}, which must be verified by
calling @samp{ap_error}, the options and arguments parsed can be accessed
by means of the following functions:
@deftypefun {int} ap_argv_index ( const Arg_parser * const @var{ap} )
Return the index in @var{argv} of the first argument skipped (the first
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 number is usually different from argc.
@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
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
argument (or is empty if the option does not have an argument). If the
code returned is zero, @samp{ap_argument(@var{i})} is a non-option argument.
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 code
returned is zero, @samp{ap_argument(@var{i})} is a non-option argument.
@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
position @var{i}. It may be useful to produce more accurate diagnostic
messages. For non-option arguments it returns the empty string.
@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
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
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
When you are finished, you should free all dynamically allocated data
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
@chapter Reporting bugs
@cindex bugs
@ -326,4 +656,14 @@ you can find by running @w{@samp{arg_parser --version}}.
@printindex cp
@node Function index
@unnumbered Index of constants and variables
@printindex vr
@sp 1
@unnumbered Index of functions
@printindex fn
@bye

View file

@ -1,32 +1,34 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.16.
.TH CARG_PARSER "1" "February 2022" "carg_parser 1.17" "User Commands"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.2.
.TH CARG_PARSER "1" "March 2026" "carg_parser 1.21" "User Commands"
.SH NAME
carg_parser \- test program for Arg_parser
.SH SYNOPSIS
.B carg_parser
[\fI\,options\/\fR]
.SH DESCRIPTION
Arg_parser \- POSIX/GNU command line argument parser. (C version)
Arg_parser \- POSIX/GNU command\-line argument parser. (C version)
.PP
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
implemented as a single struct plus associated functions. Both are simpler,
easier to use, and safer than 'getopt_long'.
.PP
For maximum stability, Arg_parser is self\-contained. It extracts all the
information it needs from its arguments to avoid refering to them later.
This avoids index\-out\-of\-bounds errors.
information it needs from its arguments to avoid referring to them later.
This avoids index\-out\-of\-bounds errors and allows the parser object to be
passed as argument to other functions for further analysis.
.PP
Arg_parser does not modify its arguments, nor uses any global variables. So
you may create more than one parser in your program if you need or want to.
Arg_parser does not modify its arguments (argc, argv), nor uses any global
variables.
.PP
The C++ version of Arg_parser can also parse options from configuration
files.
.PP
To use Arg_parser in your own programs simply copy the files 'carg_parser.h'
and 'carg_parser.c' in your source tree. See the file 'cmain.c' for an
example of use.
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.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
@ -44,10 +46,13 @@ example of option with required argument
\fB\-c\fR, \fB\-\-casual[=\fR<arg>]
example of option with optional argument
.TP
\fB\-e\fR, \fB\-\-empty=\fR<arg>
example of option with maybe empty argument
.TP
\fB\-o\fR <arg>
example of short only option
.TP
\fB\-\-orphan\fR
\fB\-\-long\-only\fR
example of long only option
.TP
\fB\-q\fR, \fB\-\-quiet\fR
@ -63,7 +68,7 @@ Report bugs to arg\-parser\-bug@nongnu.org
.br
Arg_parser home page: http://www.nongnu.org/arg\-parser/arg_parser.html
.SH COPYRIGHT
Copyright \(co 2022 Antonio Diaz Diaz.
Copyright \(co 2026 Antonio Diaz Diaz.
License 2\-clause BSD.
.br
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)
Copyright (C) 2006-2022 Antonio Diaz Diaz.
/* Arg_parser - POSIX/GNU command-line argument parser. (C++ version)
Copyright (C) 2006-2026 Antonio Diaz Diaz.
This program is free software: you have unlimited permission to
copy, distribute, and modify it.
@ -10,9 +10,9 @@
*/
/*
Exit status: 0 for a normal exit, 1 for environmental problems
(file not found, invalid flags, I/O errors, etc), 2 to indicate a
corrupt or invalid input file, 3 for an internal consistency error
(e.g., bug) which caused arg_parser to panic.
(file not found, invalid command-line options, I/O errors, etc), 2 to
indicate a corrupt or invalid input file, 3 for an internal consistency
error (e.g., bug) which caused arg_parser to panic.
*/
#include <climits>
@ -24,48 +24,54 @@
#include "arg_parser.h"
namespace {
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
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(
"Arg_parser - POSIX/GNU command-line argument parser. (C++ version)\n"
"\nArg_parser is an argument parser that follows POSIX and GNU conventions for\n"
"command line arguments. There exist C++ and C versions of Arg_parser. The\n"
"command-line arguments. There exist C++ and C versions of Arg_parser. The\n"
"C++ version is implemented as a C++ class, while the C version is\n"
"implemented as a single struct plus associated functions. Both are simpler,\n"
"easier to use, and safer than 'getopt_long'.\n"
"\nFor maximum stability, Arg_parser is self-contained. It extracts all the\n"
"information it needs from its arguments to avoid refering to them later.\n"
"This avoids index-out-of-bounds errors.\n"
"\nArg_parser does not modify its arguments, nor uses any global variables. So\n"
"you may create more than one parser in your program if you need or want to.\n"
"information it needs from its arguments to avoid referring to them later.\n"
"This avoids index-out-of-bounds errors and allows the parser object to be\n"
"passed as argument to other functions for further analysis.\n"
"\nArg_parser does not modify its arguments (argc, argv), nor uses any global\n"
"variables.\n"
"\nThe C++ version of Arg_parser can also parse options from configuration\n"
"files.\n"
"\nTo use Arg_parser in your own programs simply copy the files 'arg_parser.h'\n"
"and 'arg_parser.cc' in your source tree. See the file 'main.cc' for an\n"
"example of use.\n"
"\nUsage: %s [options]\n", invocation_name );
std::printf( "\nOptions:\n"
"\nThe C++ version of Arg_parser is provided in the files 'arg_parser.h' and\n"
"'arg_parser.cc'. To learn how to use Arg_parser in your C++ programs, see\n"
"the C++ example in the manual and the example file 'main.cc' in the source\n"
"tarball.\n", stdout );
std::printf( "\nUsage: %s [options]\n", invocation_name );
std::fputs( "\nOptions:\n"
" -h, --help display this help and exit\n"
" -V, --version output version information and exit\n"
" -a, --append example of option with no argument\n"
" -b, --block=<arg> example of option with required argument\n"
" -c, --casual[=<arg>] example of option with optional argument\n"
" -e, --empty=<arg> example of option with maybe empty argument\n"
" -o <arg> example of short only option\n"
" --orphan example of long only option\n"
" --long-only example of long only option\n"
" -q, --quiet quiet operation\n"
" -u, --uncaught example of intentional bug\n"
" -v, --verbose verbose operation\n" );
if( verbose )
std::printf( " -H, --hidden example of hidden option (shown with -v -h)\n" );
std::printf( "\nReport bugs to arg-parser-bug@nongnu.org\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( "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"
"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 );
}
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
int main( const int argc, const char * const argv[] )
{
bool verbose = false;
if( argc > 0 ) invocation_name = argv[0];
const Arg_parser::Option options[] =
{
// code, long_name, has_arg (no/yes/maybe/yesme)
{ '.', 0, Arg_parser::maybe },
{ '0', 0, Arg_parser::maybe },
{ '1', 0, Arg_parser::maybe },
{ '2', 0, Arg_parser::maybe },
{ '3', 0, Arg_parser::maybe },
{ '4', 0, Arg_parser::maybe },
{ '5', 0, Arg_parser::maybe },
{ '6', 0, Arg_parser::maybe },
{ '7', 0, Arg_parser::maybe },
{ '8', 0, Arg_parser::maybe },
{ '9', 0, Arg_parser::maybe },
{ 'a', "append", 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 },
{ 256, "orphan", 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 );
if( parser.error().size() ) // bad option
{ show_error( parser.error().c_str(), 0, true ); return 1; }
for( int argind = 0; argind < parser.arguments(); ++argind )
{
const int code = parser.code( argind );
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." );
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;
}
} // 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 );
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;
}
else // non-option
std::printf( "non-option argument '%s'", arg );
for( int flags = 1; flags < 16; ++flags )
{
if( flags == Arg_parser::neg_non_opt ) continue; // done above
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;
}

View file

@ -1,6 +1,6 @@
#! /bin/sh
# check script for Arg_parser - POSIX/GNU command line argument parser.
# Copyright (C) 2011-2022 Antonio Diaz Diaz.
# check script for Arg_parser - POSIX/GNU command-line argument parser.
# Copyright (C) 2011-2026 Antonio Diaz Diaz.
#
# This script is free software: you have unlimited permission
# to copy, distribute, and modify it.
@ -26,7 +26,6 @@ if [ -d tmp ] ; then rm -rf tmp ; fi
mkdir tmp
cd "${objdir}"/tmp || framework_failure
in="${testdir}"/test.txt
fail=0
test_failed() { fail=1 ; printf "\n$1" ; [ -z "$2" ] || printf "($2)" ; }
@ -40,21 +39,25 @@ for i in "${PARSER}" "${CPARSER}" ; do
"$i" -h -v > /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 ||
test_failed $LINENO "$i"
cmp "${in}" out || test_failed $LINENO "$i"
"$i" --append -a -b 5 --block 10 -c -carg --casual= --casual=arg -e "" -1 \
-earg --empty= --empty=arg -H -o file --l -v --verbose -.5 -3.14 -inf \
-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 ||
test_failed $LINENO "$i"
cmp "${in}" out || test_failed $LINENO "$i"
"$i" -1 -.5 file1 -3.14 -inf file2 --appen -ab5 --bloc=10 -c -carg --casu \
--casu=arg -e "" -earg --empt="" --empt=arg -H -ofile --long-o -v \
--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 ||
test_failed $LINENO "$i"
cmp "${in}" out || test_failed $LINENO "$i"
"$i" --appe -1 -.5 -a file1 -b 5 --blo 10 -c -carg --casua --casua=arg \
-e "" -e arg -3.14 --emp "" --emp arg -inf -H -o file file2 \
--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 ||
test_failed $LINENO "$i"
cmp "${in}" out || test_failed $LINENO "$i"
"$i" --app -a -b5 -1 --bl=10 -c -.5 -carg --cas= --cas=arg -e "" \
file1 -e arg --em "" -3.14 --em arg -H -ofile -inf --long-only -v \
--verb file2 -Inf -INF > out || test_failed $LINENO "$i"
cmp "${testdir}"/test4.txt out || test_failed $LINENO "$i"
done
"${PARSER}" --int-min --int-max > out || test_failed $LINENO
@ -64,36 +67,42 @@ cmp out cout || test_failed $LINENO
printf "\ntesting bad input..."
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"
"$i" --ve 2> /dev/null
"$i" --ve 2>> cout
[ $? = 1 ] || test_failed $LINENO "$i"
"$i" --ver 2> /dev/null
"$i" --ver 2>> cout
[ $? = 1 ] || test_failed $LINENO "$i"
"$i" --unrecognized 2> /dev/null # unrecognized option
"$i" --unrecognized 2>> cout # unrecognized option
[ $? = 1 ] || test_failed $LINENO "$i"
"$i" -x 2> /dev/null # invalid option
"$i" -x 2>> cout # invalid option
[ $? = 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"
"$i" -b 2> /dev/null # argument required
"$i" -b 2>> cout # argument required
[ $? = 1 ] || test_failed $LINENO "$i"
"$i" --block 2> /dev/null
"$i" --block 2>> cout
[ $? = 1 ] || test_failed $LINENO "$i"
"$i" --block= 2> /dev/null
"$i" --block= 2>> cout
[ $? = 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"
done
sed -e 's/carg_parser/arg_parser/' cout > out2 || framework_failure
diff -u out out2 || test_failed $LINENO
echo
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
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/semaphore.lua $(DESTDIR)$(LUA_DIR)/copas/semaphore.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:
cd ./tests/certs && \
@ -43,13 +44,16 @@ test: certs
$(LUA) $(DELIM) $(PKGPATH) tests/close.lua
$(LUA) $(DELIM) $(PKGPATH) tests/connecttwice.lua
$(LUA) $(DELIM) $(PKGPATH) tests/errhandlers.lua
$(LUA) $(DELIM) $(PKGPATH) tests/future.lua
$(LUA) $(DELIM) $(PKGPATH) tests/exit.lua
$(LUA) $(DELIM) $(PKGPATH) tests/exittest.lua
$(LUA) $(DELIM) $(PKGPATH) tests/http-timeout.lua
$(LUA) $(DELIM) $(PKGPATH) tests/httpredirect.lua
$(LUA) $(DELIM) $(PKGPATH) tests/httpvalidate.lua
$(LUA) $(DELIM) $(PKGPATH) tests/largetransfer.lua
$(LUA) $(DELIM) $(PKGPATH) tests/lock.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/queue.lua
$(LUA) $(DELIM) $(PKGPATH) tests/removeserver.lua
@ -61,8 +65,10 @@ test: certs
$(LUA) $(DELIM) $(PKGPATH) tests/tcptimeout.lua
$(LUA) $(DELIM) $(PKGPATH) tests/timer.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/udptimeout.lua
$(LUA) $(DELIM) $(PKGPATH) tests/wakeup.lua
$(LUA) $(DELIM)
coverage:

View file

@ -14,3 +14,4 @@ install:
copy src\copas\semaphore.lua "$(LUA_DIR)\copas\semaphore.lua"
copy src\copas\smtp.lua "$(LUA_DIR)\copas\smtp.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)
[![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 version in `copas.lua`
- update version at the top of this README,
- update copyright years if needed
- update rockspec
- commit as `release X.Y.Z`
- update copyright years if needed (in `license.html` and `copas.lua`)
- update cvs rockspec, and create a new rockspec in ./rockspec
- 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`
- push commit and tag
- upload to luarocks

View file

@ -27,8 +27,8 @@ description = {
homepage = "https://github.com/"..github_account_name.."/"..github_repo_name,
}
dependencies = {
"lua >= 5.1, < 5.5",
"luasocket >= 2.1, <= 3.0rc1-2",
"lua >= 5.1, < 5.6",
"luasocket ~> 3",
"coxpcall >= 1.14",
"binaryheap >= 0.4",
"timerwheel ~> 1",
@ -48,6 +48,7 @@ build = {
["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 = {

View file

@ -57,7 +57,7 @@ and client support for http(s), ftp and smtp requests.
<p>
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
support.
support. For concurrent programming Copas provides timers, locks, semaphores, queues, and futures.
</p>
<p>
@ -69,12 +69,12 @@ For a full implementation of a Copas HTTP server you can refer to
</p>
<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>
<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>
@ -93,17 +93,113 @@ luarocks install copas
<h2><a name="dependencies"></a>Dependencies</h2>
<p>Copas depends on
LuaSocket, <a href="http://keplerproject.github.io/coxpcall/">Coxpcall</a> (only when using Lua 5.1), and (optionally) LuaSec.
</p>
<p>Copas depends on LuaSocket (or LuaSystem), <a href="http://keplerproject.github.io/coxpcall/">Coxpcall</a>
(only when using Lua 5.1), and (optionally) LuaSec.</p>
<h2><a name="history"></a>History</h2>
<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>
<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>
<li>Fix: <code>queue:finish</code> will return after the last item has been handled, not just popped
(if using workers).</li>
</ul></dd>
<dt><strong>Copas 4.7.0</strong> [15/Jan/2023]</dt>

View file

@ -64,7 +64,7 @@ licensed software.</p>
</p>
<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>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation

View file

@ -81,8 +81,12 @@ local function connection_handler(skt)
end
copas.addthread(function()
copas.addserver(server_socket, copas.handler(connection_handler,
ssl_params), "my_TCP_server")
copas.waitforexit()
copas.removeserver(server_socket)
end)
copas()
</pre>
@ -180,6 +184,29 @@ are used to register servers and to execute the main loop of Copas:</p>
truthy.</p>
</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>
<dd>
<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>
</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>
<dd>
<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>
</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>
<dd>
<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>
<dl class="reference">
<dt><strong><code>copas.pause([delay])</code></strong></dt>
<dt><strong><code>number = copas.pause([delay])</code></strong></dt>
<dd>
<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>
<p>Returns the time slept (in seconds).</p>
</dd>
<dt><strong><code>copas.pauseforever()</code></strong></dt>
<dt><strong><code>number = copas.pauseforever()</code></strong></dt>
<dd>
<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>
<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>
</dd>
<dt><strong><code>copas.wakeup(co)</code></strong></dt>
<dt><strong><code>ok, err = copas.wakeup(co)</code></strong></dt>
<dd>
<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>
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>
<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
outgoing connections), which then ensures that the connection will automatically be
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>
<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>).
Please see <code>sock:receivepartial</code> for differences with LuaSocket, especially
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>
<dt><strong><code>sock:receivefrom([size])</code></strong></dt>
@ -467,6 +537,58 @@ exchange data with other services.</p>
secured when started.</p>
</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>
<dd>
<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
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
(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>
<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
release all waiting threads and leave the semaphore with 0 resources. 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>
release all waiting threads and leave the semaphore with 0 resources (provided they are
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>
<dt><strong><code>semaphore:give([given])</code></strong></dt>
<dd>
<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
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>
<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><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>
<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>
</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>
<dd>
<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
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>
<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>
</dl>
@ -800,6 +942,11 @@ servers.</p>
<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 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>
<dt><strong><code>copas.receivepartial(skt [, pattern [, prefix]])</code></strong></dt>
@ -961,6 +1108,39 @@ print("result: ", result)
<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>
<dd>
<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,
-- 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 $
-------------------------------------------------------------------------------
@ -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")
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 gettime = socket.gettime
local gettime = (socket or system).gettime
local block_sleep = (socket or system).sleep
local ssl -- only loaded upon demand
local core_timer_thread
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 fnil = function() end
@ -52,7 +70,7 @@ if _VERSION=="Lua 5.1" and not jit then -- obsolete: only for Lua 5.1 compat
end
do
if socket then
-- Redefines LuaSocket functions with coroutine safe versions (pure Lua)
-- (this allows the use of socket.http from within copas)
local err_mt = {
@ -94,7 +112,7 @@ end
-- Setup the Copas meta table to auto-load submodules and define a default method
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
submodules[key] = true
submodules[i] = nil
@ -116,9 +134,9 @@ end
-- 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._VERSION = "Copas 4.7.0"
copas._VERSION = "Copas 4.12.0"
-- Close the socket associated with the current connection after the handler finishes
copas.autoclose = true
@ -126,6 +144,8 @@ copas.autoclose = true
-- indicator for the loop running
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
@ -143,7 +163,9 @@ local object_names = setmetatable({}, {
-------------------------------------------------------------------------------
-- 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()
@ -180,26 +202,42 @@ local function newsocketset()
end
do -- queues implementation
local fifo_queues = setmetatable({},{
__mode = "k", -- auto collect queue if socket is gone
__index = function(self, skt) -- auto create fifo queue if not found
local newfifo = {}
self[skt] = newfifo
return newfifo
end,
})
do -- single-waiter implementation
-- the set instance (read or write) determines what operation the coroutine is waiting for
local waiters = setmetatable({}, { __mode = "k" }) -- coroutine by socket
-- pushes an item in the fifo queue for the socket.
function set:push(skt, itm)
local queue = fifo_queues[skt]
queue[#queue + 1] = itm
-- Registers the coroutine as the socket's waiter, to be resumed
-- once the socket becomes ready.
-- @return true on success, or nil + error message if another coroutine
-- 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
-- pops an item from the fifo queue for the socket
function set:pop(skt)
local queue = fifo_queues[skt]
return table.remove(queue, 1)
-- Clears and returns the coroutine waiting on the socket read/write operation, or nil if
-- none is waiting.
function set:release(skt)
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
@ -281,11 +319,21 @@ local _sleeping = {} do
if lethargy[co] then
lethargy[co] = nil
_resumable:push(co)
return
return true
end
if heap:remove(co) then
_resumable:push(co)
return true
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
function _sleeping:cancel(co)
@ -293,6 +341,12 @@ local _sleeping = {} do
heap:remove(co)
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
function _sleeping:done(tos)
-- 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
-- The callback called when a socket timeout occurs.
local function socket_callback(co)
local skt = socket_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
_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
_reading:release(skt)
_reading:remove(skt)
elseif queue == "write" then
_writing:release(skt)
_writing:remove(skt)
else
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.
-- 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
-- @param use_connect_to (bool) timeout to use is determined based on queue (read/write) or if this
-- is truthy, it is the connect timeout.
-- @param skt (socket) the socket on which to operate, use 'nil' to cancel the current timeout
-- @param queue (string) the queue the socket is currently in: "read" or "write"
-- @param use_connect_to (bool) if truthy, use the connect timeout instead of the
-- 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
function sto_timeout(skt, queue, use_connect_to)
local co = coroutine_running()
@ -438,6 +497,14 @@ local sto_timeout, sto_timed_out, sto_change_queue, sto_error do
function sto_error(err)
return useSocketTimeoutErrors[coroutine_running()] and err or "timeout"
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
@ -446,6 +513,21 @@ end
-- 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
-- for tcp based, and falsy for udp based.
local isTCP do
@ -459,13 +541,22 @@ local isTCP do
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, ...)
_closed[#_closed+1] = skt
return skt:close(...)
end
-- nil or negative is indefinitly
function copas.settimeout(skt, timeout)
timeout = timeout or -1
@ -476,6 +567,7 @@ function copas.settimeout(skt, timeout)
return copas.settimeouts(skt, timeout, timeout, timeout)
end
-- negative is indefinitly, nil means do not change
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
-- be provided as the 'pattern' below defaults to a string. Will throw a
-- '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)
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
pattern = pattern or "*l"
local current_log = _reading_log
@ -545,19 +648,28 @@ function copas.receive(client, pattern, part)
elseif sto_timed_out() then
current_log[client] = nil
sto_timeout()
return nil, sto_error(err), part
end
local queue, direction
if err == "wantwrite" then -- wantwrite may be returned during SSL renegotiations
queue = _writing
direction = "write"
current_log = _writing_log
current_log[client] = gettime()
sto_change_queue("write")
coroutine_yield(client, _writing)
else
queue = _reading
direction = "read"
current_log = _reading_log
end
current_log[client] = gettime()
sto_change_queue("read")
coroutine_yield(client, _reading)
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
until false
end
@ -589,17 +701,28 @@ function copas.receivefrom(client, size)
elseif sto_timed_out() then
_reading_log[client] = nil
sto_timeout()
return nil, sto_error(err), port
end
_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
end
-- same as above but with special treatment when reading chunks,
-- unblocks on any data received.
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
pattern = pattern or "*l"
local orig_size = #(part or "")
@ -626,19 +749,28 @@ function copas.receivepartial(client, pattern, part)
elseif sto_timed_out() then
current_log[client] = nil
sto_timeout()
return nil, sto_error(err), part
end
local queue, direction
if err == "wantwrite" then
queue = _writing
direction = "write"
current_log = _writing_log
current_log[client] = gettime()
sto_change_queue("write")
coroutine_yield(client, _writing)
else
queue = _reading
direction = "read"
current_log = _reading_log
end
current_log[client] = gettime()
sto_change_queue("read")
coroutine_yield(client, _reading)
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
until false
end
@ -674,19 +806,28 @@ function copas.send(client, data, from, to)
elseif sto_timed_out() then
current_log[client] = nil
sto_timeout()
return nil, sto_error(err), lastIndex
end
local queue, direction
if err == "wantread" then
queue = _reading
direction = "read"
current_log = _reading_log
current_log[client] = gettime()
sto_change_queue("read")
coroutine_yield(client, _reading)
else
queue = _writing
direction = "write"
current_log = _writing_log
end
current_log[client] = gettime()
sto_change_queue("write")
coroutine_yield(client, _writing)
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
until false
end
@ -722,12 +863,18 @@ function copas.connect(skt, host, port)
elseif sto_timed_out() then
_writing_log[skt] = nil
sto_timeout()
return nil, sto_error(err)
end
tried_more_than_once = tried_more_than_once or true
_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
end
@ -789,15 +936,19 @@ local function normalize_sslt(sslt)
r.sni = false
elseif t == "table" then
if sslt.mode or sslt.protocol then
-- has the mandatory fields for the ssl-params table for handshake
-- backward compatibility
r.wrap = sslt
r.sni = false
else
-- has the target definition, copy our known keys
if sslt.wrap or sslt.sni then
-- has the target definition (current format), copy our known keys
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
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
elseif t == "userdata" then
@ -846,7 +997,8 @@ function copas.dohandshake(skt, wrap_params)
error("TLS/SSL handshake failed: " .. tostring(err))
elseif sto_timed_out() then
return nil, sto_error(err)
sto_timeout()
error("TLS/SSL handshake timeout: " .. tostring(err))
elseif err == "wantwrite" then
sto_change_queue("write")
@ -860,7 +1012,11 @@ function copas.dohandshake(skt, wrap_params)
error("TLS/SSL handshake failed: " .. tostring(err))
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
end
@ -906,6 +1062,8 @@ local _skt_mt_tcp = {
-- if ssl parameters are available, it will also include a handshake
connect = function(self, ...)
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 self.ssl_params.sni then self:sni() end
if self.ssl_params.wrap then res, err = self:dohandshake() end
@ -975,13 +1133,13 @@ local _skt_mt_tcp = {
names = sslp.sni.names
strict = sslp.sni.strict
end
-- sni() throws on bad parameters by itself, and returns nothing on success
return self.socket:sni(names, strict)
end,
dohandshake = function(self, wrap_params)
local nskt, err = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap)
if not nskt then return nskt, err end
self.socket = nskt -- replace internal socket with the newly wrapped ssl one
-- copas.dohandshake() either returns the wrapped ssl socket, or throws
self.socket = copas.dohandshake(self.socket, wrap_params or self.ssl_params.wrap)
return self
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.
-- @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
function copas.wrap (skt, sslt)
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, ...)
skt = copas.wrap(skt, sslparams) -- this call will normalize the sslparams table
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.wrap then skt:dohandshake(sslp.wrap) end
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)
-- end
if new_q == _reading or new_q == _writing or new_q == _sleeping then
-- we're yielding to a new queue
if new_q == _reading or new_q == _writing then
-- 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:push (res, co)
return
@ -1326,23 +1493,38 @@ end
-- yields the current coroutine and wakes it after 'sleeptime' seconds.
-- if sleeptime < 0 then it sleeps 0 seconds.
function copas.pause(sleeptime)
local s = gettime()
if sleeptime and sleeptime > 0 then
coroutine_yield(sleeptime, _sleeping)
else
coroutine_yield(0, _sleeping)
end
return gettime() - s
end
-- yields the current coroutine until explicitly woken up using 'wakeup'
function copas.pauseforever()
local s = gettime()
coroutine_yield(-1, _sleeping)
return gettime() - s
end
-- 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)
_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
@ -1353,16 +1535,16 @@ end
do
local timeout_register = setmetatable({}, { __mode = "k" })
local time_out_thread
local timerwheel = require("timerwheel").new({
now = gettime,
precision = TIMEOUT_PRECISION,
ringsize = math.floor(60*60*24/TIMEOUT_PRECISION), -- ring size 1 day
err_handler = function(err)
return _deferror(err, time_out_thread)
return _deferror(err, core_timer_thread)
end,
})
time_out_thread = copas.addnamedthread("copas_core_timer", function()
core_timer_thread = copas.addnamedthread("copas_core_timer", function()
while true do
copas.pause(TIMEOUT_PRECISION)
timerwheel:step()
@ -1416,13 +1598,15 @@ end
-- a task to check ready to read events
local _readable_task = {} do
_readable_task._events = {}
local function tick(skt)
local handler = _servers[skt]
if handler then
_accept(skt, handler)
else
_reading:remove(skt)
_doTick(_reading:pop(skt), skt)
_doTick(_reading:release(skt), skt)
end
end
@ -1439,9 +1623,11 @@ end
-- a task to check ready to write events
local _writable_task = {} do
_writable_task._events = {}
local function tick(skt)
_writing:remove(skt)
_doTick(_writing:pop(skt), skt)
_doTick(_writing:release(skt), skt)
end
function _writable_task:step()
@ -1502,6 +1688,11 @@ local _select_plain do
local last_cleansing = 0
local duration = function(t2, t1) return t2-t1 end
if not socket then
-- socket module unavailable, switch to luasystem sleep
_select_plain = block_sleep
else
-- use socket.select to handle socket-io
_select_plain = function(timeout)
local err
local now = gettime()
@ -1558,6 +1749,7 @@ local _select_plain do
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())
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_instrumented, _getstats_plain
@ -1730,8 +1993,17 @@ function copas.loop(initializer, timeout)
timeout = initializer or timeout
end
resetexit()
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
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.SSLOPTIONS = "all"
_M.SSLVERIFY = "none"
_M.SSLSNISTRICT = false
-----------------------------------------------------------------------------
@ -205,13 +204,13 @@ end
-----------------------------------------------------------------------------
local function adjusturi(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 not reqt.proxy and not _M.PROXY then
u = {
path = socket.try(reqt.path, "invalid path 'nil'"),
params = reqt.params,
query = reqt.query,
fragment = reqt.fragment
}
end
return url.build(u)
@ -261,6 +260,11 @@ local function adjustrequest(reqt)
local nreqt = reqt.url and url.parse(reqt.url, default) or {}
-- explicit components override url
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
socket.try(nreqt.host and nreqt.host ~= "",
"invalid host '" .. base.tostring(nreqt.host) .. "'")
@ -350,6 +354,16 @@ end
return 1, code, headers, status
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
-- include the optional SSL/TLS connection, and unsafe redirect checks
function _M.getcreatefunc(params)
@ -361,9 +375,7 @@ function _M.getcreatefunc(params)
options = params.options,
verify = params.verify,
}
ssl_params.sni = ssl_params.sni or {
strict = _M.SSLSNISTRICT
}
ssl_params.sni = ssl_params.sni or {}
-- Default settings
ssl_params.wrap.protocol = ssl_params.wrap.protocol or _M.SSLPROTOCOL
@ -373,8 +385,9 @@ function _M.getcreatefunc(params)
end
ssl_params.wrap.mode = "client" -- Force client mode
if not ssl_params.sni.names then
-- names haven't been set, and hence will be set below. Since this alters
local sni_name = ssl_params.sni.names
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
-- reused if a redirect is encountered.
local old_params = ssl_params
@ -382,18 +395,34 @@ function _M.getcreatefunc(params)
for k,v in pairs(old_params) do
ssl_params[k] = v
end
ssl_params.sni = { strict = old_params.sni.strict }
ssl_params.sni = {}
end
-- upvalue to track https -> http redirection
local washttps = false
local first_request = true -- on follow up redirects we must clear sni-name
-- 'create' function for LuaSocket
return function (reqt)
local u = url.parse(reqt.url)
if (reqt.scheme or u.scheme) == "https" then
-- set SNI name to host if not given
ssl_params.sni.names = ssl_params.sni.names or u.host
if type(ssl_params.sni) ~= "table" then
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
local conn = copas.wrap(socket.tcp(), ssl_params)
-- insert https default port, overriding http port inserted by LuaSocket

View file

@ -1,5 +1,10 @@
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
@ -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).
-- @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)
local co = coroutine.running()
local co = coroutine_running()
local start_time
-- is the lock already taken?
@ -115,13 +120,14 @@ function lock:get(timeout)
return 0
end
self.queue[self.q_tail] = co
self.q_tail = self.q_tail + 1
timeout = timeout or self.timeout
if timeout == 0 then
return nil, "timeout", 0
end
self.queue[self.q_tail] = co
self.q_tail = self.q_tail + 1
-- set up timeout
registry[co] = self
copas.timeout(timeout, timeout_handler)
@ -154,7 +160,7 @@ end
-- an error.
-- returns true, or nil+err on an error
function lock:release()
local co = coroutine.running()
local co = coroutine_running()
if co ~= self.owner then
return nil, "cannot release a lock not owned"
@ -167,17 +173,18 @@ function lock:release()
end
-- 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
local next_up = self.queue[self.q_tip]
if next_up then
self.owner = next_up
self.queue[self.q_tip] = nil
self.q_tip = self.q_tip + 1
copas.wakeup(next_up)
if next_up and copas.wakeup(next_up) then
self.owner = next_up
return true
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
-- queue is empty, reset pointers
self.owner = nil

View file

@ -1,4 +1,5 @@
local copas = require "copas"
local gettime = copas.gettime
local Sema = copas.semaphore
local Lock = copas.lock
@ -30,6 +31,7 @@ function Queue.new(opts)
self.workers = setmetatable({}, { __mode = "k" })
self.stopping = false
self.worker_id = 0
self.exit_semaphore = Sema.new(10^9)
return self
end
@ -105,6 +107,8 @@ end
-- destroyed on a timeout.
function Queue:finish(timeout, no_destroy_on_timeout)
self:stop()
timeout = timeout or self.lock.timeout
local endtime = gettime() + timeout
local _, err = self.lock:get(timeout)
-- the lock never gets released, only destroyed, so we have to check the error string
if err == "timeout" then
@ -113,9 +117,33 @@ function Queue:finish(timeout, no_destroy_on_timeout)
end
return nil, err
end
-- 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
do
local destroyed_func = function()
@ -170,6 +198,9 @@ function Queue:add_worker(worker)
worker(item) -- TODO: wrap in errorhandling
end
self.workers[coro] = nil
if self.exit_semaphore then
self.exit_semaphore:give(1)
end
end)
self.workers[coro] = true

View file

@ -1,5 +1,10 @@
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 semaphore = {}
@ -51,7 +56,7 @@ do
-- destroy a semaphore.
-- Releases all waiting threads with `nil+"destroyed"`
function semaphore:destroy()
self:give(math.huge)
self:release_all()
self.destroyed = true
setmetatable(self, destroyed_semaphore_mt)
return true
@ -60,12 +65,16 @@ end
-- Gives resources.
-- @param given (optional, default 1) number of resources to return. If more
-- than the maximum are returned then it will be capped at the maximum and
-- error "too many" will be returned.
-- @param given (optional, default 1) number of resources to return. Must be
-- a finite number greater than or equal to 0. If more than the maximum are
-- returned then it will be capped at the maximum and error "too many" will
-- be returned.
function semaphore:give(given)
local err
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
--print("now at",count, ", after +"..given)
if count > self.max then
@ -78,8 +87,17 @@ function semaphore:give(given)
local nxt = self.queue[i] -- there can be holes, so nxt might be nil
if not nxt then
self.q_tip = i + 1
else
if count >= nxt.requested then
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
@ -91,7 +109,6 @@ function semaphore:give(given)
break -- we ran out of resources
end
end
end
if self.q_tip == self.q_tail then -- reset queue
self.queue = {}
@ -131,13 +148,17 @@ end
-- Requests resources from the semaphore.
-- 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
-- seconds. If 0 it will either succeed or return immediately with error "timeout".
-- If `math.huge` it will wait forever.
-- @return true, or nil+"destroyed"
function semaphore:take(requested, timeout)
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
-- nobody is waiting before us, and there is enough in store
self.count = self.count - requested
@ -154,7 +175,7 @@ function semaphore:take(requested, timeout)
end
-- get in line
local co = coroutine.running()
local co = coroutine_running()
self.to_flags[co] = nil
registry[co] = self
copas.timeout(to, timeout_handler)
@ -193,10 +214,30 @@ end
function semaphore:get_wait()
local wait = 0
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
return wait - self.count
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

View file

@ -25,11 +25,20 @@ end
do
local function expire_func(self, initial_delay)
local my_co = coroutine_running()
if self.errorhandler then
copas.seterrorhandler(self.errorhandler)
end
copas.pause(initial_delay)
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.recurring then
-- non-recurring timer
@ -45,6 +54,11 @@ do
end
end
if self.co ~= my_co then
-- replaced while the callback was running, see above
return
end
if self.cancelled then
-- clean up and exit the thread
self.co = nil
@ -75,6 +89,10 @@ end
--- 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
function timer:cancel()
if not self.co then
@ -86,8 +104,7 @@ function timer:cancel()
end
self.cancelled = true
copas.wakeup(self.co) -- resume asap
copas.removethread(self.co) -- will immediately drop the thread upon resuming
copas.wakeup(self.co) -- in case it's idle between recurrences, exit immediately
self.co = nil
return self
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`.
-- @return timer object, or throws an error on bad input
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")
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
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

View file

@ -1,9 +1,9 @@
#!/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
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 \
-out clientAcert.pem -days 365

View file

@ -1,8 +1,8 @@
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

View file

@ -1,9 +1,9 @@
#!/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
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 \
-out clientBcert.pem -days 365

View file

@ -1,7 +1,7 @@
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

View file

@ -1,7 +1,6 @@
#!/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 -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

View file

@ -1,7 +1,7 @@
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

View file

@ -1,7 +1,7 @@
#!/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

View file

@ -1,8 +1,8 @@
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

View file

@ -1,9 +1,9 @@
#!/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
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 \
-out serverAcert.pem -days 365

View file

@ -1,8 +1,8 @@
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

View file

@ -1,9 +1,9 @@
#!/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
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 \
-out serverBcert.pem -days 365

View file

@ -36,7 +36,7 @@ copas.loop(function()
-- wait in the read/write queues
copas.pause(2)
-- 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")
client_socket:close()
@ -55,7 +55,7 @@ copas.loop(function()
copas.addthread(function()
local data, err = client_socket:receive(1)
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)
check_exit()
end)
@ -66,7 +66,7 @@ copas.loop(function()
ok, err = client_socket:send(("hello world"):rep(100))
end
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)
check_exit()
end)

View file

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

View file

@ -72,3 +72,46 @@ copas.loop()
assert(testran == 6, "Test 6 was not executed!")
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 s1 = socket.bind('*', 49500)
local s1 = socket.bind('localhost', 49500)
copas.addserver(s1, copas.handler(function(skt)
-- HTTP server that will optionally do a timeout on the request, or on the response
copas.setsocketname("Server 49500", skt)

View file

@ -35,13 +35,13 @@ local done = false
copas.addthread(function()
local _, code, headers = doreq("https://goo.gl/UBCUc5") -- https --> https redirect
assert(tonumber(code)==200)
assert(headers.location == "https://github.com/brunoos/luasec")
assert(tonumber(code)==200, "unexpected status code: "..tostring(code))
assert(headers.location == "https://github.com/lunarmodules/luasec", "unexpected location header: "..tostring(headers.location))
print("https -> https redirect OK!")
copas.addthread(function()
local _, code, headers = doreq("http://goo.gl/UBCUc5") -- http --> https redirect
assert(tonumber(code)==200)
assert(headers.location == "https://github.com/brunoos/luasec")
assert(tonumber(code)==200, "unexpected status code: "..tostring(code))
assert(headers.location == "https://github.com/lunarmodules/luasec", "unexpected location header: "..tostring(headers.location))
print("http -> https redirect OK!")
copas.addthread(function()
--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
-- redirect is a downgrade which then errors out
-- 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)
copas.addserver(server, function(skt)
skt = copas.wrap(skt)
@ -63,7 +63,7 @@ copas.addthread(function()
-- execute test request
local _, code, headers = doreq("http://localhost:9876/") -- http --> http redirect
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")
print("http -> http redirect OK!")
copas.addthread(function()
@ -73,7 +73,7 @@ copas.addthread(function()
copas.addthread(function()
redirect = "all"
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/")
print("https -> http redirect, while allowed OK!")
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()
local body = ("A"):rep(1024*1024*50) -- 50 mb string
local start = socket.gettime()
local start = copas.gettime()
local done = 0
local sparams, cparams
local function runtest()
local s1 = socket.bind('*', 49500)
local s1 = socket.bind('localhost', 49500)
copas.addserver(s1, copas.handler(function(skt)
copas.setsocketname("Server 49500", skt)
copas.setthreadname("Server 49500")
@ -25,12 +25,12 @@ local function runtest()
local res, err, part = skt:receive('*a')
res = res or part
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)
done = done + 1
end, sparams))
local s2 = socket.bind('*', 49501)
local s2 = socket.bind('localhost', 49501)
copas.addserver(s2, copas.handler(function(skt)
skt:settimeout(0) -- set, uses the `receivepartial` method
copas.setsocketname("Server 49501", skt)
@ -38,7 +38,7 @@ local function runtest()
local res, err, part = skt:receive('*a')
res = res or part
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)
done = done + 1
end, sparams))
@ -52,7 +52,7 @@ local function runtest()
repeat
last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1)
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
skt = nil -- luacheck: ignore
done = done + 1
@ -67,7 +67,7 @@ local function runtest()
repeat
last_byte_sent, err = skt:send(body, last_byte_sent or 1, -1)
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
skt = nil -- luacheck: ignore
done = done + 1
@ -77,7 +77,7 @@ local function runtest()
local i = 1
while done ~= 4 do
copas.pause(1)
print(i, "seconds:", socket.gettime()-start)
print(i, "seconds:", copas.gettime()-start)
i = i + 1
if i > 60 then
print"timeout"
@ -97,22 +97,22 @@ runtest() -- run test using regular connection (s/cparams == nil)
-- set ssl parameters and do it again
sparams = {
mode = "server",
protocol = "tlsv1",
protocol = "any",
key = "tests/certs/serverAkey.pem",
certificate = "tests/certs/serverA.pem",
cafile = "tests/certs/rootA.pem",
verify = {"peer", "fail_if_no_peer_cert"},
options = {"all", "no_sslv2"},
options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"},
}
cparams = {
mode = "client",
protocol = "tlsv1",
protocol = "any",
key = "tests/certs/clientAkey.pem",
certificate = "tests/certs/clientA.pem",
cafile = "tests/certs/rootA.pem",
verify = {"peer", "fail_if_no_peer_cert"},
options = {"all", "no_sslv2"},
options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"},
}
done = 0
start = socket.gettime()
start = copas.gettime()
runtest()

View file

@ -5,7 +5,7 @@ package.path = string.format("../src/?.lua;%s", package.path)
local copas = require "copas"
local Lock = copas.lock
local gettime = require("socket").gettime
local gettime = copas.gettime
local test_complete = false
copas.loop(function()
@ -104,4 +104,106 @@ copas.loop(function()
end)
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!")

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
package.path = string.format("../src/?.lua;%s", package.path)
local now = require("socket").gettime
local copas = require "copas"
local now = copas.gettime
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
q:stop() -- this should exit the idle workers and exit the copas loop
end)
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 socket = require("socket")
local wskt = socket.bind("*", 0)
local wskt = socket.bind("localhost", 0)
local whost, wport = wskt:getsockname()
wport = tonumber(wport)
local function wait_for_trigger()
copas.addserver(wskt, function(cskt)
-- 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)
end
local function wait_for_trigger()
copas.addserver(wskt, copas.handler(connection_handler), "my_TCP_server")
end
local function trigger_it(n)
local cskt = socket.tcp()
local cskt = copas.wrap(socket.tcp())
local ok = cskt:connect(whost, wport)
if ok then
cskt:send("hi "..n)
@ -35,6 +47,7 @@ copas.addthread(function()
trigger_it(i)
copas.pause(0.1)
end
timeout_timer:cancel()
end)
copas.loop()

View file

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

View file

@ -1,9 +1,9 @@
-- make sure we are pointing to the local copas first
package.path = string.format("../src/?.lua;%s", package.path)
local now = require("socket").gettime
local copas = require "copas"
local now = copas.gettime
local semaphore = copas.semaphore
@ -143,4 +143,252 @@ copas.loop(function()
test_complete = true
end)
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!")

View file

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

View file

@ -14,6 +14,7 @@ end
print("Testing platform: " .. platform)
_G._TEST = true -- mark as test, to export some internals for testing
local copas = require("copas")
local socket = require("socket")
@ -157,6 +158,189 @@ function tests.receive_timeout()
copas.loop()
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"
for name, test in pairs(tests) do
print("testing: "..tostring(name))

View file

@ -4,7 +4,8 @@ package.path = string.format("../src/?.lua;%s", package.path)
local copas = require "copas"
local gettime = require("socket").gettime
local socket = require "socket"
local gettime = copas.gettime
local timer = copas.timer
local successes = 0
@ -121,7 +122,115 @@ copas.loop(function()
})
-- 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)
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!")

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 = {
wrap = {
mode = "server",
protocol = "tlsv1",
protocol = "any",
key = "tests/certs/serverAkey.pem",
certificate = "tests/certs/serverA.pem",
cafile = "tests/certs/rootA.pem",
verify = {"peer", "fail_if_no_peer_cert"},
options = {"all", "no_sslv2"},
options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"},
},
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 = {}
}
}
@ -33,12 +34,12 @@ server_params.sni.names["myhost.com"] = ssl.newcontext(server_params.wrap)
local client_params = {
wrap = {
mode = "client",
protocol = "tlsv1",
protocol = "any",
key = "tests/certs/clientAkey.pem",
certificate = "tests/certs/clientA.pem",
cafile = "tests/certs/rootA.pem",
verify = {"peer", "fail_if_no_peer_cert"},
options = {"all", "no_sslv2"},
options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"},
},
sni = {
names = "" -- will be added in test below
@ -63,11 +64,11 @@ local function echoHandler(skt)
return -- close this client connection, after stopping the server
end
skt:send(data)
skt:send(data.."\n")
end
end
server = assert(socket.bind("*", port))
server = assert(socket.bind("localhost", port))
copas.addserver(server, copas.handler(echoHandler, server_params))
copas.addthread(function()
@ -92,12 +93,16 @@ copas.addthread(function()
local skt = copas.wrap(socket.tcp(), client_params)
local success, ok = pcall(skt.connect, skt, "localhost", port)
if not (success and ok) then
print "expected connection to be completed"
print("expected connection to be completed", success, ok)
os.exit(1)
end
assert(skt:send("hello world\n"))
assert(skt:receive() == "hello world")
print "succesfully completed test"
os.exit(0)
-- send exit signal to server
skt:send("exit\n")
end)
-- 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>
<sw@jkqxz.net> <mrt@jkqxz.net>
<u@pkh.me> <cboesch@gopro.com>
<zhilizhao@tencent.com> <quinkblack@foxmail.com>
<zhilizhao@tencent.com> <wantlamy@gmail.com>
<quinkblack@foxmail.com> <wantlamy@gmail.com>
<quinkblack@foxmail.com> <zhilizhao@tencent.com>
<modmaker@google.com> <modmaker-at-google.com@ffmpeg.org>
<stebbins@jetheaddev.com> <jstebbins@jetheaddev.com>
<barryjzhao@tencent.com> <mypopydev@gmail.com>
@ -17,8 +17,14 @@
<atomnuker@gmail.com> <rpehlivanov@obe.tv>
<lizhong1008@gmail.com> <zhong.li@intel.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>
<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>
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
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.

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
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
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
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
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,
and you may at your option offer warranty protection in exchange for a
fee.
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
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
that version instead if you wish.) Do not make any other change in
these notices.
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
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.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
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
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
use both them and the Library together in an executable that you
distribute.
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
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.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
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
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
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.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
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,
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
- Playdate video decoder and demuxer
- Extend VAAPI support for libva-win32 on Windows
@ -35,7 +303,11 @@ version <next>:
- CRI USM demuxer
- ffmpeg CLI '-top' option deprecated in favor of the setfield filter
- 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:
- 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
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.
NOTICE
------
## NOTICE
- 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:
- libpostproc
- optional x86 optimization in the files
- `libavcodec/x86/flac_dsp_gpl.asm`
- `libavcodec/x86/idct_mmx.c`
@ -45,7 +44,6 @@ Specifically, the GPL parts of FFmpeg are:
- `vf_owdenoise.c`
- `vf_perspective.c`
- `vf_phase.c`
- `vf_pp.c`
- `vf_pp7.c`
- `vf_pullup.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!
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
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
============
@ -18,10 +34,10 @@ ffmpeg:
ffmpeg.c Michael Niedermayer, Anton Khirnov
ffplay:
ffplay.c Marton Balint
ffplay.c [2] Marton Balint
ffprobe:
ffprobe.c Stefano Sabatini
ffprobe.c [2] Stefano Sabatini
Commandline utility code:
cmdutils.c, cmdutils.h Michael Niedermayer
@ -29,31 +45,33 @@ Commandline utility code:
QuickTime faststart:
tools/qt-faststart.c Baptiste Coudurier
Execution Graph Printing
fftools/graph, fftools/resources [2] softworkz
Miscellaneous Areas
===================
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 emergencies Árpád Gereöffy, Reimar Doeffinger, Alexander Strasser, Nikolay Aleksandrov
presets Robert Swain
project server day to day operations (L: root@ffmpeg.org) Michael Niedermayer, Reimar Doeffinger, Alexander Strasser, Nikolay Aleksandrov, Timo Rothenpieler
project server emergencies (L: root@ffmpeg.org) Reimar Doeffinger, Alexander Strasser, Nikolay Aleksandrov, Timo Rothenpieler
presets [0]
metadata subsystem Aurelien Jacobs
release management Michael Niedermayer
API tests Ludmila Glinskih
API tests [0]
samples-request [2] Thilo Borgmann, James Almer, Ben Littler
Communication
=============
website Deby Barbara Lepage
fate.ffmpeg.org Timothy Gu
Trac bug tracker Alexander Strasser, Michael Niedermayer, Carl Eugen Hoyos
Patchwork Andriy Gelman
mailing lists Baptiste Coudurier
website (T: https://git.ffmpeg.org/ffmpeg-web) 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
Trac bug tracker [X] (W: https://trac.ffmpeg.org) Alexander Strasser, Michael Niedermayer, Carl Eugen Hoyos
Forgejo issue tracker (W: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues)
Patchwork [2] (W: https://patchwork.ffmpeg.org) Andriy Gelman
mailing lists (W: https://ffmpeg.org/contact.html#MailingLists) Baptiste Coudurier
Twitter Reynaldo H. Verdejo Pinochet
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
=========
@ -67,24 +85,27 @@ Other:
aes_ctr.c, aes_ctr.h Eran Kornblau
bprint Nicolas George
bswap.h
csp.c, csp.h Leo Izen, Ronald S. Bultje
des Reimar Doeffinger
dynarray.h Nicolas George
eval.c, eval.h Michael Niedermayer
eval.c, eval.h [2] Michael Niedermayer
float_dsp Loren Merritt
hash Reimar Doeffinger
hwcontext_cuda* Timo Rothenpieler
hwcontext_vulkan* Lynne
hwcontext_d3d12va* Wu Jianhua
hwcontext_oh* Zhao Zhili
hwcontext_vulkan* [2] Lynne
intfloat* Michael Niedermayer
integer.c, integer.h Michael Niedermayer
lzo Reimar Doeffinger
mathematics.c, mathematics.h Michael Niedermayer
mem.c, mem.h Michael Niedermayer
mathematics.c, mathematics.h [2] Michael Niedermayer
mem.c, mem.h [2] Michael Niedermayer
opencl.c, opencl.h Wei Gao
opt.c, opt.h Michael Niedermayer
rational.c, rational.h Michael Niedermayer
rational.c, rational.h [2] Michael Niedermayer
rc4 Reimar Doeffinger
ripemd.c, ripemd.h James Almer
tx* Lynne
tx* [2] Lynne
libavcodec
@ -106,20 +127,18 @@ Generic Parts:
DSP utilities:
dsputils.c, dsputils.h Michael Niedermayer
entropy coding:
rangecoder.c, rangecoder.h Michael Niedermayer
rangecoder.c, rangecoder.h [2] Michael Niedermayer
lzw.* Michael Niedermayer
floating point AAN DCT:
faandct.c, faandct.h Michael Niedermayer
faandct.c, faandct.h [2] Michael Niedermayer
Golomb coding:
golomb.c, golomb.h Michael Niedermayer
golomb.c, golomb.h [2] Michael Niedermayer
motion estimation:
motion* Michael Niedermayer
rate control:
ratecontrol.c Michael Niedermayer
ratecontrol.c [2] Michael Niedermayer
simple IDCT:
simple_idct.c, simple_idct.h Michael Niedermayer
postprocessing:
libpostproc/* Michael Niedermayer
simple_idct.c, simple_idct.h [2] Michael Niedermayer
table generation:
tableprint.c, tableprint.h Reimar Doeffinger
fixed point FFT:
@ -127,24 +146,23 @@ Generic Parts:
Text Subtitles Clément Bœsch
Codecs:
4xm.c Michael Niedermayer
4xm.c [2] Michael Niedermayer
8bps.c Roberto Togni
8svx.c Jaikrishnan Menon
aacenc*, aaccoder.c Rostislav Pehlivanov
adpcm.c Zane van Iperen
alacenc.c Jaikrishnan Menon
alsdec.c Thilo Borgmann, Umair Khan
amfenc* Dmitrii Ovchinnikov
amfdec*,amfenc* [2] Dmitrii Ovchinnikov, Araz Iusubov
aptx.c Aurelien Jacobs
ass* Aurelien Jacobs
asv* Michael Niedermayer
atrac3plus* Maxim Poliakovski
audiotoolbox* rcombs
avs2* Huiwen Ren
avs2* Huiwen Ren, Zhao Zhili
bgmc.c, bgmc.h Thilo Borgmann
binkaudio.c Peter Ross
cavs* Stefan Gehrer
cdxl.c Paul B Mahol
celp_filters.* Vitor Sessak
cinepak.c Roberto Togni
cinepakenc.c Rl / Aetey G.T. AB
@ -162,10 +180,10 @@ Codecs:
dss_sp.c Oleksij Rempel
dv.c Roman Shaposhnik
dvbsubdec.c Anshul Maheshwari
dxv.*, dxvenc.* Emma Worley
eacmv*, eaidct*, eat* Peter Ross
evrc* Paul B Mahol
exif.c, exif.h Thilo Borgmann
ffv1* Michael Niedermayer
ffv1* [2] Michael Niedermayer
ffwavesynth.c Nicolas George
fifo.c Jan Sebechlebsky
flicvideo.c Mike Melanson
@ -176,6 +194,7 @@ Codecs:
h263* Michael Niedermayer
h264* Loren Merritt, Michael Niedermayer
hap* Tom Butterworth
hevc/* Anton Khirnov
huffyuv* Michael Niedermayer
idcinvideo.c Mike Melanson
interplayvideo.c Mike Melanson
@ -192,9 +211,8 @@ Codecs:
libgsm.c Michel Bardiaux
libkvazaar.c Arttu Ylä-Outinen
libopenh264enc.c Martin Storsjo, Linjie Fu
libopenjpeg.c Jaikrishnan Menon
libopenjpegenc.c Michael Bradshaw
libtheoraenc.c David Conrad
libtheoraenc.c [0]
libvorbis.c David Conrad
libvpx* James Zern
libxavs.c Stefan Gehrer
@ -215,9 +233,8 @@ Codecs:
msvideo1.c Mike Melanson
nuv.c Reimar Doeffinger
nvdec*, nvenc* Timo Rothenpieler
omx.c Martin Storsjo, Aman Gupta
oh* Zhao Zhili
opus* Rostislav Pehlivanov
paf.* Paul B Mahol
pcx.c Ivo van Poorten
pgssubdec.c Reimar Doeffinger
ptx.c Ivo van Poorten
@ -227,28 +244,25 @@ Codecs:
qtrle.c Mike Melanson
ra144.c, ra144.h, ra288.c, ra288.h Roberto Togni
resample2.c Michael Niedermayer
rkmppenc* Zhao Zhili
rl2.c Sascha Sommer
rpza.c Roberto Togni
rtjpeg.c, rtjpeg.h Reimar Doeffinger
rv10.c Michael Niedermayer
s3tc* Ivo van Poorten
sanm.c Manuel Lauss
smc.c Mike Melanson
snow* Michael Niedermayer, Loren Merritt
sonic.c Alex Beregszaszi
speedhq.c Steinar H. Gunderson
srt* Aurelien Jacobs
sunrast.c Ivo van Poorten
svq3.c Michael Niedermayer
tak* Paul B Mahol
truemotion1* Mike Melanson
tta.c Alex Beregszaszi, Jaikrishnan Menon
ttaenc.c Paul B Mahol
txd.c Ivo van Poorten
v4l2_* Jorge Ramirez-Ortiz
vc2* Rostislav Pehlivanov
vcr1.c Michael Niedermayer
videotoolboxenc.c Rick Kern, Aman Gupta
vima.c Paul B Mahol
vorbisdec.c Denes Balatoni, David Conrad
vorbisenc.c Oded Shimon
vp3* Mike Melanson
@ -257,22 +271,24 @@ Codecs:
vp8 David Conrad, Ronald Bultje
vp9 Ronald Bultje
vqavideo.c Mike Melanson
vvc [2] Nuo Mi, Wu Jianhua, Frank Plowman
wmaprodec.c Sascha Sommer
wmavoice.c Ronald S. Bultje
wmv2.c Michael Niedermayer
xan.c Mike Melanson
xbm* Paul B Mahol
xface Stefano Sabatini
xwd* Paul B Mahol
Hardware acceleration:
amf* [2] Dmitrii Ovchinnikov, Araz Iusubov
dxva2* Hendrik Leppkes, Laurent Aimar, 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_encode* Mark Thompson, Haihao Xiang
vdpau* Philip Langdale, Carl Eugen Hoyos
videotoolbox* Rick Kern, Aman Gupta
videotoolbox* Rick Kern, Aman Gupta, Zhao Zhili
libavdevice
@ -302,72 +318,49 @@ libavfilter
===========
Generic parts:
Framework and orphaned filters Nicolas George
(except hardware acceleration)
graphdump.c Nicolas George
motion_estimation.c Davinder Singh
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_aphaser.c Paul B Mahol
af_aresample.c Michael Niedermayer
af_astats.c Paul B Mahol
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_hdcd.c Burt P.
af_ladspa.c Paul B Mahol
af_loudnorm.c Kyle Swanson
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
vf_blend.c Paul B Mahol
vf_bwdif Thomas Mundt (CC <thomas.mundt@hr.de>)
vf_chromakey.c Timo Rothenpieler
vf_colorchannelmixer.c Paul B Mahol
vf_colorconstancy.c Mina Sami (CC <minas.gorgy@gmail.com>)
vf_colorbalance.c Paul B Mahol
vf_colorkey.c Timo Rothenpieler
vf_colorlevels.c Paul B Mahol
vf_coreimage.m Thilo Borgmann
vf_deband.c Paul B Mahol
vf_dejudder.c Nicholas Robbins
vf_delogo.c Jean Delvare (CC <jdelvare@suse.com>)
vf_drawbox.c/drawgrid Andrey Utkin
vf_extractplanes.c Paul B Mahol
vf_histogram.c Paul B Mahol
vf_fsync.c Thilo Borgmann
vf_hqx.c Clément Bœsch
vf_idet.c Pascal Massimino
vf_il.c Paul B Mahol
vf_(t)interlace Thomas Mundt (CC <thomas.mundt@hr.de>)
vf_lenscorrection.c Daniel Oberhoff
vf_libplacebo.c Niklas Haas
vf_mergeplanes.c Paul B Mahol
vf_mestimate.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_scale.c Michael Niedermayer
vf_separatefields.c Paul B Mahol
vf_ssim.c Paul B Mahol
vf_stereo3d.c Paul B Mahol
vf_telecine.c Paul B Mahol
vf_scale.c [2] Michael Niedermayer
vf_scale_vt.c Zhao Zhili
vf_tonemap_opencl.c Ruiling Song
vf_yadif.c Michael Niedermayer
vf_zoompan.c Paul B Mahol
vf_transpose_vt.c Zhao Zhili
vf_yadif.c [2] Michael Niedermayer
vf_xfade_vulkan.c [2] Marvin Scholz (CC <epirat07@gmail.com>)
Sources:
vsrc_mandelbrot.c Michael Niedermayer
vsrc_mandelbrot.c [2] Michael Niedermayer
dnn Yejun Guo
@ -385,8 +378,7 @@ Generic parts:
Muxers/Demuxers:
4xm.c Mike Melanson
aadec.c Vesselin Bontchev (vesselin.bontchev at yandex dot com)
adtsenc.c Robert Swain
afc.c Paul B Mahol
adtsenc.c [0]
aiffdec.c Baptiste Coudurier, Matthieu Bouron
aiffenc.c Baptiste Coudurier, Matthieu Bouron
alp.c Zane van Iperen
@ -397,16 +389,12 @@ Muxers/Demuxers:
argo_brp.c Zane van Iperen
argo_cvg.c Zane van Iperen
ass* Aurelien Jacobs
astdec.c Paul B Mahol
astenc.c James Almer
avi* Michael Niedermayer
avisynth.c Stephen Hutchinson
avr.c Paul B Mahol
bink.c Peter Ross
boadec.c Michael Niedermayer
brstm.c Paul B Mahol
caf* Peter Ross
cdxl.c Paul B Mahol
codec2.c Tomas Härdin
crc.c Michael Niedermayer
dashdec.c Steven Liu
@ -415,10 +403,9 @@ Muxers/Demuxers:
dfpwmdec.c Jack Bruienne
dss.c Oleksij Rempel
dtsdec.c foo86
dtshddec.c Paul B Mahol
dv.c Roman Shaposhnik
dvdvideodec.c [2] Marth64
electronicarts.c Peter Ross
epafdec.c Paul B Mahol
evc* Samsung (Dawid Kozinski)
ffm* Baptiste Coudurier
flic.c Mike Melanson
@ -426,14 +413,16 @@ Muxers/Demuxers:
flvenc.c Michael Niedermayer, Steven Liu
gxf.c Reimar Doeffinger
gxfenc.c Baptiste Coudurier
hls.c Kacper Michajłow
hlsenc.c Christian Suloway, Steven Liu
hxvs.c Zhao Zhili
iamf* [2] James Almer
idcin.c Mike Melanson
idroqdec.c Mike Melanson
iff.c Jaikrishnan Menon
imf* Pierre-Anthony Lemieux
img2*.c Michael Niedermayer
ipmovie.c Mike Melanson
ircam* Paul B Mahol
iss.c Stefan Gehrer
jpegxl* Leo Izen
jvdec.c Peter Ross
@ -441,14 +430,12 @@ Muxers/Demuxers:
libmodplug.c Clément Bœsch
libopenmpt.c Josh de Kock
lmlm4.c Ivo van Poorten
lvfdec.c Paul B Mahol
lxfdec.c Tomas Härdin
matroska.c Aurelien Jacobs, Andreas Rheinhardt
matroskadec.c Aurelien Jacobs, Andreas Rheinhardt
matroskaenc.c David Conrad, Andreas Rheinhardt
matroska.c Andreas Rheinhardt
matroskadec.c Andreas Rheinhardt
matroskaenc.c Andreas Rheinhardt
matroska subtitles (matroskaenc.c) John Peebles
metadata* Aurelien Jacobs
mgsts.c Paul B Mahol
microdvd* Aurelien Jacobs
mm.c Peter Ross
mov.c Baptiste Coudurier
@ -461,21 +448,20 @@ Muxers/Demuxers:
msnwc_tcp.c Ramiro Polla
mtv.c Reynaldo H. Verdejo Pinochet
mxf* Baptiste Coudurier, Tomas Härdin
nistspheredec.c Paul B Mahol
nsvdec.c Francois Revol
nut* Michael Niedermayer
nuv.c Reimar Doeffinger
oggdec.c, oggdec.h David Conrad
oggenc.c Baptiste Coudurier
oggparse*.c David Conrad
oggdec.c, oggdec.h David Conrad, Romain Beauxis
oggenc.c Baptiste Coudurier, Romain Beauxis
oggparse*.c David Conrad, Romain Beauxis
oma.c Maxim Poliakovski
paf.c Paul B Mahol
pp_bnk.c Zane van Iperen
psxstr.c Mike Melanson
pva.c Ivo van Poorten
pvfdec.c Paul B Mahol
r3d.c Baptiste Coudurier
raw.c Michael Niedermayer
rcwtdec.c [2] Marth64
rcwtenc.c [2] Marth64
rdt.c Ronald S. Bultje
rl2.c Sascha Sommer
rmdec.c, rmenc.c Ronald S. Bultje
@ -494,11 +480,10 @@ Muxers/Demuxers:
sdp.c Martin Storsjo
segafilm.c Mike Melanson
segment.c Stefano Sabatini
smjpeg* Paul B Mahol
smush.c Manuel Lauss
spdif* Anssi Hannula
srtdec.c Aurelien Jacobs
swf.c Baptiste Coudurier
takdec.c Paul B Mahol
tta.c Alex Beregszaszi
txd.c Ivo van Poorten
voc.c Aurelien Jacobs
@ -507,8 +492,8 @@ Muxers/Demuxers:
webm dash (matroskaenc.c) Vignesh Venkatasubramanian
webvtt* Matthew J Heaney
westwood.c Mike Melanson
whip.c [2] Jack Lau
wtv.c Peter Ross
wvenc.c Paul B Mahol
Protocols:
async.c Zhang Rui
@ -520,37 +505,38 @@ Protocols:
libzmq.c Andriy Gelman
mms*.c Ronald S. Bultje
udp.c Luca Abeni
icecast.c Marvin Scholz
icecast.c [2] Marvin Scholz (CC <epirat07@gmail.com>)
libswresample
=============
Generic parts:
audioconvert.c Michael Niedermayer
dither.c Michael Niedermayer
rematrix*.c Michael Niedermayer
swresample*.c Michael Niedermayer
audioconvert.c [2] Michael Niedermayer
dither.c [2] Michael Niedermayer
rematrix*.c [2] Michael Niedermayer
swresample*.c [2] Michael Niedermayer
Resamplers:
resample*.c Michael Niedermayer
resample*.c [2] Michael Niedermayer
soxr_resample.c Rob Sykes
Operating systems / CPU architectures
=====================================
Alpha Falk Hueffner
*BSD [2] Brad Smith
Alpha [0]
MIPS Manojkumar Bhosale, Shiyou Yin
LoongArch Shiyou Yin
Mac OS X / PowerPC Romain Dolbeau, Guillaume Poirier
LoongArch [2] Shiyou Yin
Darwin (macOS, iOS) [2] Marvin Scholz
Mac OS X / PowerPC [0]
Amiga / PowerPC Colin Ward
Linux / PowerPC Lauri Kasanen
RISC-V Rémi Denis-Courmont
Linux / PowerPC [2] Sean McGovern (CC <gseanmcg@gmail.com>), Lauri Kasanen
RISC-V [2] Rémi Denis-Courmont
Windows MinGW Alex Beregszaszi, Ramiro Polla
Windows Cygwin Victor Paesa
Windows MSVC Matthew Oliver, Hendrik Leppkes
Windows ICL Matthew Oliver
Windows MSVC Hendrik Leppkes
ADI/Blackfin DSP Marc Hoffman
Sparc Roman Shaposhnik
OS/2 KO Myung-Hun
@ -566,6 +552,7 @@ Benjamin Larsson
Bobby Bingham
Daniel Verkamp
Derek Buitenhuis
Fei Wang
Ganesh Ajjanagadde
Henrik Gramner
Ivan Uskov
@ -573,6 +560,7 @@ James Darnley
Jan Ekström
Joakim Plate
Jun Zhao
Kacper Michajłow
Kieran Kunhya
Kirill Gavrilov
Limin Wang
@ -588,10 +576,12 @@ wm4
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.7 Michael Niedermayer
2.6 Michael Niedermayer
2.5 Michael Niedermayer
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
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
Frank Plowman 34E2 48D6 B7DF 4769 70C7 3304 03A8 4C6A 098F 2C6B
Ganesh Ajjanagadde C96A 848E 97C3 CEA2 AB72 5CE4 45F9 6A2D 3C36 FB1B
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
@ -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
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) 1D83 0A0B CE46 709E 203B 26FC 764E 48EA 4822 1833
Loren Merritt ABD9 08F4 C920 3F65 D8BE 35D7 1540 DAA7 060F 56DE
Lynne FE50 139C 6805 72CA FD52 1F8D A2FE A5F0 3F03 4464
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
Robert Swain EE7A 56EA 4A81 A7B5 2001 A521 67FA 362D A2FC 3E71
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
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

View file

@ -11,6 +11,8 @@ vpath %.asm $(SRC_PATH)
vpath %.rc $(SRC_PATH)
vpath %.v $(SRC_PATH)
vpath %.texi $(SRC_PATH)
vpath %.glsl $(SRC_PATH)
vpath %.spv $(SRC_PATH)
vpath %.cu $(SRC_PATH)
vpath %.ptx $(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
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-$(CONFIG_AVDEVICE) += avdevice
FFLIBS-$(CONFIG_AVFILTER) += avfilter
FFLIBS-$(CONFIG_AVFORMAT) += avformat
FFLIBS-$(CONFIG_AVCODEC) += avcodec
FFLIBS-$(CONFIG_POSTPROC) += postproc
FFLIBS-$(CONFIG_SWRESAMPLE) += swresample
FFLIBS-$(CONFIG_SWSCALE) += swscale
@ -47,23 +55,31 @@ FF_DEP_LIBS := $(DEP_LIBS)
FF_STATIC_DEP_LIBS := $(STATIC_DEP_LIBS)
$(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)
$(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)
$(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)
$(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)
$(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)
$(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): $(FF_DEP_LIBS)
@ -93,10 +109,11 @@ ffbuild/.config: $(CONFIGURABLE_COMPONENTS)
SUBDIR_VARS := CLEANFILES FFLIBS HOSTPROGS TESTPROGS TOOLS \
HEADERS ARCH_HEADERS BUILT_HEADERS SKIPHEADERS \
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 \
MMI-OBJS LSX-OBJS LASX-OBJS OBJS SLIBOBJS SHLIBOBJS \
STLIBOBJS HOSTOBJS TESTOBJS
MMI-OBJS LSX-OBJS LASX-OBJS RV-OBJS RVV-OBJS RVVB-OBJS \
OBJS SHLIBOBJS STLIBOBJS HOSTOBJS TESTOBJS SIMD128-OBJS \
SVE-OBJS SVE2-OBJS SME-OBJS SME2-OBJS
define RESET
$(1) :=
@ -130,16 +147,21 @@ else
endif
%$(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
ifeq ($(VERSION_TRACKING),yes)
GIT_LOG = $(SRC_PATH)/.git/logs/HEAD
endif
.version: $(wildcard $(GIT_LOG)) $(VERSION_SH) ffbuild/config.mak
.version: M=@
ifneq ($(VERSION_TRACKING),yes)
libavutil/ffversion.h .version: REVISION=unknown
endif
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
# force version.sh to run whenever version might have changed
@ -165,7 +187,8 @@ clean::
$(RM) -rf coverage.info coverage.info.in lcov
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 \
version.h libavutil/ffversion.h libavcodec/codec_names.h \
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
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