-
Notifications
You must be signed in to change notification settings - Fork 2
/
build.bat
3420 lines (3278 loc) · 133 KB
/
build.bat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
@:: build [TARGET [ARGS ...]]
@:: TARGET == realclean | <makefile target>
@:: ARGS == <makefile TARGET args>
@setlocal
@echo off
set __dp0=%~dp0
set __ME=%~n0
set build_dir=%__dp0%@build
rem set build_dir=%build_dir_base%-x^%%_bin_type^%%
set src_dir=%__dp0%PCRE2-mirror
call :$create_list build_bin_types 32 64
::
:: NOTE: using scoop (see "http://scoop.sh") to install/check `gcc` prereqs
:: scoop install git cmake gcc-tdw gow &:: install `cmake`, 'gcc-tdw' (multilib/32+64bit), and 'gow'
:: configuration
:: NOTES
:: Test #2: "API, errors, internals, and non-Perl stuff" FAILS with GPF if using stack recursion with default stack size
:: * use either "-D PCRE2_HEAP_MATCH_RECURSE:BOOL=ON" or increase stack size to pass
:: user-configurable cmake project properties
set "project_props="
::
:: ref: http://pcre.org/current/doc/html/pcre2build.html @@ https://archive.is/rbI5U
::
::set "project_props=%project_props% -D BUILD_SHARED_LIBS:BOOL=ON" &:: build shared/dynamic library; (default == OFF)
::
::set "project_props=%project_props% -D PCRE2_BUILD_PCRE2_8:BOOL=OFF" &:: build 8-bit PCRE library (default == ON; used by pcregrep)
set "project_props=%project_props% -D PCRE2_BUILD_PCRE2_16:BOOL=ON" &:: build 16-bit PCRE library
set "project_props=%project_props% -D PCRE2_BUILD_PCRE2_32:BOOL=ON" &:: build 32-bit PCRE library
::
::set "project_props=%project_props% -D PCRE2_EBCDIC:BOOL=ON" &:: use EBCDIC coding instead of ASCII; (default == OFF)
::set "project_props=%project_props% -D PCRE2_EBCDIC_NL25:BOOL=ON" &:: use 0x25 as EBCDIC NL character instead of 0x15; implies EBCDIC; (default == OFF)
::
::set "project_props=%project_props% -D PCRE2_LINK_SIZE:STRING=4" &:: internal link size (in bytes) [ 2 (maximum 64Ki compiled pattern size ("gigantic patterns")), 3 ("truly enormous"), 4 ("truly enormous"+) ]
::set "project_props=%project_props% -D PCRE2_PARENS_NEST_LIMIT:STRING=500" &:: maximum depth of nesting parenthesis within regex pattern (default == 250)
::set "project_props=%project_props% -D PCRE2GREP_BUFSIZE:STRING=51200" &:: internal buffer size (longest line length guaranteed to be processable) (default == 20480)
set "project_props=%project_props% -D PCRE2_NEWLINE:STRING=ANYCRLF" &:: EOLN matching [CR, LF, CRLF, ANYCRLF, ANY (any Unicode newline sequence)] (default == LF) (NOTE: always overridable at run-time)
::set "project_props=%project_props% -D PCRE2_HEAP_MATCH_RECURSE:BOOL=ON" &:: OFF == use stack recursion; ON == use heap for recursion (slower); (default == OFF == stack recursion)
set "project_props=%project_props% -D PCRE2_SUPPORT_JIT:BOOL=ON" &:: support for Just-In-Time compiling (default == OFF)
::set "project_props=%project_props% -D PCRE2_SUPPORT_PCRE2GREP_JIT:BOOL=OFF" &:: support for Just-In-Time compiling in pcre2grep (default == ON)
::set "project_props=%project_props% -D PCRE2_SUPPORT_UNICODE:BOOL=OFF" &:: enable support for Unicode and UTF-8/UTF-16/UTF-32 encoding (default == ON)
::set "project_props=%project_props% -D PCRE2_SUPPORT_BSR_ANYCRLF:BOOL=ON" &:: ON=Backslash-R matches only LF CR and CRLF, OFF=Backslash-R matches all Unicode Linebreaks; (default == OFF)
::set "project_props=%project_props% -D PCRE2_SUPPORT_VALGRIND:BOOL=ON" &:: enable Valgrind support (default == OFF)
::
::set "project_props=%project_props% -D PCRE2_SHOW_REPORT:BOOL=OFF" &:: show configuration report (default == ON)
::set "project_props=%project_props% -D PCRE2_BUILD_PCRE2GREP:BOOL=OFF" &:: build pcre2grep (default == ON)
::set "project_props=%project_props% -D PCRE2_BUILD_TESTS:BOOL=OFF" &:: build tests (default == ON)
::
:: MinGW
::set "project_props=%project_props% -D NON_STANDARD_LIB_PREFIX:BOOL=ON" &:: ON=Shared libraries built in mingw will be named pcre2.dll, etc., instead of libpcre2.dll, etc (default == OFF)
::set "project_props=%project_props% -D NON_STANDARD_LIB_SUFFIX:BOOL=ON" &:: ON=Shared libraries built in mingw will be named libpcre2-0.dll, etc., instead of libpcre2.dll, etc. (default == OFF)
::
:: MSVC
::set "project_props=%project_props% -D INSTALL_MSVC_PDB:BOOL=ON" &:: ON=Install .pdb files built by MSVC, if generated (default == OFF)
:: cmake settings
:: CMAKE_VERBOSE_MAKEFILE
::set "project_props=%project_props% -D CMAKE_VERBOSE_MAKEFILE:BOOL=ON" &:: create verbose makefile (default == OFF)
:: CMAKE_C_FLAGS
set "CMAKE_C_FLAGS="
set "CMAKE_GCC_C_FLAGS="
set "CMAKE_MSVC_C_FLAGS="
:: CMAKE_EXE_LINKER_FLAGS
set "CMAKE_EXE_LINKER_FLAGS="
set "CMAKE_GCC_EXE_LINKER_FLAGS="
set "CMAKE_MSVC_EXE_LINKER_FLAGS="
:: set increased stack size
set "stack_size=8388608" &:: set new stack size to 8MiB
:: set stack size for GCC
set "CMAKE_GCC_C_FLAGS=%CMAKE_GCC_C_FLAGS% -Wl,--stack,%stack_size%" &:: GCC ~ set stack size
:: set stack size for MSVC
::set "CMAKE_MSVC_C_FLAGS=%CMAKE_MSVC_C_FLAGS% /F%stack_size%" &:: ** not working ** MSVC ~ set stack size (via `cl`)
set "CMAKE_MSVC_EXE_LINKER_FLAGS=%CMAKE_MSVC_EXE_LINKER_FLAGS% /STACK:%stack_size%" &:: MSVC ~ set stack size (via `link`)
:: CMAKE_BUILD_TYPE
set "CMAKE_BUILD_TYPE=MinSizeRel" &:: [<empty/null>, "Debug", "Release", "RelWithDebInfo", "MinSizeRel"]
:: cmake / make
set "CC="
set "CFLAGS="
set "CXX="
set "CXXFLAGS="
set "LDFLAGS="
::
:: TARGET: realclean
if /i "%~1" == "realclean" (
rem :: remove build directories
rem for /d %%D in ("%build_dir%*") do rmdir /s /q "%%D"
if EXIST "%build_dir%" (
rmdir /s /q "%build_dir%"
echo "%build_dir%" removed
)
exit /b 0
)
::
set "_exit_code="
set "compiler_found="
:: GCC build(s)
:GCC
:: call :$path_of_file_in_pathlist _path "gcc" "%PATH%" ".exe;.com;%PATHEXT%"
call :$path_of_file_in_pathlist _path "gcc.exe" "%PATH%" &:: ~50% faster than using "%PATHEXT%"
if NOT DEFINED _path goto :GCC_DONE
set "compiler_found=1"
setlocal
:: GCC clean PATH
:: remove PATH references to alternate compiler installations
:: ... CMAKE can get confused by alternate and incompatible headers/libraries if alternate GCC installations are in PATH (eg, `perl`'s included GCC)
:: NOTE: assumes PATH contains fully qualified paths (without trailing slashes)
call :$echo_color cyan "%__ME%: INFO: cleaning PATH for GCC"
set new_PATH=%PATH%
rem :: call :$path_of_file_in_pathlist _path "gcc" "%new_PATH%" ".exe;.com;%PATHEXT%"
rem call :$path_of_file_in_pathlist _path "gcc.exe" "%new_PATH%" &:: ~50% faster than using "%PATHEXT%"
call :$FQ_dir_of _dir "%_path%"
call :$remove_from_list new_PATH "%_dir%" "%new_PATH%"
set "prior_dir="
:GCC_clean_PATH_LOOP
::call :$path_of_file_in_pathlist _path "gcc" "%new_PATH%" ".exe;.com;%PATHEXT%"
call :$path_of_file_in_pathlist _path "gcc.exe" "%new_PATH%" &:: ~50% faster than using "%PATHEXT%"
if NOT DEFINED _path ( goto :GCC_clean_PATH_LOOP_DONE )
call :$FQ_dir_of _dir "%_path%"
if /I "%prior_dir%" == "%_dir%" ( goto :GCC_clean_PATH_LOOP_DONE ) &:: repeating same loop (PATH likely has non-matching trailing slashes)
call :$remove_from_list new_PATH "%_dir%" "%new_PATH%"
call :$remove_from_list PATH "%_dir%" "%PATH%"
goto :GCC_clean_PATH_LOOP
:GCC_clean_PATH_LOOP_DONE
set "_list=%build_bin_types%"
call :$first_of _bin_type "%_list%"
call :$remove_first _list "%_list%"
::GCC build loop
:GCC_build_LOOP
:: check for ability to compile
call :$tempfile _OUTFNAME gcc.test .exe
if NOT DEFINED _OUTFNAME ( call :$echo_color red "%__ME%: ERR!: unable to create temp file" & exit /b -1 )
if DEFINED _bin_type set "_bin_type_FLAG=-m%_bin_type%"
set "ERRORLEVEL="
echo void main(){} | gcc %_bin_type_FLAG% -x c -o"%_OUTFNAME%" - 2>NUL 1>&2
set _ERR=%ERRORLEVEL%
erase /q "%_OUTFNAME%" 2>NUL 1>&2
set "_bin_type_text="
if DEFINED _bin_type set "_bin_type_text=%_bin_type%-bit "
if NOT "%_ERR%" == "0" ( call :$echo_color darkyellow "%__ME%: WARN: `gcc` unable to create %_bin_type_text%binaries" & goto :GCC_build_LOOP_NEXT )
::
:: check/create directory
set "_dir=%build_dir%\MinGW"
:: hide redundant cmake output report if build directory already present (== initial build already complete)
set "_suppress_cmake_output=1"
if DEFINED _bin_type set "_dir=%_dir%-x%_bin_type%"
if DEFINED CMAKE_BUILD_TYPE set "_dir=%_dir%.%CMAKE_BUILD_TYPE%"
if NOT EXIST "%_dir%" ( mkdir "%_dir%" & set "_suppress_cmake_output=" )
cd %_dir%
call :$echo_color yellow "[%_dir%]"
::
call :$echo_color cyan "%__ME%: INFO: starting cmake"
set "CMAKE_BUILD_TYPE_OPTION="
if DEFINED CMAKE_BUILD_TYPE ( set "CMAKE_BUILD_TYPE_OPTION=-D CMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE%")
set "_bin_type_FLAG="
if DEFINED _bin_type set "_bin_type_FLAG=-m%_bin_type%"
set "_cmake_stdout="
if DEFINED _suppress_cmake_output ( set "_cmake_stdout=>NUL" )
set "ERRORLEVEL=" &:: clear any previous erroneus ERRORLEVEL overrides
cmake -G "MinGW Makefiles" %CMAKE_BUILD_TYPE_OPTION% -D CMAKE_MAKE_PROGRAM=make -D CMAKE_C_COMPILER=gcc -D CMAKE_C_FLAGS="%_bin_type_FLAG% %CMAKE_C_FLAGS% %CMAKE_GCC_C_FLAGS%" -D CMAKE_EXE_LINKER_FLAGS="%CMAKE_EXE_LINKER_FLAGS% %CMAKE_GCC_EXE_LINKER_FLAGS%" %project_props% "%src_dir%" %_cmake_stdout%
if NOT "%ERRORLEVEL%"=="0" ( set "_exit_code=%ERRORLEVEL%" & call :$echo_color red "%__ME%: ERR!: cmake error occurred" & goto :GCC_build_LOOP_NEXT )
set command=make & set args=%*
if DEFINED args (set command=`make %*`)
call :$echo_color cyan "%__ME%: INFO: starting %command%"
make %*
if NOT "%ERRORLEVEL%"=="0" ( set "_exit_code=%ERRORLEVEL%" & call :$echo_color red "%__ME%: ERR!: make error occurred" )
:GCC_build_LOOP_NEXT
if NOT DEFINED _list goto :GCC_build_LOOP_DONE
call :$first_of _bin_type "%_list%"
call :$remove_first _list "%_list%"
goto :GCC_build_LOOP
:GCC_build_LOOP_DONE
::
endlocal
:GCC_DONE
::
:: MSVC build
:MSVC
:: call :$path_of_file_in_pathlist _path "cl" "%PATH%" ".exe;.com;%PATHEXT%"
call :$path_of_file_in_pathlist _path "cl.exe" "%PATH%" &:: ~50% faster than using "%PATHEXT%"
if NOT DEFINED _path goto :MSVC_DONE
set "compiler_found=1"
setlocal
rem goto :MSVC_clean_PATH_LOOP_DONE
:: MSVC clean PATH
:: remove PATH references to alternate compiler installations
:: ... CMAKE can get confused by alternate and incompatible headers/libraries if alternate GCC installations are in PATH (eg, `perl`'s included GCC)
:: NOTE: assumes PATH contains fully qualified paths (without trailing slashes)
call :$echo_color cyan "%__ME%: INFO: cleaning PATH for MSVC"
set new_PATH=%PATH%
set "prior_dir="
:MSVC_clean_PATH_LOOP
::call :$path_of_file_in_pathlist _path "gcc" "%new_PATH%" ".exe;.com;%PATHEXT%"
call :$path_of_file_in_pathlist _path "gcc.exe" "%new_PATH%" &:: ~50% faster than using "%PATHEXT%"
if NOT DEFINED _path ( goto :MSVC_clean_PATH_LOOP_DONE )
call :$FQ_dir_of _dir "%_path%"
if /I "%prior_dir%" == "%_dir%" ( goto :MSVC_clean_PATH_LOOP_DONE ) &:: repeating same loop (PATH likely has non-matching trailing slashes)
call :$remove_from_list new_PATH "%_dir%" "%new_PATH%"
call :$remove_from_list PATH "%_dir%" "%PATH%"
goto :MSVC_clean_PATH_LOOP
:MSVC_clean_PATH_LOOP_DONE
::MSVC/nmake build
:MSVC_build
::check/create directory
set "_dir=%build_dir%\nmake"
if DEFINED VCvars_CL_VER set "_dir=%_dir%-cl@%VCvars_CL_VER%"
:: hide redundant cmake output report if build directories are already present (== initial build already complete)
set "_suppress_cmake_output=1"
if DEFINED CMAKE_BUILD_TYPE set "_dir=%_dir%.%CMAKE_BUILD_TYPE%"
if NOT EXIST "%_dir%" ( mkdir "%_dir%" & set "_suppress_cmake_output=" )
cd %_dir%
call :$echo_color yellow "[%_dir%]"
::
call :$echo_color cyan "%__ME%: INFO: starting cmake"
set "CMAKE_BUILD_TYPE_OPTION="
if DEFINED CMAKE_BUILD_TYPE ( set "CMAKE_BUILD_TYPE_OPTION=-D CMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE%")
set "_cmake_stdout="
if DEFINED _suppress_cmake_output ( set "_cmake_stdout=>NUL" )
set "ERRORLEVEL=" &:: clear any previous erroneus ERRORLEVEL overrides
cmake -G "NMake Makefiles" %CMAKE_BUILD_TYPE_OPTION% -D CMAKE_MAKE_PROGRAM=nmake -D CMAKE_C_COMPILER=cl -D CMAKE_C_FLAGS="%_bin_type_FLAG% %CMAKE_C_FLAGS% %CMAKE_MSVC_C_FLAGS%" -D CMAKE_EXE_LINKER_FLAGS="%CMAKE_EXE_LINKER_FLAGS% %CMAKE_MSVC_EXE_LINKER_FLAGS%" %project_props% "%src_dir%" %_cmake_stdout%
if NOT "%ERRORLEVEL%"=="0" ( set "_exit_code=%ERRORLEVEL%" & call :$echo_color red "%__ME%: ERR!: cmake error occurred" & goto :MSVC_build_DONE )
set command=nmake & set args=%*
if DEFINED args (set command=`nmake %*`)
call :$echo_color cyan "%__ME%: INFO: starting %command%"
nmake %*
if NOT "%ERRORLEVEL%"=="0" ( set "_exit_code=%ERRORLEVEL%" & call :$echo_color red "%__ME%: ERR!: nmake error occurred" )
:MSVC_build_DONE
::
endlocal
:MSVC_DONE
::
::
:DONE
if NOT DEFINED compiler_found ( set "_exit_code=-1" & call :$echo_color red "%__ME%: ERR!: no compiler found" )
exit /b %_exit_code%
::
::
goto :EOF
:: ### SUBs
::
:$echo_color ( [FORE_COLOR [BACK_COLOR]] TEXT )
:_echo_color ( [FORE_COLOR [BACK_COLOR]] TEXT )
:: echo TEXT to console with foreground FORE_COLOR and background BACK_COLOR (defaults to regular echo if powershell is not present)
:: FORE_COLOR == standard color name for foreground color
:: BACK_COLOR == standard color name for background color
:: TEXT == TEXT to echo
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=$echo_color"
call :$echo_color_NO_NEWLINE %*
echo:
endlocal
goto :EOF
::
::
:$echo_color_NO_NEWLINE ( [FORE_COLOR [BACK_COLOR]] TEXT )
:_echo_color_NO_NEWLINE ( [FORE_COLOR [BACK_COLOR]] TEXT )
:: echo TEXT to console with foreground FORE_COLOR and background BACK_COLOR (defaults to regular echo if powershell is not present)
:: FORE_COLOR == standard color name for foreground color
:: BACK_COLOR == standard color name for background color
:: TEXT == TEXT to echo
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=$echo_color"
set "fore_color=%~1"
set "back_color=%~2"
set "text=%~3"
if NOT DEFINED text ( set "text=%back_color%" & set "back_color=" )
if NOT DEFINED text ( set "text=%fore_color%" & set "fore_color=" )
set "options="
if DEFINED fore_color ( set "options=%options% -fore %fore_color%" )
if DEFINED back_color ( set "options=%options% -back %back_color%" )
::
set _POWERSHELL=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe
if EXIST "%_POWERSHELL%" ( goto :_echo_color_POWERSHELL_out )
echo text
goto :_echo_color_DONE
:_echo_color_POWERSHELL_out
"%_POWERSHELL%" -noprofile -ex unrestricted "write-host -nonewline %options% '%text%'"
:_echo_color_DONE
:_echo_color_RETURN
endlocal
goto :EOF
::
@::::
@:: FUNCTIONS (library:rev69) :: (C) 2010-2016, Roy Ivy III, MIT license
@call %*
@goto :EOF
::
:: NOTE: ToDO: add noLF versions to all $$echo... functions
:: NOTE: ToDO: add ability to store/use/rerturn double-quotes and semicolons (? using character replacements)
:: #.... use ref: https://sbjh.wordpress.com/2013/02/08/generate-any-control-character @@ https://archive.is/QYqgh
:: NOTE: fix bug working with ampersand (&) characters; any problems with redirection characters, etc?
:: NOTE: for use with TCC, _MUST_ have TCMD.INI "CMDVariables=Yes"; this, plus all the WAD ("works as designed") bugs & the interminably slow execution of large batch files makes the case for completely removing TCC compatibility
:: NOTE: BAT/CMD script files require PC (CRLF) line endings for correct function; ASCII/UTF-8 encoding should also be used
:: NOTES
:: DEFINITIONS
:: ITEMs are character strings which may contain no internal double quotes (and no characters outside the usual non-graphical, printable set [ ord(ch) > 31, ord(ch) < 128 ]
:: LISTs are a ';' seperated list of ITEMs
:: SETs or PATHLISTs are LISTs which contain unique individual ITEMs (and don't accept duplicate additions)
:: PATHLISTs don't accept NULL ITEM additions
:: PATHLISTs normalize all added ITEMs as PATHs (which removes any trailing backslashes [DOS/Win path dividers])
:: BOOLEAN values are defined as FALSE == NULL, TRUE == any non-NULL value (generally "1" or "true", but can be any non-NULL, including "0")
:: :: this makes testing for truth easy... if DEFINED foo ( echo true ) else ( echo false )
::
:: LISTs may contain NULL values, and are all defined as "" in the special case of containing a single NULL value
:: NULL valued ITEMs within LISTs may be either encoded as either "" or NULL, but will be returned as NULL when using functions returning individual elements within LISTs
::
:: NOTE: Don't use '(set %RETvar%=%RETval%)' for return values as internal close parenthesis in RETval will cause errors. Use 'set "%RETvar%=%RETval%"' instead.
:: NOTE: ... if RETval may contain double quotes, must use 'endlocal & set %RETvar%=%RETval%', as surrounding aa double quote with double quotes won't work.
:: ToDO: add new function "get_output( REF_VARNAME COMMAND )" that runs COMMAND & stores the last line output into VARNAME (initial plan is to use a temporary file)
:: ToDO: ( endlocal ... ) block and variations to bypass setlocal/endlocal walls
:: ToDO: BLOG needed parens for ifs in end ( endlocal ... ) block o/w only 1st if is evaluated
:: URLref: Use of $ as subroutine sigil @@ http://www.webcitation.org/5z4F3V9yk @ 2011-05-30.0754
:: ToDO: rethink returning values (using jeb's techinique @ http://www.dostips.com/forum/viewtopic.php?p=6930#p6930 @@ http://www.webcitation.org/6ADfeDSrJ)
:: ToDO: rethink PATH functions in reference to the following notes:
:: : [How-TO check if directory is within PATH] "http://stackoverflow.com/questions/141344/how-to-check-if-directory-exists-in-path/8046515#8046515"
:: : [How-TO split PATH on ';' - initial correct answer] http://stackoverflow.com/questions/5471556/pretty-print-windows-path-variable-how-to-split-on-in-cmd-shell/5472168#5472168
:: : [How-TO split PATH on ';' - slightly improved answer] http://stackoverflow.com/a/7940444/43774
:: ToDO: look at [DOS Function Collection] http://www.dostips.com/DtCodeFunctions.php @@ http://www.webcitation.org/6ADg2siao
:: ToDO: look at [Date Math] http://www.robvanderwoude.com/datetimentmath.php @@ http://www.webcitation.org/6Ei47MYcp AND http://www.powercram.com/2010/07/get-yesterdays-date-in-ms-dos-batch.html @@ http://www.webcitation.org/6ADgoDmWV
:: DONE: add is_dir [see http://stackoverflow.com/questions/8909355/how-to-check-if-target-of-path-is-a-directory]
:: ToDO: rethink PREinitialize ... currently, doesn't work as portrayed; %0 %* is NOT the main script and it's arguments; so, the script can't be re-run as written; additionally, environment variables and path changes are lost when returning from the subshell, so it's not correct; calling the script directly doesn't change the parse/comspec misalignment
:: :: possible, changing comspec and recalling directly after PREinitialize might work
:: NOTE: the only culprit known right now is Perl with a TCC shell and an empty Perl5SHELL environment variable
:: :: this is one Perl fix: `perl -e "if (not defined $ENV{PERL5SHELL}) {$ENV{COMSPEC}=q{c:\windows\system32\cmd.exe}}; system(q{gccvars perl});"`
:: ToDO: ?? change API function names to use sigil sign ($)?
:: NOTE: Use DEFINED and NOT DEFINED instead of comparison of vars to "" when possible to avoid quoting issues as much as possible
:: NOTE: "%_RETvar%" == "" comparision @ function RETURN is necessary because of endlocal which undefines _RETvar after the %% instantiation
:: DONE::ToDO: update API changes :: _echo_no_LF => _echo_noLF ; _path_of_first => _path_of_file
:: ToDO: !! change path_of_item_in_pathlist to either NOTE NULL item will be found in first existing path OR return NULL
:: ToDO: !! copy updated library to all other BATs
:: ToDO: update library documentation with a list of functions & short, one line explanations as a summary section
:: URLref: [CMD Syntax: Escape characters, delimiters, & quotes] http://ss64.com/nt/syntax-esc.html @@ http://archive.is/V92JZ
:: URLref: [CMD parsing (at command line & at batch file level)] http://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/4095133#4095133
:: URLref: [dbenham HOME - CharLib.BAT] https://sites.google.com/site/dbenhamfiles/home @@ http://archive.is/8KAbT
:: URLref: [BUG SOLVED! - Return any string across ENDLOCAL boundary; using %%2 ... in FOR] http://www.dostips.com/forum/viewtopic.php?f=3&t=1839 @@ http://archive.is/0QuYk
::
:__exec_self_as_BTM
:: NOTE: should NOT be called as a subroutine; ONLY use directly, with a guarded GOTO (eg, "if 1.0 == 01 if /i "%~x0" neq ".BTM" goto :__exec_self_as_BTM")
:: VARS: GLOBAL::__exec_self_as_BTM_localize; must be set prior to execution; BOOLEAN == is current script localized within SETlocal/ENDlocal block?; required to set up balanced SETlocal/ENDlocal blocks within newly incarnated BTM
:: VARS: GLOBAL::__ME, __ME_dir, __ME_fullpath; REQUIRED; these are passed on through to the new BTM script as pointers to the context of the original script
:: NOTE: Prior failed attempts at TCC speedup:
:: * @if 1.0 == 1 ( loadbtm on 1> nul 2> nul & cd . ) &:: only works for TCC (not TCC/LE) and DOESN'T work well at all, timing seems just as slow!! (Maybe this is because of the call's to subroutines?)
:: * @if 1.0 == 1 ( option //UpdateTitle=No ) &:: ToDO: TEST by timing -- speeds up TCC execution by disabling window title updates? -- NO, initial testing shows no differences
if NOT 01 == 1.0 ( echo %__ME%:__exec_self_as_BTM: ERROR: Console interpreter is NOT TCC/4NT; unable to execute script as BTM 1>&2 & @echo %__ME_echo% & @exit /B -1 )
if NOT defined __ME ( echo %__ME%:__exec_self_as_BTM: ERROR: __ME must be defined prior to call; unable to execute script as BTM 1>&2 & @echo %__ME_echo% & @exit /B -1 )
if NOT defined __ME_dir ( echo %__ME%:__exec_self_as_BTM: ERROR: __ME_dir must be defined prior to call; unable to execute script as BTM 1>&2 & @echo %__ME_echo% & @exit /B -1 )
if NOT defined __ME_fullpath ( echo %__ME%:__exec_self_as_BTM: ERROR: __ME_fullpath must be defined prior to call; unable to execute script as BTM 1>&2 & @echo %__ME_echo% & @exit /B -1 )
call :_tempfile __exec_self_as_BTM_TEMPFILE "%__ME_fullpath%" .BTM
if NOT exist "%__exec_self_as_BTM_TEMPFILE%" ( echo %__ME%:__exec_self_as_BTM: ERROR: unable to open temporary file for BTM creation 1>&2 & @echo %__ME_echo% & @exit /B -1 )
if DEFINED __exec_self_as_BTM_localize echo @setlocal>> "%__exec_self_as_BTM_TEMPFILE%"
echo @:: [dynamic BTM, created from "%__ME_fullpath%"]>> "%__exec_self_as_BTM_TEMPFILE%"
echo @set __exec_self_as_BTM_TEMPFILE=>> "%__exec_self_as_BTM_TEMPFILE%"
echo set __ME=%__ME%>> "%__exec_self_as_BTM_TEMPFILE%"
echo set __ME_dir=%__ME_dir%>> "%__exec_self_as_BTM_TEMPFILE%"
echo set __ME_fullpath=%__ME_fullpath%>> "%__exec_self_as_BTM_TEMPFILE%"
echo @goto :__BTM_ENTRY >> "%__exec_self_as_BTM_TEMPFILE%"
::type "%~dpnx0" >> "%__exec_self_as_BTM_TEMPFILE%"
::type "%~f0" >> "%__exec_self_as_BTM_TEMPFILE%"
type "%__ME_fullpath%" >> "%__exec_self_as_BTM_TEMPFILE%"
:: correct setlocal/endlocal imbalance
if NOT DEFINED __exec_self_as_BTM_localize goto :__exec_self_as_BTM_call
endlocal & set __exec_self_as_BTM_TEMPFILE=%__exec_self_as_BTM_TEMPFILE%
:__exec_self_as_BTM_call
:: call new BTM incarnation
call "%__exec_self_as_BTM_TEMPFILE%" %*
:__exec_self_as_BTM_END
:: clean up temporaries
if EXIST "%__exec_self_as_BTM_TEMPFILE%" erase "%__exec_self_as_BTM_TEMPFILE%" >NUL
set __exec_self_as_BTM_TEMPFILE=
goto :EOF
::
::
:$$echo_state ( ref_RETURN )
:$echo_state ( ref_RETURN )
:_echo_state ( ref_RETURN )
:: determine and return the current echo state [ON/OFF]
:: RETURN == ON _or_ OFF [corresponding to the current echo state and usable as "echo %RETval%"]
:: NOTE: subs generally expect echo ON/OFF to be set prior to call for debugging so don't use any subroutine calls
@setlocal
@set "__DEBUG_KEY=@"
@set "__MEfn=_echo_state"
@set "_RETval=OFF"
@set "_RETvar=%~1"
@if NOT DEFINED temp ( set "temp=%tmp%" )
@if NOT EXIST "%temp%" ( set "temp=%tmp%" )
@if NOT EXIST "%temp%" ( set "temp=%LocalAppData%\Temp" )
@if NOT EXIST "%temp%" ( set "temp=%SystemRoot%\Temp" )
@if NOT EXIST "%temp%" ( set "temp=." )
:_echo_state_find_unique_temp
@set "tempfile=%temp%\%~nx0.echo_state.%RANDOM%.%RANDOM%.txt"
@if EXIST %tempfile% ( @goto :_echo_state_find_unique_temp )
@echo > "%tempfile%"
@type "%tempfile%" | "%SystemRoot%\System32\findstr" /i /r " [(]*on[)]*\.$" >nul 2>&1
@if "%ERRORLEVEL%"=="0" ( set _RETval=ON )
::@erase "%tempfile%" /Q > nul 2>&1
@endlocal & @set %_RETvar%^=%_RETval%
@if NOT 01 == 1.0 (@exit /b 0) else (@quit 0)
::
::
:$$tempfile ( ref_RETURN [PREFIX [EXTENSION]])
:$tempfile ( ref_RETURN [PREFIX [EXTENSION]])
:_tempfile ( ref_RETURN [PREFIX [EXTENSION]])
:: open a unique temporary file
:: RETURN == full pathname of temporary file (with given PREFIX and EXTENSION) [NOTE: has NO surrounding quotes]
:: PREFIX == optional filename prefix for temporary file
:: EXTENSION == optional extension (including leading '.') for temporary file [default == '.bat']
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=_tempfile"
set "_RETval="
set "_RETvar=%~1"
set "prefix=%~nx2"
set "extension=%~3"
if NOT DEFINED extension ( set "extension=.txt")
:: attempt to find a temp directory
:: NOTE: see [How-TO check for directory-only existence] http://stackoverflow.com/a/12037613/43774
if NOT DEFINED temp ( set "temp=%tmp%" )
if NOT EXIST "%temp%" ( set "temp=%tmp%" )
if NOT EXIST "%temp%" ( set "temp=%LocalAppData%\Temp" )
if NOT EXIST "%temp%" ( set "temp=%SystemRoot%\Temp" )
if NOT EXIST "%temp%" ( goto :_tempfile_RETURN ) &:: undefined TEMP, RETURN (with NULL filename)
:_tempfile_find_unique_temp
set "_RETval=%temp%\%prefix%.TEMPFILE.%RANDOM%.%RANDOM%%extension%"
if EXIST %_RETval% ( goto :_tempfile_find_unique_temp )
:: instantiate tempfile [NOTE: this is an unavoidable race condition]
if NOT 01 == 1.0 (
set /p OUTPUT=<nul >"%_RETval%" 2>nul
) else (
echos >"%_RETval%" 2>nul
)
if NOT EXIST "%_RETval%" (
echo %__ME%:%__MEfn%: ERROR: unable to open tempfile [%_RETval%] 1>&2
set "_RETval="
)
:_tempfile_find_unique_temp_DONE
:_tempfile_RETURN
::endlocal & if NOT 01 == 1.0 (set "%_RETvar%=%_RETval%") else (set "%_RETvar=%_RETval")
endlocal & set %_RETvar%^=%_RETval%
goto :EOF
::
::
:__PREinitialize ( [ ref_RETURN ] )
:: RETURN == COMSPEC name ('cmd', 'tcc')
::
:: BAT/CMD preinitialization for alternative shell compatibility [TCC/4NT, etc]
:: get COMSPEC filename and determine shell parsing type
:: NOTE: COMSPEC and parsing shell could be different if, for example, this BAT is executed from perl (perl defaults to using 'cmd' for system commands despite %COMSPEC% [see PERL5SHELL references in URLrefs: http://perldoc.perl.org/perlwin32.html#Usage-Hints-for-Perl-on-Win32 , http://perldoc.perl.org/perlrun.html#ENVIRONMENT])
:: NOTE: MINIMIZE subroutine calls within __PREinitialize (and any called subroutines) to allow quick transition to BTM for TCC (especially, comment out "call :_echo_DEBUG_KEY ..." calls unless/until needed)
setlocal
set "__DEBUG_KEY=@@"
set "__MEfn=__PREinitialize"
call :_echo_DEBUG_KEY [ %__MEfn% :: start ]
set "_RETval="
set "_RETvar=%~1"
call :_echo_DEBUG_KEY _RETvar="%_RETvar%"
:__PREinitialize_CMDVARIABLES_CHECK
::TCC/4NT; expect "CMDVariables=Yes" in TCMD.ini to maximize compatibility
if NOT 01 == 1.0 ( goto :__PREinitialize_CMDVARIABLES_CHECK_DONE )
(set _TEMP=)
(set _TEMP(x86)=1)
set _TEMP=%_TEMP(x86)%
if "%_TEMP%" == "1" ( goto :__PREinitialize_CMDVARIABLES_CHECK_DONE )
echo %__ME%:%__MEfn%: ERROR: "CMDVariables=No" in TCMD.ini; make "CMDVariables=Yes" in TCMD.ini for CMD compatibility 1>&2
if 01 == 1.0 ( @echo %__ME_echo% & @quit -1 ) else ( @echo %__ME_echo% & @exit /B -1 ) &::TCC drops/ignores exit ERRORLEVEL from subroutines [so, use "quit" instead]
:__PREinitialize_CMDVARIABLES_CHECK_DONE
::check shell PARSE type vs COMSPEC
call :_filename_of _COMSPECNAME "%ComSpec%"
set "_PARSETYPE=%_COMSPECNAME%"
:: known parsers (CMD & TCC)
if 01 == 1.0 ( set "_PARSETYPE=tcc" ) else ( set "_PARSETYPE=cmd" )
call :_echo_DEBUG_KEY _COMSPECNAME="%_COMSPECNAME%"
call :_echo_DEBUG_KEY _PARSETYPE="%_PARSETYPE%"
:: if same, then assume user wants the current shell and continue
if /i [%_COMSPECNAME%]==[%_PARSETYPE%] ( goto :__PREinitialize_RETURN )
:: NOTE: this doesn't work ...
:: :: otherwise, restart using known COMSPEC with CMD.exe fallback (if found)
:: :: ?? use /d to avoid AutoRun for TCC and/or CMD shell execution
::if /i "%_COMSPECNAME%"=="tcc" (
:: "%ComSpec%" /x/c %0 %*
:: exit /B %ERRORLEVEL%
:: )
::set ComSpec=%SystemRoot%\\System32\\cmd.exe
::if EXIST "%ComSpec%" (
:: "%ComSpec%" /x/c %0 %*
:: exit /B %ERRORLEVEL%
:: )
:: echo %__ME%:%__MEfn%: ERROR: unmatched batch file parser and COMSPEC [unrecognized current COMSPEC and unable to find CMD.exe (at "%ComSpec%")]
:: exit /B -1
::
::
:__PREinitialize_RETURN
if /i NOT [%_COMSPECNAME%]==[%_PARSETYPE%] (
echo %__ME%:%__MEfn%: ERROR: unmatched batch file parser and COMSPEC 1>&2
if 01 == 1.0 ( @echo %__ME_echo% & @quit -1 ) else ( @echo %__ME_echo% & @exit /B -1 ) &::TCC drops/ignores exit ERRORLEVEL from subroutines [so, use "quit" instead]
)
set "_RETval=%_COMSPECNAME%"
call :_echo_DEBUG_KEY _COMSPECNAME="%_COMSPECNAME%"
call :_echo_DEBUG_KEY _RETvar="%_RETvar%"
call :_echo_DEBUG_KEY _RETval="%_RETval%"
::( endlocal
:: ( if "%_RETvar%" NEQ "" ( if NOT 01 == 1.0 (
:: set "%_RETvar%=%_RETval%"
:: ) else (
:: set %_RETvar=%_RETval
:: ))
::call :_echo_item_DEBUG_KEY "%__DEBUG%" "%__DEBUG_KEY%" "%__ME%" "%__MEfn%" "[ %__MEfn% :: done ]"
::)
endlocal & set %_RETvar%^=%_RETval%
goto :EOF
::
::
:$$filename_of ( ref_RETURN PATH )
:$filename_of ( ref_RETURN PATH )
:_filename_of ( ref_RETURN PATH )
:: RETURN == filename of PATH
:: NOTE: special processing to deal correctly with the case of "<DRIVE>:" ("<DRIVE>:" == "<DRIVE>:." == ".")
:: NOTE: _filename_of("") == ""
:: NOTE: _filename_of("\\") == _filename_of("\") == _filename_of("c:\") == _filename_of("c:\.") == ""
:: NOTE: _filename_of("c:") == _filename_of("c:.") == _filename_of(".")
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=_filename_of"
call :_echo_DEBUG_KEY [ %__MEfn% :: start ]
set "_RETval="
set "_RETvar=%~1"
set "_path=%~2"
if NOT DEFINED _path ( goto :_filename_of_RETURN )
if "%_path%" == "\\" ( set "_path=\" )
::?:call :_drive_of drive "%~1"
::?:if /i "%drive%" == "%~1" ( set "_RETval=" & goto :_filename_of_RETURN )
::?:if /i "%drive%\" == "%~1" ( set "_RETval=" & goto :_filename_of_RETURN )
call :_rewrite_path_to_FQ_local _path _ "%_path%"
call :_echo_DEBUG_KEY _path="%_path%"
call :_echo_DEBUG_KEY _="%_%"
call :_param_tilde_N _RETval "%_path%"
:: remove trailing backslashes
call :_rtrim _RETval "%_RETval%" "\"
:_filename_of_RETURN
::( endlocal
:: ( if "%_RETvar%" NEQ "" ( if NOT 01 == 1.0 (
:: set "%_RETvar%=%_RETval%"
:: ) else (
:: set %_RETvar=%_RETval
:: ))
::call :_echo_item_DEBUG_KEY "%__DEBUG%" "%__DEBUG_KEY%" "%__ME%" "%__MEfn%" "[ %__MEfn% :: done ]"
::)
endlocal & set %_RETvar%^=%_RETval%
goto :EOF
::
::
:$$rewrite_path_to_FQ_local ( ref_RETURN_path ref_RETURN_drive PATH )
:$rewrite_path_to_FQ_local ( ref_RETURN_path ref_RETURN_drive PATH )
:_rewrite_path_to_FQ_local ( ref_RETURN_path ref_RETURN_drive PATH )
:: RETURN_path == PATH, rewritten in a fully qualified (semi-canonical) form: removing extraneous trailing "\" or "\." and changing path to point to current drive (if a drive is specified)
:: RETURN_drive == original drive of PATH, "" if no drive was specified
:: NOTE:: changing PATH to refer to a similar "false" PATH on the local drive (for CMD and/or TCC) speeds up calculations for UNC network paths (avoiding network timeouts)
:: NOTE:: special processing is needed to deal with the fact that TCC acts out with almost unsuppressible errors for inaccessible and UNC PATHs [which is "WAD" per developer [meh, see URLref: http://jpsoft.com/forums/threads/using-dp1-for-paths-with-unavailable-drives.3450 @@ http://www.webcitation.org/63ua1bpOk]]
:: NOTE:: changing PATH to refer to a local drive for TCC [avoids a loud & nasty TCC bug [URLref: http://jpsoft.com/forums/threads/using-dp1-for-paths-with-unavailable-drives.3450 @@ http://webcitation.org/63ua1bpOk]]
:: NOTE: assumes PATH has leading "X:", "\\...\...", "\...", or is a relative path (without leading "\")
:: NOTE: _rewrite_path_to_FQ_local("") => ("", "")
:: NOTE: _rewrite_path_to_FQ_local("\\") == _rewrite_path_to_FQ_local("\")
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=_rewrite_path_to_FQ_local"
call :_echo_DEBUG_KEY [ %__MEfn% :: start ]
set "_RETvar=%~1"
set "_RETvar_drive=%~2"
set "_RETval=%~3"
set "drive="
call :_echo_DEBUG_KEY PATH="%_RETval%"
if NOT DEFINED _RETval ( goto :_rewrite_path_to_FQ_local_RETURN )
set "_path=%_RETval%"
if "%_path%" == "." ( goto :_rewrite_path_to_FQ_local_RETURN )
set local_drive=
call :_split_drive_path_of drive _path "%_path%"
if NOT DEFINED _path ( set "_path=." )
call :_echo_DEBUG_KEY drive="%drive%"
call :_echo_DEBUG_KEY _path="%_path%"
:_rewrite_path_to_FQ_local_LOOP
if NOT DEFINED _path ( goto :_rewrite_path_to_FQ_local_LOOP_DONE )
if "%_path%" == "\\" ( set "_path=\" )
if "%_path%" == "\." ( set "_path=\" )
if "%_path%" == "\" ( goto :_rewrite_path_to_FQ_local_LOOP_DONE )
:: TCC doesn't handle trailing "\." or "\" correctly, so remove them
call :_echo_DEBUG_KEY 2_path="%_path%"
:_rewrite_path_to_FQ_local_LOOP_test_1
if NOT "%_path:~-2,2%" == "\." ( goto :_rewrite_path_to_FQ_local_LOOP_test_2 )
set "_path=%_path:~0,-2%"
goto :_rewrite_path_to_FQ_local_LOOP
:_rewrite_path_to_FQ_local_LOOP_test_2
call :_echo_DEBUG_KEY 3_path="%_path%"
if NOT "%_path:~-1,1%" == "\" ( goto :_rewrite_path_to_FQ_local_LOOP_test_3 )
set "_path=%_path:~0,-1%"
goto :_rewrite_path_to_FQ_local_LOOP
:_rewrite_path_to_FQ_local_LOOP_test_3
if NOT "%_path:~0,2%" == "\\" ( goto :_rewrite_path_to_FQ_local_LOOP_test_DONE )
set "_RETval=%_path:~1%"
:_rewrite_path_to_FQ_local_LOOP_test_DONE
:_rewrite_path_to_FQ_local_LOOP_DONE
call :_echo_DEBUG_KEY _RETval="%_RETval%"
if NOT DEFINED drive ( goto :_rewrite_path_to_FQ_local_DONE )
if NOT DEFINED local_drive ( set "local_drive=%SYSTEMDRIVE%" )
if NOT DEFINED local_drive ( set "local_drive=%SYSTEMROOT:~0,2%" )
if NOT DEFINED local_drive ( set "local_drive=%~d0" )
call :_echo_DEBUG_KEY local_drive="%local_drive%"
set "_RETval=%local_drive%%_path%"
:_rewrite_path_to_FQ_local_DONE
:_rewrite_path_to_FQ_local_RETURN
call :_echo_DEBUG_KEY _RETvar="%_RETvar%"
call :_echo_DEBUG_KEY _RETval="%_RETval%"
::( endlocal
:: ( if "%_RETvar%" NEQ "" ( if NOT 01 == 1.0 (
:: set "%_RETvar%=%_RETval%"
:: ) else (
:: set %_RETvar=%_RETval
:: ))
::call :_echo_item_DEBUG_KEY "%__DEBUG%" "%__DEBUG_KEY%" "%__ME%" "%__MEfn%" "[ %__MEfn% :: done ]"
::)
endlocal & set "%_RETvar%=%_RETval%" & set "%_RETvar_drive%=%drive%"
goto :EOF
::
::
:$$drive_of ( ref_RETURN PATH )
:$drive_of ( ref_RETURN PATH )
:_drive_of ( ref_RETURN PATH )
:: RETURN == drive of PATH
:: NOTE: assumes PATH has leading "X:", "\\SERVER"; PATHs without a drive indicator return NULL
:: NOTE: drive_of("") == ""; drive_of("\\SERVER\PATH") == "\\SERVER"
:: NOTE: special UNC paths: drive_of("\\?\UNC\PATH") => drive_of("\\PATH"); drive_of("\\?\PATH") == drive_of("\\.\PATH") => drive_of("PATH")
:: URLref: [Naming Files, Paths, and Namespaces - MSDN] http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx @@ http://archive.is/EcTrM @@ http://webcitation.org/6IwYVd1Cn
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=_drive_of"
call :_echo_DEBUG_KEY [ %__MEfn% :: start ]
set "_RETval="
set "_RETvar=%~1"
set "_path=%~2"
call :_split_drive_path_of _RETval _ "%_path%"
::if NOT DEFINED _RETval ( set "_RETval=%~d2" ) &:: causes TCC errors for non-existant PATHs
:_drive_of_RETURN
call :_echo_DEBUG_KEY _RETvar="%_RETvar%"
call :_echo_DEBUG_KEY _RETval="%_RETval%"
::( endlocal
:: ( if "%_RETvar%" NEQ "" ( if NOT 01 == 1.0 (
:: set "%_RETvar%=%_RETval%"
:: ) else (
:: set %_RETvar=%_RETval
:: ))
::call :_echo_item_DEBUG_KEY "%__DEBUG%" "%__DEBUG_KEY%" "%__ME%" "%__MEfn%" "[ %__MEfn% :: done ]"
::)
endlocal & set %_RETvar%^=%_RETval%
goto :EOF
::
::
:$$FQ_drive_of ( ref_RETURN PATH )
:$FQ_drive_of ( ref_RETURN PATH )
:_FQ_drive_of ( ref_RETURN PATH )
:: RETURN == fully qualified (canonical) directory of PATH
:: NOTE: _FQ_drive_of("") == ""
:: NOTE: _FQ_drive_of("\\") == _FQ_drive_of("\") == _FQ_drive_of("\.")
:: NOTE: _FQ_drive_of("c:") == _FQ_drive_of("c:.")
:: NOTE: _FQ_drive_of("c:\") == _FQ_drive_of("c:\.") == "c:\"
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=_FQ_drive_of"
call :_echo_DEBUG_KEY [ %__MEfn% :: start ]
set "_RETval="
set "_RETvar=%~1"
:: avoid TCC path parsing errors for null strings
set "_RETval=%~2"
if NOT DEFINED _RETval ( goto :_FQ_drive_of_RETURN )
call :_rewrite_path_to_FQ_local _ drive "%_RETval%"
if NOT DEFINED drive ( goto :_FQ_drive_of_DONE )
:_FQ_drive_of_DONE
if NOT DEFINED drive ( set "drive=%SYSTEMDRIVE%" )
if NOT DEFINED drive ( set "drive=%SYSTEMROOT:~0,2%" )
if NOT DEFINED drive ( set "drive=%~d0" )
call :_echo_DEBUG_KEY drive="%drive%"
:_FQ_drive_of_DONE
set "_RETval=%drive%"
:_FQ_drive_of_RETURN
endlocal & set %_RETvar%^=%_RETval%
goto :EOF
::
::
:$$split_drive_path_of ( ref_RETURN_DRIVE ref_RETURN_PATH PATH )
:$split_drive_path_of ( ref_RETURN_DRIVE ref_RETURN_PATH PATH )
:_split_drive_path_of ( ref_RETURN_DRIVE ref_RETURN_PATH PATH )
:: RETURN_DRIVE == drive of PATH
:: RETURN_PATH == PATH with drive prefix removed
:: NOTE: assumes PATH has leading "X:", "\\", "\...", or is a relative path (without leading "\")
:: NOTE: drive_of("") == ""; drive_of("\\SERVER\PATH") == "\\SERVER"
:: NOTE: special UNC paths: drive_of("\\?\UNC\PATH") => drive_of("\\PATH"); drive_of("\\?\PATH") => drive_of("PATH"); drive_of("\\.\DEVICE\PATH") => "\\.\DEVICE"
:: URLref: [Naming Files, Paths, and Namespaces - MSDN] http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx @@ http://archive.is/EcTrM @@ http://webcitation.org/6IwYVd1Cn
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=_split_drive_path_of"
call :_echo_DEBUG_KEY [ %__MEfn% :: start ]
set "_RETval_drive="
set "_RETvar_drive=%~1"
set "_RETvar_path=%~2"
set "_path=%~3"
:_split_drive_path_of_START
if NOT DEFINED _path ( goto :_split_drive_path_of_RETURN )
if "%_path%" == "\\" ( goto :_split_drive_path_of_RETURN )
if NOT "%_path:~0,2%" == "\\" ( goto :_split_drive_path_of_NONUNC )
set "_RETval_drive=\\"
set "_path=%_path:~2%"
:_split_drive_path_of_TEST_1
if NOT "%_path:~0,6%" == "?\UNC\" ( goto :_split_drive_path_of_TEST_2 )
set "_path=\\%_path:~6%"
goto :_split_drive_path_of_UNC_LOOP
:_split_drive_path_of_TEST_2
if NOT "%_path:~0,2%" == "?\" ( goto :_split_drive_path_of_TEST_3 )
set "_path=%_path:~2%"
goto :_split_drive_path_of_START
:_split_drive_path_of_TEST_3
if NOT "%_path:~0,2%" == ".\" ( goto :_split_drive_path_of_TEST_4 )
set "_RETval_drive=\\.\"
set "_path=%_path:~2%"
:_split_drive_path_of_TEST_4
:_split_drive_path_of_UNC_LOOP
if NOT DEFINED _path ( goto :_split_drive_path_of_UNC_LOOP_DONE )
if "%_path:~0,1%" == "\" ( goto :_split_drive_path_of_UNC_LOOP_DONE )
set "_RETval_drive=%_RETval_drive%%_path:~0,1%"
set "_path=%_path:~1%"
goto :_split_drive_path_of_UNC_LOOP
:_split_drive_path_of_UNC_LOOP_DONE
goto :_split_drive_path_of_RETURN
:_split_drive_path_of_NONUNC
if NOT "%_path:~1,1%" == ":" ( goto :_split_drive_path_of_TEST_6 )
set "_RETval_drive=%_path:~0,2%"
set "_path=%_path:~2%"
goto :_split_drive_path_of_RETURN
:_split_drive_path_of_TEST_6
::set "_RETval_drive=%~d3"
:_split_drive_path_of_RETURN
call :_echo_DEBUG_KEY _RETvar_drive="%_RETvar_drive%"
call :_echo_DEBUG_KEY _RETval_drive="%_RETval_drive%"
call :_echo_DEBUG_KEY _RETvar_path="%_RETvar_path%"
call :_echo_DEBUG_KEY _RETval_path="%_path%"
::( endlocal
:: ( if "%_RETvar%" NEQ "" ( if NOT 01 == 1.0 (
:: set "%_RETvar%=%_RETval%"
:: ) else (
:: set %_RETvar=%_RETval
:: ))
::call :_echo_item_DEBUG_KEY "%__DEBUG%" "%__DEBUG_KEY%" "%__ME%" "%__MEfn%" "[ %__MEfn% :: done ]"
::)
:: return values via a single line with surrounding double-quotes; this is fine here, given double-quotes are illegal in NTFS filenames
endlocal & set "%_RETvar_drive%=%_RETval_drive%" & set "%_RETvar_path%=%_path%"
goto :EOF
::
::
:$$rtrim ( ref_RETURN ITEM [CHARSET] )
:$rtrim ( ref_RETURN ITEM [CHARSET] )
:_rtrim ( ref_RETURN ITEM [CHARSET] )
:: trim characters in CHARSET from right-side of ITEM
:: RETURN = ITEM with rightmost CHARSET characters removed
:: URLrefs: [Variable editing] http://ss64.com/nt/syntax-substring.html, [How to trim whitespace from a string] http://www.experts-exchange.com/OS/Microsoft_Operating_Systems/MS_DOS/Q_23816304.html
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=_rtrim"
call :_echo_DEBUG_KEY [ %__MEfn% :: start ]
set "_RETval="
set "_RETvar=%~1"
set "item=%~2"
if 1.0 == 01 (
set item=%@unquotes[%2]
)
set "charset=%~3"
if 1.0 == 01 (
set charset=%@unquotes[%3]
)
if NOT DEFINED charset ( set "charset= " ) &:: NOTE: default charset = " \t"
call :_echo_DEBUG_KEY item="%item%"
call :_echo_DEBUG_KEY charset="%charset%"
:: change any internal double quotes to chr(255) (avoids syntax errors during the character comparison and removal process) [ NOTE: may have internal double quotes, so no outer quotes for set; this also creates a problem with internal ()'s if the set is enclosed in a block, so use a goto around it as needed]
if NOT DEFINED item ( goto :_rtrim_LOOP_ch )
set "item=%item:"=¸%"
set "charset=%charset:"=¸%"
set "chars=%charset%"
:_rtrim_LOOP_ch
call :_echo_DEBUG_KEY LOOP.item="%item%"
call :_echo_DEBUG_KEY charset="%charset%"
call :_echo_DEBUG_KEY LOOP.chars="%chars%"
set "ch="
if DEFINED chars ( set "ch=%chars:~0,1%" & set "chars=%chars:~1%" )
if NOT DEFINED ch ( goto :_rtrim_LOOP_DONE )
:_rtrim_LOOP_removal
if NOT DEFINED item ( goto :_rtrim_LOOP_DONE )
set "last_ch="
if DEFINED item ( set "last_ch=%item:~-1%" )
call :_echo_DEBUG_KEY item="%item%"
call :_echo_DEBUG_KEY ch="%ch%"
call :_echo_DEBUG_KEY last_ch="%last_ch%"
if /i "%last_ch%" == "%ch%" (
set "item=%item:~0,-1%"
set "chars=%charset%"
goto :_rtrim_LOOP_removal
)
goto :_rtrim_LOOP_ch
:_rtrim_LOOP_DONE
:_rtrim_RETURN
if NOT DEFINED item ( goto :_rtrim_RETURN_translate_DONE )
:: return any double quotes to ITEM
set item=%item:¸=^"%
:: " [balance double quote for editor parsing]
:_rtrim_RETURN_translate_DONE
set _RETval=%item%
call :_echo_DEBUG_KEY _RETvar="%_RETvar%"
call :_echo_DEBUG_KEY _RETval="%_RETval%"
::( endlocal
:: ( if "%_RETvar%" NEQ "" ( if NOT 01 == 1.0 (
:: set "%_RETvar%=%_RETval%"
:: ) else (
:: set %_RETvar=%_RETval
:: ))
::call :_echo_item_DEBUG_KEY "%__DEBUG%" "%__DEBUG_KEY%" "%__ME%" "%__MEfn%" "[ %__MEfn% :: done ]"
::)
endlocal & set %_RETvar%^=%_RETval%
goto :EOF
::
::
:$$param_tilde_N ( ref_RETURN PATH )
:$param_tilde_N ( ref_RETURN PATH )
:_param_tilde_N ( ref_RETURN PATH )
:: NOTE: for TCC, assume that PATH is on (or has been forced onto) an accessible drive and not a UNC pathname [necessary to avoid unsupressable TCC parsing errors; which is "WAD" per developer [meh, see URLref: http://jpsoft.com/forums/threads/using-dp1-for-paths-with-unavailable-drives.3450 @@ http://www.webcitation.org/63ua1bpOk]]
:: RETURN == name of PATH
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=_param_tilde_N"
call :_echo_DEBUG_KEY [ %__MEfn% :: start ]
set "_RETvar=%~1"
set "_RETval=%~2"
call :_echo_DEBUG_KEY _RETvar="%_RETvar%"
call :_echo_DEBUG_KEY _RETval="%_RETval%"
if NOT DEFINED _RETval ( goto :_param_tilde_N_RETURN )
set "_RETval=%~n2"
call :_echo_DEBUG_KEY _RETval="%_RETval%"
:_param_tilde_N_RETURN
::( endlocal
:: ( if "%_RETvar%" NEQ "" ( if NOT 01 == 1.0 (
:: set "%_RETvar%=%_RETval%"
:: ) else (
:: set %_RETvar=%_RETval
:: ))
::call :_echo_item_DEBUG_KEY "%__DEBUG%" "%__DEBUG_KEY%" "%__ME%" "%__MEfn%" "[ %__MEfn% :: done ]"
::)
endlocal & set %_RETvar%^=%_RETval%
goto :EOF
::
::
:$$echo_DEBUG ( [ TEXT ... ] )
:$echo_DEBUG ( [ TEXT ... ] )
:_echo_DEBUG ( [ TEXT ... ] )
:: used to help avoid the CMD/TCC BUG which causes a script breaking error if ()'s (more specifically, closed parens) are found within an IF() block
:: NOTE: __DEBUG is GLOBAL to this function (and MUST be, because the shift command doesn't change subsequent %* uses {so, there is no way to pass vars into the function with arbitrary following TEXT})
if NOT DEFINED __DEBUG ( goto :_echo_DEBUG_RETURN )
if NOT DEFINED __MEfn ( goto :_echo_DEBUG_NO_MEFN )
echo %__ME%:%__MEfn%: {{DEBUG}} %*
goto :_echo_DEBUG_RETURN
:_echo_DEBUG_NO_MEFN
echo %__ME%: {{DEBUG}} %*
goto :_echo_DEBUG_RETURN
:_echo_DEBUG_RETURN
goto :EOF
::
::
:$$echo_DEBUG_KEY ( [ TEXT ... ] )
:$echo_DEBUG_KEY ( [ TEXT ... ] )
:_echo_DEBUG_KEY ( [ TEXT ... ] )
:: used to help avoid the CMD/TCC BUG which causes a script breaking error if ()'s (more specifically, closed parens) are found within an IF() block
:: NOTE: __DEBUG and __DEBUG_KEY are GLOBAL to this function (and MUST be, because the shift command doesn't change subsequent %* uses {so, there is no way to pass vars into the function with arbitrary following TEXT})
if NOT DEFINED __DEBUG ( goto :_echo_DEBUG_KEY_RETURN )
if NOT "%__DEBUG%" == "%__DEBUG_KEY%" ( goto :_echo_DEBUG_KEY_RETURN )
if NOT DEFINED __MEfn ( goto :_echo_DEBUG_KEY_NO_MEFN )
echo %__ME%:%__MEfn%: {{DEBUG: %__DEBUG%}} %*
goto :_echo_DEBUG_KEY_RETURN
:_echo_DEBUG_KEY_NO_MEFN
echo %__ME%: {{DEBUG: %__DEBUG%}} %*
goto :_echo_DEBUG_KEY_RETURN
:_echo_DEBUG_KEY_RETURN
goto :EOF
::
::
:$$echo_item_DEBUG_KEY ( DEBUG DEBUG_KEY ME MEfn ITEM )
:$echo_item_DEBUG_KEY ( DEBUG DEBUG_KEY ME MEfn ITEM )
:_echo_item_DEBUG_KEY ( DEBUG DEBUG_KEY ME MEfn ITEM )
:: used to help avoid the CMD/TCC BUG which causes a script breaking error if ()'s (more specifically, closed parens) are found within an IF() block
:: NOTE: using a single echo'd item allows passage of other vars into the function
setlocal
set __DEBUG=%~1
set __DEBUG_KEY=%~2
set __ME=%~3
set __MEfn=%~4
set item=%~5
@call :_echo_DEBUG_KEY %item%
:_echo_item_DEBUG_KEY_RETURN
endlocal
goto :EOF
::
:::: [ end of __PREinitialize section (contains all required function dependencies) ]
::
:$$is_same_command ( ref_RETURN FILENAME1 FILENAME2 )
:$is_same_command ( ref_RETURN FILENAME1 FILENAME2 )
:_is_same_command ( ref_RETURN FILENAME1 FILENAME2 )
:: determine if FILENAME1 is the same as FILENAME2
:: RETURN == (BOOLEAN as undef/1) whether FILENAMEs are the same
setlocal
set "__DEBUG_KEY=@"
set "__MEfn=_is_same_command"
call :_echo_DEBUG_KEY [ %__MEfn% :: start ]
set "_RETval="
:: more than 3 ARGS implies multiple parts for FILENAME1 and/or FILENAME2 (therefore, NOT testable and defined as NOT the same)
call :_echo_DEBUG_KEY 4thARG?="%~4"
if NOT "%~4"=="" ( goto :_is_same_command_RETURN )
:: deal with NULL extensions (if both NULL, leave alone; otherwise, use the non-NULL extension for both)
set "_f2=%~2"
set "_f3=%~3"
if "%~x2"=="" ( call :_path_of_file_in_PATH _p2 "%_f2%" "%PATHEXT%" ) else ( call :_path_in_PATH _p2 "%_f2%" )
if "%~x3"=="" ( call :_path_of_file_in_PATH _p3 "%_f3%" "%PATHEXT%" ) else ( call :_path_in_PATH _p3 "%_f3%" )
call :_echo_DEBUG_KEY p2="%_p2%"
call :_echo_DEBUG_KEY p3="%_p3%"
if /i "%_p2%"=="%_p3%" ( set "_RETval=1" )
:_is_same_command_RETURN
call :_echo_DEBUG_KEY _RETvar="%~1"
call :_echo_DEBUG_KEY _RETval="%_RETval%"
endlocal & set "%~1=%_RETval%"
goto :EOF
::