diff options
-rw-r--r-- | COPYING.SOFORT | 2 | ||||
-rw-r--r-- | Makefile.in | 1 | ||||
-rw-r--r-- | config.usage | 2 | ||||
-rwxr-xr-x | configure | 33 | ||||
-rw-r--r-- | sofort/ccenv/ccenv.in | 7 | ||||
-rw-r--r-- | sofort/ccenv/ccenv.sh | 72 | ||||
-rw-r--r-- | sofort/ccenv/ccenv.vars | 6 | ||||
-rw-r--r-- | sofort/ccenv/ccswitch.strs | 1 | ||||
-rw-r--r-- | sofort/cfgtest/cfgtest.sh | 79 | ||||
-rw-r--r-- | sofort/config/config.vars | 1 | ||||
-rw-r--r-- | src/internal/argv/argv.h | 376 |
11 files changed, 496 insertions, 84 deletions
diff --git a/COPYING.SOFORT b/COPYING.SOFORT index 5e75bb6..1c8a6c1 100644 --- a/COPYING.SOFORT +++ b/COPYING.SOFORT @@ -2,7 +2,7 @@ /* */ /* sofort: portable software project skeleton */ /* */ -/* Copyright (C) 2015--2021 SysDeer Technologies, LLC */ +/* Copyright (C) 2015--2024 SysDeer Technologies, LLC */ /* */ /* sofort provides a build system that can be incorporated into Works */ /* which may or may not be covered by a copyleft license. THE FOLLOWING */ diff --git a/Makefile.in b/Makefile.in index 4e0f9e9..5ad6ff0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -84,7 +84,6 @@ CFLAGS_DEBUG += @cflags_debug@ CFLAGS_CMDLINE += @cflags_cmdline@ CFLAGS_CONFIG += @cflags_config@ CFLAGS_SYSROOT += @cflags_sysroot@ -CFLAGS_OS += @cflags_os@ CFLAGS_SITE += @cflags_site@ CFLAGS_PATH += @cflags_path@ CFLAGS_STRICT += @cflags_strict@ diff --git a/config.usage b/config.usage index 33d1e58..41fe629 100644 --- a/config.usage +++ b/config.usage @@ -143,7 +143,7 @@ supported variables: TOOLCHAIN SYSROOT CROSS_COMPILE - SHELL + CONFIG_SHELL PKGCONF PKGCONFIG @@ -33,7 +33,12 @@ usage() " ___________________________________________" \ "__________________________________" - cat "$mb_project_dir"/project/config/cfgdefs.usage + if [ -f "$mb_project_dir"/project/config/cfgdefs.usage ]; then + cat "$mb_project_dir"/project/config/cfgdefs.usage + else + printf '%s\n\n' \ + "[ info: this project does not provide a project-specific cfgdefs.usage file. ]" + fi fi exit 0 @@ -275,7 +280,7 @@ init_vars() mb_toolchain=$TOOLCHAIN mb_sysroot=$SYSROOT mb_cross_compile=$CROSS_COMPILE - mb_shell=$SHELL + mb_shell=$CONFIG_SHELL # pkgconf mb_pkgconf=$PKGCONF @@ -713,7 +718,7 @@ config_flags() # ccstrict if [ _$mb_ccstrict = _yes ]; then - mb_cflags_strict='$(_CFLAGS_Wall) $(_CFLAGS_Werror) $(_CFLAGS_Wextra) $(_CFLAGS_Wundef)' + mb_cflags_strict='$(_CFLAGS_Wall) $(_CFLAGS_Werror) $(_CFLAGS_Wextra) $(_CFLAGS_Wundef) $(_CFLAGS_Wpedantic)' fi # ldstrict @@ -842,26 +847,9 @@ config_ccenv() error_msg "${mb_pretty} forcing native mode when cross-building is strictly prohibited." fi - # add the include and library directories to the compiler and linker search path - if [ "$mb_cchost" = "$mb_native_cchost" ]; then - mb_cflags_last="$mb_cflags_last -I$mb_includedir" - mb_ldflags_last="$mb_ldflags_last -L$mb_libdir" - else - case "$mb_libdir" in - /*) - mb_cflags_last="$mb_cflags_last -I$mb_sysroot/.$mb_includedir" - mb_ldflags_last="$mb_ldflags_last -L$mb_sysroot/.$mb_libdir" - ;; - *) - mb_cflags_last="$mb_cflags_last -I$mb_includedir" - mb_ldflags_last="$mb_ldflags_last -L$mb_libdir" - esac - fi - # re-generate Makefile.tmp output_section_break config_copy - } config_custom_cfgdefs() @@ -944,7 +932,7 @@ config_host() mb_cfghost_cflags="$mb_cfghost_cflags -ffreestanding" mb_cfghost_cflags="$mb_cfghost_cflags -nostdlib" else - printf 'int main(void){return 0;}' \ + printf 'int main(void){return 0;}\n' \ > "$mb_cfghost_tmpname" fi @@ -1123,6 +1111,9 @@ for arg ; do --mandir=*) mb_mandir=${arg#*=} ;; + --docdir=*) + mb_docdir=${arg#*=} + ;; --libexecdir=*) mb_libexecdir=${arg#*=} ;; diff --git a/sofort/ccenv/ccenv.in b/sofort/ccenv/ccenv.in index 441f8ab..0daff84 100644 --- a/sofort/ccenv/ccenv.in +++ b/sofort/ccenv/ccenv.in @@ -84,8 +84,13 @@ PKGCONF = @ccenv_pkgconf@ AS = @ccenv_as@ LD = @ccenv_ld@ +# @ccenv_cfgtype@ visibility attributes +CFLAGS_ATTR_VISIBILITY_DEFAULT = -D_ATTR_VISIBILITY_DEFAULT=@ccenv_attr_visibility_default@ +CFLAGS_ATTR_VISIBILITY_HIDDEN = -D_ATTR_VISIBILITY_HIDDEN=@ccenv_attr_visibility_hidden@ +CFLAGS_ATTR_VISIBILITY_INTERNAL = -D_ATTR_VISIBILITY_INTERNAL=@ccenv_attr_visibility_internal@ +CFLAGS_ATTR_VISIBILITY_PROTECTED = -D_ATTR_VISIBILITY_PROTECTED=@ccenv_attr_visibility_protected@ + # @ccenv_cfgtype@ cflags CFLAGS_OS += -DOS_LIB_SUFFIX=\"@ccenv_os_lib_suffix@\" CFLAGS_OS += @ccenv_cflags_os@ CFLAGS_PIC += @ccenv_cflags_pic@ - diff --git a/sofort/ccenv/ccenv.sh b/sofort/ccenv/ccenv.sh index a13aa07..d975c6b 100644 --- a/sofort/ccenv/ccenv.sh +++ b/sofort/ccenv/ccenv.sh @@ -1118,14 +1118,20 @@ ccenv_set_os() fi case "$ccenv_cchost" in - *-*-*-* ) - ccenv_tip=${ccenv_cchost%-*} - ccenv_os=${ccenv_tip#*-*-} - ;; *-*-musl | *-*-gnu ) ccenv_tip=${ccenv_cchost%-*} ccenv_os=${ccenv_tip#*-} ;; + *-*-solaris* ) + ccenv_os='solaris' + ;; + *-*-*bsd* | *-*-dragonfly* ) + ccenv_os='bsd' + ;; + *-*-*-* ) + ccenv_tip=${ccenv_cchost%-*} + ccenv_os=${ccenv_tip#*-*-} + ;; *-*-* ) ccenv_os=${ccenv_cchost#*-*-} ;; @@ -1412,6 +1418,42 @@ ccenv_set_os_pe_switches() ;; esac fi + + if [ "$ccenv_cc_binfmt" = 'PE' ]; then + if ! cfgtest_macro_definition '__PE__'; then + ccenv_cflags_os="${ccenv_cflags_os} -D__PE__" + fi + + if ! cfgtest_macro_definition '__dllexport'; then + ccenv_cflags_os="${ccenv_cflags_os} -D__dllexport=__attribute__\(\(__dllexport__\)\)" + fi + + if ! cfgtest_macro_definition '__dllimport'; then + ccenv_cflags_os="${ccenv_cflags_os} -D__dllimport=__attribute__\(\(__dllimport__\)\)" + fi + fi +} + +ccenv_set_os_gate_switches() +{ + if [ "$ccenv_os" = 'solaris' ]; then + if ! cfgtest_macro_definition 'AT_FDCWD'; then + ccenv_cflags_os="${ccenv_cflags_os} -D__EXTENSIONS__" + fi + fi +} + +ccenv_set_os_bsd_switches() +{ + if [ "$ccenv_os" = 'bsd' ]; then + mb_cfgtest_headers='sys/mman.h' + + if ! cfgtest_macro_definition 'MAP_ANON'; then + ccenv_cflags_os="${ccenv_cflags_os} -D__BSD_VISIBLE" + fi + + mb_cfgtest_headers= + fi } ccenv_output_defs() @@ -1654,6 +1696,25 @@ ccenv_set_cc_linker_switch_vars() done } +ccenv_set_cc_attr_visibility_vars() +{ + if cfgtest_attr_visibility 'default'; then + ccenv_attr_visibility_default="$mb_cfgtest_attr" + fi + + if cfgtest_attr_visibility 'hidden'; then + ccenv_attr_visibility_hidden="$mb_cfgtest_attr" + fi + + if cfgtest_attr_visibility 'internal'; then + ccenv_attr_visibility_internal="$mb_cfgtest_attr" + fi + + if cfgtest_attr_visibility 'protected'; then + ccenv_attr_visibility_protected="$mb_cfgtest_attr" + fi +} + ccenv_dso_verify() { ccenv_str='int foo(int x){return ++x;}' @@ -1760,6 +1821,9 @@ ccenv_set_toolchain_variables() ccenv_set_os_dso_linkage ccenv_set_os_dso_patterns ccenv_set_os_pe_switches + ccenv_set_os_gate_switches + ccenv_set_os_bsd_switches + ccenv_set_cc_attr_visibility_vars ccenv_output_defs ccenv_clean_up diff --git a/sofort/ccenv/ccenv.vars b/sofort/ccenv/ccenv.vars index 41eb327..054b638 100644 --- a/sofort/ccenv/ccenv.vars +++ b/sofort/ccenv/ccenv.vars @@ -82,6 +82,12 @@ ccenv_windrc= ccenv_pkgconf= +# visibility attributes +ccenv_attr_visibility_default= +ccenv_attr_visibility_hidden= +ccenv_attr_visibility_internal= +ccenv_attr_visibility_protected= + # cflags ccenv_cflags_os= ccenv_cflags_pic= diff --git a/sofort/ccenv/ccswitch.strs b/sofort/ccenv/ccswitch.strs index 0a76f03..277e02e 100644 --- a/sofort/ccenv/ccswitch.strs +++ b/sofort/ccenv/ccswitch.strs @@ -34,6 +34,7 @@ -Werror -Wextra -Wundef +-Wpedantic # debugging -g diff --git a/sofort/cfgtest/cfgtest.sh b/sofort/cfgtest/cfgtest.sh index debdb8c..7bb27a0 100644 --- a/sofort/cfgtest/cfgtest.sh +++ b/sofort/cfgtest/cfgtest.sh @@ -15,6 +15,7 @@ # mb_cfgtest_cfgtype: the type of the current test (host/native) # mb_cfgtest_makevar: the make variable affected by the current test # mb_cfgtest_headers: headers for ad-hoc inclusion with the current test +# mb_cfgtest_attr: if supported, the compiler-specific attribute definition cfgtest_newline() @@ -131,6 +132,15 @@ cfgtest_epilog() return 1 fi + if [ "${1}" = 'attr' ] && [ "${2}" = '(error)' ]; then + printf '\n\ncfgtest: the %s compiler %s %s_ attribute.\n' \ + "$mb_cfgtest_cfgtype" \ + 'does not appear to support the _' \ + "${3}" >&3 + printf '%s\n' '------------------------' >&3 + return 1 + fi + if [ "${2}" = '-----' ] || [ "${2}" = '(missing)' ]; then printf '\n\ncfgtest: %s %s is missing or cannot be found.\n' "${1}" "${3}" >&3 printf '%s\n' '------------------------' >&3 @@ -246,6 +256,8 @@ cfgtest_common_init() fi elif [ "$cfgtest_type" = 'asm' ]; then cfgtest_fmt='%s -c -xc - -o a.out' + elif [ "$cfgtest_type" = 'attr' ]; then + cfgtest_fmt='%s -c -xc - -o a.out -Werror' elif [ "$cfgtest_type" = 'lib' ]; then cfgtest_fmt='%s -xc - -o a.out' elif [ "$cfgtest_type" = 'ldflag' ]; then @@ -330,11 +342,16 @@ cfgtest_header_presence() cfgtest_prolog 'header' "${1}" cfgtest_code_snippet=$(printf '#include <%s>\n' "${1}") + cfgtest_code_onedecl='int fn(void){return 0;}' + + cfgtest_code_snippet=$(printf '%s\n%s\n' \ + "$cfgtest_code_snippet" \ + "$cfgtest_code_onedecl") cfgtest_common_init # execute - printf '%s' "$cfgtest_src" \ + printf '%s\n' "$cfgtest_src" \ | eval $(printf '%s' "$cfgtest_cmd") \ > /dev/null 2>&3 \ || cfgtest_epilog 'header' '-----' "<${1}>" \ @@ -365,11 +382,16 @@ cfgtest_header_absence() cfgtest_prolog 'header absence' "${1}" cfgtest_code_snippet=$(printf '#include <%s>\n' "${1}") + cfgtest_code_onedecl='int fn(void){return 0;}' + + cfgtest_code_snippet=$(printf '%s\n%s\n' \ + "$cfgtest_code_snippet" \ + "$cfgtest_code_onedecl") cfgtest_common_init # execute - printf '%s' "$cfgtest_src" \ + printf '%s\n' "$cfgtest_src" \ | eval $(printf '%s' "$cfgtest_cmd") \ > /dev/null 2>&3 \ && printf 'cfgtest: %s header <%s>: no error.' \ @@ -401,12 +423,12 @@ cfgtest_interface_presence() # init cfgtest_prolog 'interface' "${1}" - cfgtest_code_snippet=$(printf 'void * addr = &%s;\n' "${1}") + cfgtest_code_snippet=$(printf 'void (*addr)() = (void (*)())&%s;\n' "${1}") cfgtest_common_init # execute - printf '%s' "$cfgtest_src" \ + printf '%s\n' "$cfgtest_src" \ | eval $(printf '%s' "$cfgtest_cmd") \ > /dev/null 2>&3 \ || cfgtest_epilog 'interface' '(error)' "${1}" \ @@ -443,7 +465,7 @@ cfgtest_decl_presence() cfgtest_common_init # execute - printf '%s' "$cfgtest_src" \ + printf '%s\n' "$cfgtest_src" \ | eval $(printf '%s' "$cfgtest_cmd") \ > /dev/null 2>&3 \ || cfgtest_epilog 'decl' '(error)' "${1}" \ @@ -496,7 +518,7 @@ cfgtest_type_size() cfgtest_common_init - printf '%s' "$cfgtest_src" \ + printf '%s\n' "$cfgtest_src" \ | eval $(printf '%s' "$cfgtest_cmd") \ > /dev/null 2>&3 \ && mb_internal_size=$mb_internal_guess @@ -534,6 +556,39 @@ cfgtest_type_size() } +cfgtest_attr_visibility() +{ + # init + cfgtest_prolog 'compiler visibility attr' "${1}" + + cfgtest_attr_syntax='__attribute__((__visibility__("'"${1}"'")))' + cfgtest_code_snippet="$cfgtest_attr_syntax"' int f_'"${1}"'(void);' + + cfgtest_common_init 'attr' + + # execute + cfgtest_ret=1 + + printf '%s\n' "$cfgtest_src" \ + | eval $(printf '%s' "$cfgtest_cmd") \ + > /dev/null 2>&3 \ + || cfgtest_epilog 'attr' '(error)' "${1}" \ + || return + + # result + mb_cfgtest_attr=$(printf '__attribute__\\(\\(__visibility__\\(\\"%s\\"\\)\\)\\)' "${1}") + + cfgtest_ret=0 + + printf 'cfgtest: %s compiler: above attribute is supported; see also ccenv/%s.mk.\n\n' \ + "$mb_cfgtest_cfgtype" "$mb_cfgtest_cfgtype" >&3 + + cfgtest_epilog 'attr' '(ok)' + + return 0 +} + + cfgtest_code_snippet_asm() { # init @@ -546,7 +601,7 @@ cfgtest_code_snippet_asm() # execute cfgtest_ret=1 - printf '%s' "$cfgtest_src" \ + printf '%s\n' "$cfgtest_src" \ | eval $(printf '%s' "$cfgtest_cmd") \ > /dev/null 2>&3 \ || cfgtest_epilog 'snippet' '(error)' \ @@ -579,7 +634,7 @@ cfgtest_macro_definition() # execute cfgtest_ret=1 - printf '%s' "$cfgtest_src" \ + printf '%s\n' "$cfgtest_src" \ | eval $(printf '%s' "$cfgtest_cmd") \ > /dev/null 2>&3 \ || cfgtest_epilog 'macro' '(error)' "${1}" \ @@ -627,7 +682,7 @@ cfgtest_library_presence() cfgtest_common_init 'lib' # execute - printf '%s' "$cfgtest_src" \ + printf '%s\n' "$cfgtest_src" \ | eval $(printf '%s' "$cfgtest_cmd") \ > /dev/null 2>&3 \ || cfgtest_epilog 'library' '-----' "$@" \ @@ -805,20 +860,20 @@ cfgtest_compiler_switch() ;; *) - cfgtest_code_snippet= + cfgtest_code_snippet='int fn(void){return 0;}' cfgtest_common_init 'switch' ;; esac # execute - printf '%s' "$cfgtest_src" \ + printf '%s\n' "$cfgtest_src" \ | eval $(printf '%s' "$cfgtest_cmd") \ > /dev/null 2>&3 \ || cfgtest_epilog 'switch' '(error)' "$@" \ || return 1 # result - printf 'cfgtest: the switch `%s was accepted by the compier.\n' \ + printf 'cfgtest: the switch `%s was accepted by the compiler.\n' \ "$cfgtest_switches'" >&3 printf '%s\n' '------------------------' >&3 diff --git a/sofort/config/config.vars b/sofort/config/config.vars index 0c01a29..38d5616 100644 --- a/sofort/config/config.vars +++ b/sofort/config/config.vars @@ -54,6 +54,7 @@ zealous sysroot freestanding cross_compile +config_shell shell pkgconf diff --git a/src/internal/argv/argv.h b/src/internal/argv/argv.h index fe183ad..cd20086 100644 --- a/src/internal/argv/argv.h +++ b/src/internal/argv/argv.h @@ -1,6 +1,6 @@ /****************************************************************************/ /* argv.h: a thread-safe argument vector parser and usage screen generator */ -/* Copyright (C) 2015--2021 SysDeer Technologies, LLC */ +/* Copyright (C) 2015--2024 SysDeer Technologies, LLC */ /* Released under GPLv2 and GPLv3; see COPYING.APIMAGIC */ /****************************************************************************/ @@ -13,6 +13,7 @@ #include <string.h> #include <stdlib.h> #include <stdio.h> +#include <ctype.h> #include <unistd.h> #define ARGV_VERBOSITY_NONE 0x00 @@ -57,16 +58,27 @@ #define ARGV_OPTION_HYBRID_EQUAL 0x04 #define ARGV_OPTION_HYBRID_COMMA 0x08 #define ARGV_OPTION_HYBRID_JOINED 0x10 + +#define ARGV_OPTION_KEYVAL_PAIR 0X20 +#define ARGV_OPTION_KEYVAL_ARRAY 0X40 +#define ARGV_OPTION_KEYVAL_MASK (ARGV_OPTION_KEYVAL_PAIR \ + | ARGV_OPTION_KEYVAL_ARRAY) + #define ARGV_OPTION_HYBRID_CIRCUS (ARGV_OPTION_HYBRID_SPACE \ | ARGV_OPTION_HYBRID_JOINED) + #define ARGV_OPTION_HYBRID_DUAL (ARGV_OPTION_HYBRID_SPACE \ | ARGV_OPTION_HYBRID_EQUAL) + #define ARGV_OPTION_HYBRID_SWITCH (ARGV_OPTION_HYBRID_ONLY \ | ARGV_OPTION_HYBRID_SPACE \ | ARGV_OPTION_HYBRID_EQUAL \ | ARGV_OPTION_HYBRID_COMMA \ | ARGV_OPTION_HYBRID_JOINED) +#define ARGV_KEYVAL_ASSIGN 0x01 +#define ARGV_KEYVAL_OVERRIDE 0x02 + enum argv_optarg { ARGV_OPTARG_NONE, ARGV_OPTARG_REQUIRED, @@ -83,6 +95,7 @@ enum argv_error { ARGV_ERROR_INTERNAL, ARGV_ERROR_SHORT_OPTION, ARGV_ERROR_LONG_OPTION, + ARGV_ERROR_VENDOR_OPTION, ARGV_ERROR_OPTARG_NONE, ARGV_ERROR_OPTARG_REQUIRED, ARGV_ERROR_OPTARG_PARADIGM, @@ -91,6 +104,9 @@ enum argv_error { ARGV_ERROR_HYBRID_SPACE, ARGV_ERROR_HYBRID_EQUAL, ARGV_ERROR_HYBRID_COMMA, + ARGV_ERROR_KEYVAL_KEY, + ARGV_ERROR_KEYVAL_VALUE, + ARGV_ERROR_KEYVAL_ALLOC, }; struct argv_option { @@ -104,13 +120,20 @@ struct argv_option { const char * description; }; +struct argv_keyval { + const char * keyword; + const char * value; + int flags; +}; + struct argv_entry { - const char * arg; - int tag; - bool fopt; - bool fval; - bool fnoscan; - enum argv_error errcode; + const char * arg; + struct argv_keyval * keyv; + int tag; + bool fopt; + bool fval; + bool fnoscan; + enum argv_error errcode; }; struct argv_meta { @@ -128,6 +151,7 @@ struct argv_ctx { const char * errch; const struct argv_option * erropt; const char * program; + size_t keyvlen; }; #ifdef ARGV_DRIVER @@ -135,6 +159,8 @@ struct argv_ctx { struct argv_meta_impl { char ** argv; char * strbuf; + char * keyvbuf; + char * keyvmark; struct argv_meta meta; }; @@ -304,12 +330,131 @@ static inline const struct argv_option * option_from_tag( return 0; } +static inline int argv_scan_keyval_array(struct argv_meta_impl * meta, struct argv_entry * entry) +{ + const char * ch; + char * dst; + int ncomma; + int cint; + struct argv_keyval * keyv; + + /* count key[val] elements, assume no comma after last element */ + for (ch=entry->arg,ncomma=1; *ch; ch++) { + if ((ch[0]=='\\') && (ch[1]==',')) { + ch++; + + } else if ((ch[0]==',')) { + ncomma++; + } + } + + /* keyval string buffer */ + dst = meta->keyvmark; + + /* allocate keyval array */ + if (!(entry->keyv = calloc(ncomma+1,sizeof(*entry->keyv)))) + return ARGV_ERROR_KEYVAL_ALLOC; + + /* create keyval array */ + entry->keyv->keyword = dst; + + for (ch=entry->arg,keyv=entry->keyv; *ch; ch++) { + if ((ch[0]=='\\') && (ch[1]==',')) { + *dst++ = ','; + ch++; + + } else if ((ch[0]==':') && (ch[1]=='=')) { + if (!keyv->keyword[0]) + return ARGV_ERROR_KEYVAL_KEY; + + keyv->flags = ARGV_KEYVAL_OVERRIDE; + keyv->value = ++dst; + ch++; + + } else if ((ch[0]=='=')) { + if (!keyv->keyword[0]) + return ARGV_ERROR_KEYVAL_KEY; + + keyv->flags = ARGV_KEYVAL_ASSIGN; + keyv->value = ++dst; + + } else if ((ch[0]==',')) { + for (; isblank(cint = ch[1]); ) + ch++; + + if (ch[1]) { + keyv++; + keyv->keyword = ++dst; + } + } else { + *dst++ = *ch; + } + } + + /* keyval string buffer */ + meta->keyvmark = ++dst; + + return ARGV_ERROR_OK; +} + +static inline int argv_scan_keyval_pair(struct argv_meta_impl * meta, struct argv_entry * entry) +{ + const char * ch; + char * dst; + struct argv_keyval * keyv; + + /* keyval string buffer */ + dst = meta->keyvmark; + + /* allocate keyval array */ + if (!(entry->keyv = calloc(2,sizeof(*entry->keyv)))) + return ARGV_ERROR_KEYVAL_ALLOC; + + /* create keyval array */ + entry->keyv->keyword = dst; + + for (ch=entry->arg,keyv=entry->keyv; *ch && !keyv->flags; ch++) { + if ((ch[0]=='\\') && (ch[1]==',')) { + *dst++ = ','; + ch++; + + } else if ((ch[0]==':') && (ch[1]=='=')) { + if (!keyv->keyword[0]) + return ARGV_ERROR_KEYVAL_KEY; + + keyv->flags = ARGV_KEYVAL_OVERRIDE; + keyv->value = ++dst; + ch++; + + } else if ((ch[0]=='=')) { + if (!keyv->keyword[0]) + return ARGV_ERROR_KEYVAL_KEY; + + keyv->flags = ARGV_KEYVAL_ASSIGN; + keyv->value = ++dst; + + } else { + *dst++ = *ch; + } + } + + for (; *ch; ) + *dst++ = *ch++; + + /* keyval string buffer */ + meta->keyvmark = ++dst; + + return ARGV_ERROR_OK; +} + static void argv_scan( char ** argv, const struct argv_option ** optv, struct argv_ctx * ctx, struct argv_meta * meta) { + struct argv_meta_impl * imeta; + uintptr_t addr; char ** parg; const char * ch; const char * val; @@ -323,6 +468,9 @@ static void argv_scan( bool fhybrid; bool fnoscan; + addr = (uintptr_t)meta - offsetof(struct argv_meta_impl,meta); + imeta = (struct argv_meta_impl *)addr; + parg = &argv[1]; ch = *parg; ferr = ARGV_ERROR_OK; @@ -398,7 +546,11 @@ static void argv_scan( fval = ch; } } else { - ferr = ARGV_ERROR_SHORT_OPTION; + if ((ch == &parg[0][1]) && (ch[0] == 'W') && ch[1]) { + ferr = ARGV_ERROR_VENDOR_OPTION; + } else { + ferr = ARGV_ERROR_SHORT_OPTION; + } } } else if (!fnoscan && (fhybrid || is_long_option(ch))) { @@ -413,49 +565,72 @@ static void argv_scan( ch = *parg; } - if (fhybrid && !(option->flags & ARGV_OPTION_HYBRID_SWITCH)) + /* now verify the proper setting of option values */ + if (fhybrid && !(option->flags & ARGV_OPTION_HYBRID_SWITCH)) { ferr = ARGV_ERROR_HYBRID_NONE; - else if (!fhybrid && (option->flags & ARGV_OPTION_HYBRID_ONLY)) + + } else if (!fhybrid && (option->flags & ARGV_OPTION_HYBRID_ONLY)) { ferr = ARGV_ERROR_HYBRID_ONLY; - else if (option->optarg == ARGV_OPTARG_NONE) { + + } else if (option->optarg == ARGV_OPTARG_NONE) { if (val[0]) { ferr = ARGV_ERROR_OPTARG_NONE; ctx->errch = val + 1; - } else + } else { fval = false; + } + } else if (val[0] && (option->flags & ARGV_OPTION_HYBRID_JOINED)) { fval = true; ch = val; - } else if (fhybrid && !val[0] && !(option->flags & ARGV_OPTION_HYBRID_SPACE)) - ferr = ARGV_ERROR_HYBRID_SPACE; - else if (fhybrid && (val[0]=='=') && !(option->flags & ARGV_OPTION_HYBRID_EQUAL)) + + } else if (fhybrid && !val[0] && !(option->flags & ARGV_OPTION_HYBRID_SPACE)) { + if (option->optarg == ARGV_OPTARG_OPTIONAL) { + fval = false; + + } else { + ferr = ARGV_ERROR_HYBRID_SPACE; + } + + } else if (fhybrid && (val[0]=='=') && !(option->flags & ARGV_OPTION_HYBRID_EQUAL)) { ferr = ARGV_ERROR_HYBRID_EQUAL; - else if (fhybrid && (val[0]==',') && !(option->flags & ARGV_OPTION_HYBRID_COMMA)) + + } else if (fhybrid && (val[0]==',') && !(option->flags & ARGV_OPTION_HYBRID_COMMA)) { ferr = ARGV_ERROR_HYBRID_COMMA; - else if (!fhybrid && (val[0]==',')) + + } else if (!fhybrid && (val[0]==',')) { ferr = ARGV_ERROR_HYBRID_COMMA; - else if (val[0] && !val[1]) + + } else if (val[0] && !val[1]) { ferr = ARGV_ERROR_OPTARG_REQUIRED; - else if (val[0] && val[1]) { + + } else if (val[0] && val[1]) { fval = true; ch = ++val; + } else if (option->optarg == ARGV_OPTARG_REQUIRED) { - if (!val[0] && !*parg) + if (!val[0] && !*parg) { ferr = ARGV_ERROR_OPTARG_REQUIRED; - else if (*parg && is_short_option(*parg)) + + } else if (*parg && is_short_option(*parg)) { ferr = ARGV_ERROR_OPTARG_REQUIRED; - else if (*parg && is_long_option(*parg)) + + } else if (*parg && is_long_option(*parg)) { ferr = ARGV_ERROR_OPTARG_REQUIRED; - else if (*parg && is_last_option(*parg)) + + } else if (*parg && is_last_option(*parg)) { ferr = ARGV_ERROR_OPTARG_REQUIRED; - else + + } else { fval = true; + } } else { /* ARGV_OPTARG_OPTIONAL */ fval = val[0]; } - } else + } else { ferr = ARGV_ERROR_LONG_OPTION; + } } if (ferr == ARGV_ERROR_OK) @@ -467,13 +642,20 @@ static void argv_scan( if (!option && !ctx->unitidx) ctx->unitidx = parg - argv; + if (ferr == ARGV_ERROR_OK) + if (option && (option->flags & ARGV_OPTION_KEYVAL_MASK)) + if (ctx->mode == ARGV_MODE_SCAN) + ctx->keyvlen += strlen(ch) + 1; + if (ferr != ARGV_ERROR_OK) { ctx->errcode = ferr; ctx->errch = ctx->errch ? ctx->errch : ch; ctx->erropt = option; ctx->erridx = parg - argv; return; - } else if (ctx->mode == ARGV_MODE_SCAN) { + } + + if (ctx->mode == ARGV_MODE_SCAN) { if (!fnoscan) ctx->nentries++; else if (fval) @@ -483,12 +665,12 @@ static void argv_scan( parg++; ch = *parg; } + } else if (ctx->mode == ARGV_MODE_COPY) { if (fnoscan) { if (fval) { mentry->arg = ch; mentry->fnoscan = true; - mentry++; } parg++; @@ -498,7 +680,6 @@ static void argv_scan( mentry->tag = option->tag; mentry->fopt = true; mentry->fval = fval; - mentry++; if (fval) { parg++; @@ -506,11 +687,28 @@ static void argv_scan( } } else { mentry->arg = ch; - mentry++; parg++; ch = *parg; } } + + if (option && (option->flags & ARGV_OPTION_KEYVAL_PAIR)) + if (ctx->mode == ARGV_MODE_COPY) + ferr = argv_scan_keyval_pair(imeta,mentry); + + if (option && (option->flags & ARGV_OPTION_KEYVAL_ARRAY)) + if (ctx->mode == ARGV_MODE_COPY) + ferr = argv_scan_keyval_array(imeta,mentry); + + if (ferr != ARGV_ERROR_OK) { + ctx->errcode = ferr; + ctx->errch = ctx->errch ? ctx->errch : ch; + ctx->erropt = option; + ctx->erridx = parg - argv; + return; + } + + mentry++; } } @@ -531,7 +729,11 @@ static const char * argv_program_name(const char * program_path) static void argv_show_error(int fd, struct argv_ctx * ctx) { - char opt_short_name[2] = {0,0}; + const char * src; + char * dst; + char * cap; + char opt_vendor_buf[256]; + char opt_short_name[2] = {0,0}; if (ctx->erropt && ctx->erropt->short_name) opt_short_name[0] = ctx->erropt->short_name; @@ -547,6 +749,27 @@ static void argv_show_error(int fd, struct argv_ctx * ctx) argv_dprintf(fd,"'--%s' is not a valid long option\n",ctx->errch); break; + case ARGV_ERROR_VENDOR_OPTION: + src = ctx->errch; + dst = opt_vendor_buf; + cap = &opt_vendor_buf[sizeof(opt_vendor_buf)]; + + for (; src && *src && dst<cap; ) { + if ((*src == '=') || (*src == ',') || (*src == ':')) { + src = 0; + } else { + *dst++ = *src++; + } + } + + if (dst == cap) + dst--; + + *dst = '\0'; + + argv_dprintf(fd,"'-%s' is not a valid vendor option\n",opt_vendor_buf); + break; + case ARGV_ERROR_OPTARG_NONE: argv_dprintf(fd,"'%s' is not a valid option value for [%s%s%s%s%s] " "(option values may not be specified)\n", @@ -610,6 +833,14 @@ static void argv_show_error(int fd, struct argv_ctx * ctx) break; + case ARGV_ERROR_KEYVAL_KEY: + argv_dprintf(fd,"illegal key detected in keyval argument\n"); + break; + + case ARGV_ERROR_KEYVAL_VALUE: + argv_dprintf(fd,"illegal value detected in keyval argument\n"); + break; + case ARGV_ERROR_INTERNAL: argv_dprintf(fd,"internal error"); break; @@ -625,8 +856,10 @@ static void argv_show_status( struct argv_ctx * ctx, struct argv_meta * meta) { + int i; int argc; char ** argv; + struct argv_keyval * keyv; struct argv_entry * entry; const struct argv_option * option; char short_name[2] = {0}; @@ -645,7 +878,7 @@ static void argv_show_status( argv_dprintf(fd,"argv[%d]: %s\n",argc,*argv); argv_dprintf(fd,"\n\nparsed entries:\n"); - for (entry=meta->entries; entry->arg || entry->fopt; entry++) + for (entry=meta->entries; entry->arg || entry->fopt; entry++) { if (entry->fopt) { option = option_from_tag(optv,entry->tag); short_name[0] = option->short_name; @@ -656,14 +889,48 @@ static void argv_show_status( else argv_dprintf(fd,"[-%s,--%s]\n", short_name,option->long_name); - } else + + if (entry->keyv) { + for (i=0,keyv=entry->keyv; keyv->keyword; i++,keyv++) { + switch (keyv->flags) { + case ARGV_KEYVAL_ASSIGN: + argv_dprintf(fd,"\tkeyval[%d]: <%s>=%s\n", + i,keyv->keyword,keyv->value); + break; + + case ARGV_KEYVAL_OVERRIDE: + argv_dprintf(fd,"\tkeyval[%d]: <%s>:=%s\n", + i,keyv->keyword,keyv->value); + break; + + default: + argv_dprintf(fd,"\tkeyval[%d]: <%s>\n", + i,keyv->keyword); + break; + } + } + } + } else { argv_dprintf(fd,"<program arg> := %s\n",entry->arg); + } + } argv_dprintf(fd,"\n\n"); } static struct argv_meta * argv_free_impl(struct argv_meta_impl * imeta) { + struct argv_entry * entry; + void * addr; + + if (imeta->keyvbuf) + for (entry=imeta->meta.entries; entry->fopt || entry->arg; entry++) + if (entry->keyv) + free((addr = entry->keyv)); + + if (imeta->keyvbuf) + free(imeta->keyvbuf); + if (imeta->argv) free(imeta->argv); @@ -697,7 +964,8 @@ static struct argv_meta * argv_alloc(char ** argv, struct argv_ctx * ctx) if (!(imeta->argv = calloc(argc+1,sizeof(char *)))) return argv_free_impl(imeta); - else if (!(imeta->strbuf = calloc(1,size+1))) + + if (!(imeta->strbuf = calloc(1,size+1))) return argv_free_impl(imeta); for (i=0,dst=imeta->strbuf; i<argc; i++) { @@ -707,15 +975,27 @@ static struct argv_meta * argv_alloc(char ** argv, struct argv_ctx * ctx) } imeta->meta.argv = imeta->argv; - } else + } else { imeta->meta.argv = argv; + } - if (!(imeta->meta.entries = calloc( - ctx->nentries+1, - sizeof(struct argv_entry)))) + imeta->meta.entries = calloc( + ctx->nentries+1, + sizeof(struct argv_entry)); + + if (!imeta->meta.entries) return argv_free_impl(imeta); - else - return &imeta->meta; + + if (ctx->keyvlen) { + imeta->keyvbuf = calloc( + ctx->keyvlen, + sizeof(char)); + + if (!(imeta->keyvmark = imeta->keyvbuf)) + return argv_free_impl(imeta); + } + + return &imeta->meta; } static struct argv_meta * argv_get( @@ -725,7 +1005,7 @@ static struct argv_meta * argv_get( int fd) { struct argv_meta * meta; - struct argv_ctx ctx = {flags,ARGV_MODE_SCAN,0,0,0,0,0,0,0}; + struct argv_ctx ctx = {flags,ARGV_MODE_SCAN,0,0,0,0,0,0,0,0}; argv_scan(argv,optv,&ctx,0); @@ -780,6 +1060,7 @@ static void argv_usage_impl( { const struct argv_option ** optv; const struct argv_option * option; + int nlong; bool fshort,flong,fboth; size_t len,optlen,desclen; char cache; @@ -812,7 +1093,7 @@ static void argv_usage_impl( if (header) argv_dprintf(fd,"%s",header); - for (optlen=0,optv=options; *optv; optv++) { + for (optlen=0,nlong=0,optv=options; *optv; optv++) { option = *optv; /* indent + comma */ @@ -831,6 +1112,11 @@ static void argv_usage_impl( /* optlen */ if (len > optlen) optlen = len; + + /* long (vs. hybrid-only) option? */ + if (option->long_name) + if (!(option->flags & ARGV_OPTION_HYBRID_ONLY)) + nlong++; } if (optlen >= optcap) { @@ -851,7 +1137,7 @@ static void argv_usage_impl( /* color */ if (fcolor) { color = (color == ccyan) ? cblue : ccyan; - argv_dprintf(fd,color); + argv_dprintf(fd,color,0); } /* description, using either paradigm or argname if applicable */ @@ -863,7 +1149,11 @@ static void argv_usage_impl( /* long/hybrid option prefix (-/--) */ prefix = option->flags & ARGV_OPTION_HYBRID_ONLY - ? " -" : "--"; + ? " -" : " --"; + + /* avoid extra <stace> when all long opts are hybrid-only */ + if (nlong == 0) + prefix++; /* option string */ if (fboth && option->short_name && option->long_name) |