diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 35bad2724e9004..ac74ae1daaa9db 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -28,6 +28,15 @@ jobs: # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN checkout: '' # false (ref: https://github.com/actions/runner/issues/2238) + - name: Re-generate Makefiles + run: | + # config.status needs to run as a shell script + { echo ':&&exit'; cat tool/prereq.status; } > config.status + : # same as actions/setup/directories/action.yml + for mk in Makefile GNUmakefile; do + sed -f tool/prereq.status template/$mk.in > $mk + done + - name: Check for code styles run: | set -x @@ -74,7 +83,6 @@ jobs: run: | set -- $(sed 's/#.*//;/^rdoc /!d' gems/bundled_gems) { echo version=$2; echo ref=$4; } >> $GITHUB_OUTPUT - echo RDOC='ruby -W0 --disable-gems tool/rdoc-srcdir -q' >> $GITHUB_ENV - name: Checkout rdoc uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -84,7 +92,7 @@ jobs: path: .bundle/gems/rdoc-${{ steps.rdoc.outputs.version }} if: ${{ steps.rdoc.outputs.ref != '' }} - - name: Generate rdoc + - name: Generate rdoc scripts run: | set -x gempath=$(ruby -e 'print Gem.user_dir, "/bin"') @@ -98,12 +106,12 @@ jobs: - name: Core docs coverage run: | - $RDOC -C -x ^ext -x ^lib . + make XRUBY=ruby RDOC_DEPENDS= RBCONFIG=update-rbconfig rdoc-coverage - name: Generate docs id: docs run: | - $RDOC --op html . + make XRUBY=ruby RDOC_DEPENDS= RBCONFIG=update-rbconfig html echo htmlout=ruby-html-${GITHUB_SHA:0:10} >> $GITHUB_OUTPUT # Generate only when document commit/PR if: >- diff --git a/common.mk b/common.mk index 5623b50b853cd9..9506869b72a313 100644 --- a/common.mk +++ b/common.mk @@ -75,6 +75,7 @@ HTMLOUT = $(EXTOUT)/html CAPIOUT = doc/capi INSTALL_DOC_OPTS = --rdoc-output="$(RDOCOUT)" --html-output="$(HTMLOUT)" RDOC_GEN_OPTS = --no-force-update \ + --exclude '^lib/rubygems/core_ext/kernel_require\.rb$$' \ $(empty) INITOBJS = dmyext.$(OBJEXT) dmyenc.$(OBJEXT) @@ -608,11 +609,12 @@ post-install-dbg:: srcs-doc: prepare-gems -rdoc: PHONY main srcs-doc +RDOC_DEPENDS = main srcs-doc +rdoc: PHONY $(RDOC_DEPENDS) $(RBCONFIG) @echo Generating RDoc documentation $(Q) $(RDOC) --ri --op "$(RDOCOUT)" $(RDOC_GEN_OPTS) $(RDOCFLAGS) . -html: PHONY main srcs-doc +html: PHONY $(RDOC_DEPENDS) $(RBCONFIG) @echo Generating RDoc HTML files $(Q) $(RDOC) --op "$(HTMLOUT)" $(RDOC_GEN_OPTS) $(RDOCFLAGS) . @@ -620,11 +622,11 @@ RDOC_COVERAGE_EXCLUDES = -x ^ext/json -x ^ext/openssl -x ^ext/psych \ -x ^lib/bundler -x ^lib/rubygems \ -x ^lib/did_you_mean -x ^lib/error_highlight -x ^lib/syntax_suggest -rdoc-coverage: PHONY main srcs-doc +rdoc-coverage: PHONY $(RDOC_DEPENDS) $(RBCONFIG) @echo Generating RDoc coverage report $(Q) $(RDOC) --quiet -C $(RDOCFLAGS) $(RDOC_COVERAGE_EXCLUDES) . -undocumented: PHONY main srcs-doc +undocumented: PHONY $(RDOC_DEPENDS) $(RBCONFIG) $(Q) $(RDOC) --quiet -C $(RDOCFLAGS) $(RDOC_COVERAGE_EXCLUDES) . | \ sed -n \ -e '/^ *# in file /{' -e 's///;N;s/\n/: /p' -e '}' \ @@ -1643,7 +1645,8 @@ test-bundler: $(TEST_RUNNABLE)-test-bundler yes-test-bundler: $(PREPARE_BUNDLER) $(gnumake_recursive)$(XRUBY) \ -r./$(arch)-fake \ - -C $(srcdir) -Ispec/bundler -Ispec/lib spec/bin/rspec \ + -I$(srcdir)/spec/bundler -I$(srcdir)/spec/lib \ + -e 'Dir.chdir(ARGV.shift); load("spec/bin/rspec")' $(srcdir) \ -r spec_helper $(RSPECOPTS) spec/bundler/$(BUNDLER_SPECS) no-test-bundler: diff --git a/doc/language/options.md b/doc/language/options.md index 5f56ef0dd46db7..a1f29bfd03c41f 100644 --- a/doc/language/options.md +++ b/doc/language/options.md @@ -391,7 +391,7 @@ the option may be given more than once: $ ruby -e 'p defined?(JSON); p defined?(CSV)' nil nil -$ ruby -r CSV -r JSON -e 'p defined?(JSON); p defined?(CSV)' +$ ruby -r csv -r json -e 'p defined?(JSON); p defined?(CSV)' "constant" "constant" ``` @@ -685,4 +685,3 @@ and disables input from `$stdin`. ### `--version`: Print Ruby Version Option `--version` prints the version of the Ruby interpreter, then exits. - diff --git a/ext/json/json.h b/ext/json/json.h index 9379d7ae7fa97a..087a2eae66e317 100644 --- a/ext/json/json.h +++ b/ext/json/json.h @@ -55,8 +55,12 @@ typedef unsigned char _Bool; #endif #ifndef NORETURN +#if defined(__has_attribute) && __has_attribute(noreturn) +#define NORETURN(x) __attribute__((noreturn)) x +#else #define NORETURN(x) x #endif +#endif #ifndef NOINLINE #if defined(__has_attribute) && __has_attribute(noinline) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index f1ea1b6abbfdc3..e4b619b42fddca 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -400,10 +400,7 @@ static void emit_parse_warning(const char *message, JSON_ParserState *state) #define PARSE_ERROR_FRAGMENT_LEN 32 -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_parse_error(const char *format, JSON_ParserState *state) +NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *state) { unsigned char buffer[PARSE_ERROR_FRAGMENT_LEN + 3]; long line, column; @@ -449,10 +446,7 @@ static void raise_parse_error(const char *format, JSON_ParserState *state) rb_exc_raise(exc); } -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_parse_error_at(const char *format, JSON_ParserState *state, const char *at) +NORETURN(static) void raise_parse_error_at(const char *format, JSON_ParserState *state, const char *at) { state->cursor = at; raise_parse_error(format, state); @@ -776,20 +770,39 @@ NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_Parser } #define MAX_FAST_INTEGER_SIZE 18 +#define MAX_NUMBER_STACK_BUFFER 128 + +typedef VALUE (*json_number_decode_func_t)(const char *ptr); -static VALUE json_decode_large_integer(const char *start, long len) +static inline VALUE json_decode_large_number(const char *start, long len, json_number_decode_func_t func) { - VALUE buffer_v; - char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1); - MEMCPY(buffer, start, char, len); - buffer[len] = '\0'; - VALUE number = rb_cstr2inum(buffer, 10); - RB_ALLOCV_END(buffer_v); - return number; + if (RB_LIKELY(len < MAX_NUMBER_STACK_BUFFER)) { + char buffer[MAX_NUMBER_STACK_BUFFER]; + MEMCPY(buffer, start, char, len); + buffer[len] = '\0'; + return func(buffer); + } else { + VALUE buffer_v = rb_str_tmp_new(len); + char *buffer = RSTRING_PTR(buffer_v); + MEMCPY(buffer, start, char, len); + buffer[len] = '\0'; + VALUE number = func(buffer); + RB_GC_GUARD(buffer_v); + return number; + } } -static inline VALUE -json_decode_integer(uint64_t mantissa, int mantissa_digits, bool negative, const char *start, const char *end) +static VALUE json_decode_inum(const char *buffer) +{ + return rb_cstr2inum(buffer, 10); +} + +NOINLINE(static) VALUE json_decode_large_integer(const char *start, long len) +{ + return json_decode_large_number(start, len, json_decode_inum); +} + +static inline VALUE json_decode_integer(uint64_t mantissa, int mantissa_digits, bool negative, const char *start, const char *end) { if (RB_LIKELY(mantissa_digits < MAX_FAST_INTEGER_SIZE)) { if (negative) { @@ -801,22 +814,14 @@ json_decode_integer(uint64_t mantissa, int mantissa_digits, bool negative, const return json_decode_large_integer(start, end - start); } -static VALUE json_decode_large_float(const char *start, long len) +static VALUE json_decode_dnum(const char *buffer) { - if (RB_LIKELY(len < 64)) { - char buffer[64]; - MEMCPY(buffer, start, char, len); - buffer[len] = '\0'; - return DBL2NUM(rb_cstr_to_dbl(buffer, 1)); - } + return DBL2NUM(rb_cstr_to_dbl(buffer, 1)); +} - VALUE buffer_v; - char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1); - MEMCPY(buffer, start, char, len); - buffer[len] = '\0'; - VALUE number = DBL2NUM(rb_cstr_to_dbl(buffer, 1)); - RB_ALLOCV_END(buffer_v); - return number; +NOINLINE(static) VALUE json_decode_large_float(const char *start, long len) +{ + return json_decode_large_number(start, len, json_decode_dnum); } /* Ruby JSON optimized float decoder using vendored Ryu algorithm @@ -868,7 +873,7 @@ static VALUE json_find_duplicated_key(size_t count, const VALUE *pairs) return Qfalse; } -static void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_key) +NOINLINE(static) void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_key) { VALUE message = rb_sprintf( "detected duplicate key %"PRIsVALUE" in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`", @@ -879,10 +884,7 @@ static void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_ RB_GC_GUARD(message); } -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key) +NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key) { VALUE message = rb_sprintf( "duplicate key %"PRIsVALUE, diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb index afe7957f43064f..9c22c14fad5b35 100644 --- a/lib/rubygems/gemcutter_utilities.rb +++ b/lib/rubygems/gemcutter_utilities.rb @@ -154,10 +154,11 @@ def update_scope(scope) def sign_in(sign_in_host = nil, scope: nil) sign_in_host ||= host - return if api_key - pretty_host = pretty_host(sign_in_host) - + if api_key + say "You are already signed in on #{pretty_host}." + return + end say "Enter your #{pretty_host} credentials." say "Don't have an account yet? " \ "Create one at #{sign_in_host}/sign_up" diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 793aacf5c2b5cd..48a5dba4aa9db8 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -934,5 +934,11 @@ def should_not_have_gems(*gems) bundle :clean should_have_gems "bundler-#{version}" + ensure + ["bundler-#{version}.gem", "bundler-#{version}.gemspec"].each do |filename| + Pathname(vendored_gems(filename)).tap do |path| + FileUtils.rm_rf(path.basename) + end + end end end diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index ac53ba9f0c872f..1b875422a1adc5 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -135,6 +135,10 @@ def test_parse_bignum bignum = Integer('1234567890' * 10) assert_equal(bignum, JSON.parse(bignum.to_s)) assert_equal(bignum.to_f, JSON.parse(bignum.to_s + ".0")) + + bignum = Integer('1234567890' * 50) + assert_equal(bignum, JSON.parse(bignum.to_s)) + assert_equal(bignum.to_f, JSON.parse(bignum.to_s + ".0")) end def test_parse_bigdecimals diff --git a/test/rubygems/test_gem_gemcutter_utilities.rb b/test/rubygems/test_gem_gemcutter_utilities.rb index 9204dc5f20ec6f..ca34c8d03dc73a 100644 --- a/test/rubygems/test_gem_gemcutter_utilities.rb +++ b/test/rubygems/test_gem_gemcutter_utilities.rb @@ -150,7 +150,7 @@ def test_sign_in_skips_with_existing_credentials util_sign_in - assert_equal "", @sign_in_ui.output + assert_match(/You are already signed in/, @sign_in_ui.output) end def test_sign_in_skips_with_key_override @@ -158,7 +158,7 @@ def test_sign_in_skips_with_key_override @cmd.options[:key] = :KEY util_sign_in - assert_equal "", @sign_in_ui.output + assert_match(/You are already signed in/, @sign_in_ui.output) end def test_sign_in_with_other_credentials_doesnt_overwrite_other_keys diff --git a/win32/configure.bat b/win32/configure.bat old mode 100755 new mode 100644 index 68ce7fb53f3cb4..844dafd8498b4b --- a/win32/configure.bat +++ b/win32/configure.bat @@ -1,7 +1,26 @@ @echo off @setlocal EnableExtensions DisableDelayedExpansion || exit /b -1 set PROMPT=$E[94m+$E[m$S +goto :main +:set +set %* +exit /b + +:shift +call %~dp0shellsplit.cmd +set "argv1=%argv2%" +set "argv2=%argv%" +if not defined argv1 if defined argv2 goto :shift +exit /b + +:take_arg +if defined arg exit /b +if not defined argv2 exit /b +if not "%argv2:~0,1%"=="-" (set "arg=%argv2%" & call :shift) +exit /b + +:main if "%~dp0" == "%CD%\" ( echo don't run in win32 directory. exit /b 999 @@ -14,11 +33,13 @@ if "%~dp0" == "%CD%\" ( ) set "WIN32DIR=%WIN32DIR:\=/%:/:" -call set "WIN32DIR=%%WIN32DIR:%~x0:/:=:/:%%" -call set "WIN32DIR=%%WIN32DIR:/%~n0:/:=:/:%%" +call :set "WIN32DIR=%%WIN32DIR:%~x0:/:=:/:%%" +call :set "WIN32DIR=%%WIN32DIR:/%~n0:/:=:/:%%" set "WIN32DIR=%WIN32DIR:~0,-3%" set configure=%~0 +set args=%* +set target= set optdirs= set pathlist= set config_make=confargs~%RANDOM%.mak @@ -27,12 +48,21 @@ set debug_configure= echo>%config_make% # CONFIGURE type nul > %confargs% :loop -if [%1] == [] goto :end ; -if "%~1" == "" (shift & goto :loop) -for /f "delims== tokens=1,*" %%I in ("%~1") do ((set "opt=%%I") && (set "arg=%%J")) - set "eq==" - if "%arg%" == "" if not "%~1" == "%opt%=%arg%" (set "eq=") - shift +call :shift +if not defined argv1 goto :end +for /f "delims== tokens=1,*" %%I in (" %argv1% ") do ((set "opt=%%I") && (set "arg=%%J")) + set "opt=%opt:~1%" + if defined arg ( + set "eq==" + set "arg=%arg:~0,-1%" + ) else ( + set "eq=" + set "opt=%opt:~0,-1%" + ) + if "%opt%"=="" ( + echo 1>&2 %configure%: assignment for empty variable name %argv1% + exit /b 1 + ) if "%opt%" == "--debug-configure" ( echo on set "debug_configure=yes" @@ -78,7 +108,7 @@ for /f "delims== tokens=1,*" %%I in ("%~1") do ((set "opt=%%I") && (set "arg=%%J ) goto :loop ; :target - if "%eq%" == "" (set "arg=%~1" & shift) + if "%eq%" == "" call :take_arg if "%arg%" == "" ( echo 1>&2 %configure%: missing argument for %opt% exit /b 1 @@ -88,7 +118,6 @@ goto :loop ; echo>>%confargs% "--target=%arg:$=$$%" \ goto :loop ; :program_name - if "%eq%" == "" (set "arg=%~1" & shift) for /f "delims=- tokens=1,*" %I in ("%opt%") do set "var=%%J" if "%var%" == "prefix" (set "var=PROGRAM_PREFIX" & goto :name) if "%var%" == "suffix" (set "var=PROGRAM_SUFFIX" & goto :name) @@ -99,32 +128,37 @@ goto :loop ; ) goto :unknown_opt :name - if "%eq%" == "" (set "arg=%~1" & shift) + if "%eq%" == "" call :take_arg echo>> %config_make% %var% = %arg% goto :loopend ; :dir - if "%eq%" == "" (set "arg=%~1" & shift) + if "%eq%" == "" call :take_arg echo>> %config_make% %opt:~2% = %arg:\=/% goto :loopend ; :enable - echo>>%confargs% "%opt%" \ - if %enable% == yes (set "opt=%opt:~9%") else (set "opt=%opt:~10%") - if "%opt%" == "rdoc" ( + if %enable% == yes ( + if "%eq%" == "" call :take_arg + set "feature=%opt:~9%" + ) else ( + set "feature=%opt:~10%" + ) + if %enable% == yes if defined arg (set "enable=%arg%") + if "%feature%" == "install-doc" ( echo>> %config_make% RDOCTARGET = %enable:yes=r%doc ) - if "%opt%" == "install-static-library" ( + if "%feature%" == "install-static-library" ( echo>> %config_make% INSTALL_STATIC_LIBRARY = %enable% ) - if "%opt%" == "debug-env" ( + if "%feature%" == "debug-env" ( echo>> %config_make% ENABLE_DEBUG_ENV = %enable% ) - if "%opt%" == "devel" ( + if "%feature%" == "devel" ( echo>> %config_make% RUBY_DEVEL = %enable% ) - if "%opt%" == "rubygems" ( - echo>> %config_make% USE_RUBYGEMS = %enable% + if "%feature%" == "rubygems" ( + echo>> %config_make% USE_RUBYGEMS = %enable% ) -goto :loop ; +goto :loopend ; :withoutarg echo>>%confargs% "%opt%" \ if "%opt%" == "--without-baseruby" goto :nobaseruby @@ -134,7 +168,7 @@ goto :loop ; goto :loop ; :witharg if "%opt%" == "--with-static-linked-ext" goto :extstatic - if "%eq%" == "" (set "arg=%~1" & shift) + if "%eq%" == "" call :take_arg if not "%arg%" == "" ( echo>>%confargs% "%opt%=%arg:$=$$%" \ ) else ( @@ -152,25 +186,25 @@ goto :loop ; :ntver ::- For version constants, see ::- https://learn.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt#remarks - if "%eq%" == "" (set "NTVER=%~1" & shift) else (set "NTVER=%arg%") + if "%eq%" == "" (set "NTVER=%~1" & call :shift) else (set "NTVER=%arg%") if /i not "%NTVER:~0,2%" == "0x" if /i not "%NTVER:~0,13%" == "_WIN32_WINNT_" ( for %%i in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) do ( - call set NTVER=%%NTVER:%%i=%%i%% + call :set NTVER=%%NTVER:%%i=%%i%% ) - call set NTVER=_WIN32_WINNT_%%NTVER%% + call :set NTVER=_WIN32_WINNT_%%NTVER%% ) echo>> %config_make% NTVER = %NTVER% goto :loopend ; :extout - if "%eq%" == "" (set "arg=%~1" & shift) + if "%eq%" == "" call :take_arg if not "%arg%" == ".ext" (echo>> %config_make% EXTOUT = %arg%) goto :loopend ; :path - if "%eq%" == "" (set "arg=%~1" & shift) + if "%eq%" == "" call :take_arg set "pathlist=%pathlist%%arg:\=/%;" goto :loopend ; :extstatic - if "%eq%" == "" (set "arg=static" & shift) + if "%eq%" == "" (set "arg=static") echo>> %config_make% EXTSTATIC = %arg% goto :loopend ; :baseruby @@ -205,7 +239,7 @@ goto :loop ; :optdir-loop for /f "delims=; tokens=1,*" %%I in ("%arg%") do (set "d=%%I" & set "arg=%%J") pushd %d:/=\% 2> nul && ( - set "optdirs=%optdirs%;%CD:\=/%" + call :set "optdirs=%optdirs%;%%CD:\=/%%" popd ) || ( set "optdirs=%optdirs%;%d:\=/%" @@ -230,7 +264,7 @@ goto :loop ; echo --with-ntver=0xXXXX target NT version (shouldn't use with old SDK) echo --with-ntver=_WIN32_WINNT_XXXX echo --with-ntver=XXXX same as --with-ntver=_WIN32_WINNT_XXXX - echo Note that '=,;' need to be enclosed within double quotes in batch file command line. + echo Note that parameters containing spaces must be enclosed within double quotes. del %confargs% %config_make% goto :EOF :unknown_opt @@ -241,7 +275,7 @@ goto :EOF exit /b 1 :end if "%debug_configure%" == "yes" (type %confargs%) -if defined optdirs (for %%I in ("%optdirs:~1%") do echo>>%config_make% optdirs = %%~I) +if defined optdirs (echo>>%config_make% optdirs = %optdirs:~1%) ( echo. echo configure_args = \ diff --git a/win32/shellsplit.cmd b/win32/shellsplit.cmd new file mode 100644 index 00000000000000..0594f22df1143e --- /dev/null +++ b/win32/shellsplit.cmd @@ -0,0 +1,114 @@ +setlocal EnableExtensions DisableDelayedExpansion +if not defined V set V=0 +if not defined args ( + goto :return_arg +) +set INPUT=%args% +set OUTBUF= +set UNQ= +set QTD= + +set INPUT=%INPUT:#=#35% +set INPUT=%INPUT:@=#64% +set "PENDING=%INPUT:"=@sep%" &:: escape double quotes and split consecutive marks + +::#### split into unquoted part, quoted part, remains +:loop + +for /F "tokens=1,2* delims=@" %%I in (" %PENDING%") do ( + set "UNQ=%%I" + set "QTD=%%J" + set "PENDING=%%K" +) +set "UNQ=%UNQ:~1%" + +if %V%==1 ( + echo unquoted: + (echo UNQ :"%UNQ%")&(echo QTD :"%QTD%")&(echo REST:"%PENDING%")&(if defined OUTBUF echo OUTBUF:"%OUTBUF%") +) + +if defined QTD (set "QTD=%QTD:~3%") +if defined PENDING (set "PENDING=%PENDING:~3%") + +if %V%==1 ( + (echo QTD :"%QTD%")&(echo REST:"%PENDING%") +) + +if not defined UNQ if defined OUTBUF ( + set concat_next=true + goto :process_unquote +) + +set concat_prev= +set concat_check= +if defined UNQ (set "concat_check=%UNQ:~0,1%") +if not "%concat_check%"==" " (set concat_prev=true) + +set concat_next= +set concat_check= +if defined UNQ (set "concat_check=%UNQ:~-1%") +if not "%concat_check%"==" " (set concat_next=true) + +if not defined concat_prev if defined OUTBUF ( + goto :return_arg +) + +::#### process unquoted part +:process_unquote + +if defined UNQ if "%UNQ: =%"=="" (set UNQ=) +if not defined UNQ goto :process_quoted + +for /F "tokens=1* eol=" %%I in ("%UNQ%") do ( + set "token=%%I" + set "UNQ=%%J" +) + +if %V%==1 ( + (echo unq :"%token%")&(echo UNQ :"%UNQ%") +) + +set "OUTBUF=%OUTBUF%%token%" +if defined UNQ ( + goto :return_arg +) else ( + if not defined concat_next ( + goto :return_arg + ) +) + +::#### process quoted part +:process_quoted + +if %V%==1 ( + echo quoted: + (echo UNQ :"%UNQ%")&(echo QTD :"%QTD%")&(echo REST:"%PENDING%")&(if defined OUTBUF echo OUTBUF:"%OUTBUF%") +) + +set "OUTBUF=%OUTBUF%%QTD%" +set QTD= + +if not defined PENDING ( + goto :return_arg +) +goto :loop + +::#### return splitted argv +:return_arg + +set "argv=%OUTBUF%" +if defined argv (set "argv=%argv:#64=@%") +if defined argv (set "argv=%argv:#35=#%") + +if defined QTD (set QTD="%QTD%") + +:: special handling is required because they may contain double quotes + +if defined PENDING set PENDING=%PENDING:@sep="% +set args=%UNQ%%QTD%%PENDING% +if defined args set args=%args:#64=@% +if defined args set args=%args:#35=#% + +endlocal & set "argv=%argv%" & set args=%args% + +exit /b diff --git a/win32/test_shellsplit.cmd b/win32/test_shellsplit.cmd new file mode 100644 index 00000000000000..8a2e8ec8f45961 --- /dev/null +++ b/win32/test_shellsplit.cmd @@ -0,0 +1,28 @@ +@echo off & if not [%1]==[] goto :process + +echo. +echo This script demonstrates how shellsplit.cmd works. +echo usage: %0 arg1 arg2... +echo. +echo Prints separated arguments as (arg1)(arg2)... +echo - splits commandline with spaces/tabs. cmd.exe standard rule is ignored. +echo - you can use double quotes to contain spaces/tabs into an argument. +echo - you can not escape double quote. +echo - solitary "" is ignored since cmd.exe variables cannot represent empty value. +exit /b 0 + +:process +setlocal +set V=0 + +:: %* can contain meta character inside quote. do not use set "args=%*" here. +set args=%* + +:loop +call %~dp0\shellsplit.cmd +if not defined argv goto :end +set /p "tmp=(%argv%)"