Batch "macros" with arguments

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
dbenham
Expert
Posts: 2283
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Batch "macros" with arguments

#31 Post by dbenham » 10 Jun 2011 18:33

Ed Dyreen wrote:@for_entire_line works with the %%r token but I can't make it work with %%!

I know it has to do with the escapes that are changing behavior due to the !.
But I still can't solve it :(

Code: Select all

set $LF=^


:: Two empty lines are neccessary

set @MForEntireLine=for /f ^^^^^^^"eol^^^^=^^^^^^^%$LF%%$LF%^%$LF%%$LF%^^^%$LF%%$LF%^%$LF%%$LF%^^^^ delims^^^^=^^^^^^^"

set @MacroForEntireLine=%@MForEntireLine% %%^^^! in ( ";Please <<preserve this line>>" ) do echo %%^^^!

SetLocal EnableDelayedExpansion

%@MacroForEntireLine%

You lost me Ed. I'm not seeing a problem. Here is the output from your code above on my machine:

Code: Select all

>set $LF=


>set @MForEntireLine=for /f ^^^"eol^^=^^^

^

^^ delims^^=^^^"

>set @MacroForEntireLine=for /f ^"eol^=^

^ delims^=^" %^! in ( ";Please <<preserve this line>>" ) do echo %^!

>SetLocal EnableDelayedExpansion

>for /F "eol=
 delims=" %! in (";Please <<preserve this line>>") do echo %!

>echo ;Please <<preserve this line>>
;Please <<preserve this line>>


It also works without escaping the %%!:

Code: Select all


set $LF=^


:: Two empty lines are neccessary

set @MForEntireLine=for /f ^^^^^^^"eol^^^^=^^^^^^^%$LF%%$LF%^%$LF%%$LF%^^^%$LF%%$LF%^%$LF%%$LF%^^^^ delims^^^^=^^^^^^^"

set @MacroForEntireLine=%@MForEntireLine% %%! in ( ";Please <<preserve this line>>" ) do echo %%!

SetLocal EnableDelayedExpansion

%@MacroForEntireLine%


Results:

Code: Select all

>set $LF=


>set @MForEntireLine=for /f ^^^"eol^^=^^^

^

^^ delims^^=^^^"

>set @MacroForEntireLine=for /f ^"eol^=^

^ delims^=^" %! in ( ";Please <<preserve this line>>" ) do echo %!

>SetLocal EnableDelayedExpansion

>for /F "eol=
 delims=" %! in (";Please <<preserve this line>>") do echo %!

>echo ;Please <<preserve this line>>
;Please <<preserve this line>>

Dave

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#32 Post by Ed Dyreen » 11 Jun 2011 08:10

'
I probably made a syntax error, yet posted an example without this error :oops:

dbenham
Expert
Posts: 2283
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Batch "macros" with arguments - Major Update

#33 Post by dbenham » 23 Jun 2011 16:47

OK, I've finally got a significant update to this project.
Sorry this is so long, but there is a lot to cover.

1) New Developments

New strategy for "calling" a macro within a macro:I have abandoned the ${calledMacroName} syntax and the relatively slow drefMacro function. I am now using delayed expansion to very quickly include a macro within a macro at definition time. I now realize it is not so difficult to escape ! and ^ as needed. (Ed - Sorry I was so pig headed before. I think you have been on the right path in this regard all along). My concern about having the macro definition persist after the batch file terminates is handled by the next developement.

New macros to return any string across the ENDLOCAL border: See the macroLib_Return.bat file later in this post. These macros are useful for both macro and function development. They not only allow macros to return any value across the ENDLOCAL border, they also allow us to preserve the macro definitions themselves across the ENDLOCAL border!

For examples on how to use this library for function development, see New Function Template - return ANY string safely and easily. Note - the macros have changed some since my original post there.

For examples on how to use this library for macro development, see in this post:
  • macroLib_Strings.bat : macro.ToUpper and macro.AnyToUpper for examples of using macro.Rtn1 and macro.AnyRtn1
  • macroLib_Swap.bat : macro.Swap and macro.AnySwap for examples of using macro.RtnN and macro.AnyRtnN

Modified callMacro.bat - this "command" allows macros to be called under almost any circumstance at a cost of reduced speed, (but still faster than calling a function with the same functionality). See the code posted later in this post for more information on its usage. The file now includes a call to %macro_EndAnyRtn% to support macros that use either macro.AnyRtn1 or macro.AnyRtnN. It also now has embedded help.

Formalized naming convention and help system:

macro.MacroName - Specifies a macro that requires arguments.

macro\args.MacroName - Contains calling syntax (argument list) for macro.MacroName

macro\help.MacroName - Contains detailed help on usage of macro.MacroName

macro_SimpleMacro - Specifies a simple macro that does not require arguments.

macro\args_SimpleMacro - Displays as empty but contains a space: Used to inform user of availability of a simple macro that does not require arguments.

macro\help_SimpleMacro - Contains detailed help on usage of a simple macro named macro_SimpleMacro.

macro\load.LibraryName - Allways set to 1: Used to indicate that the library has been loaded into memory.

Help commands using DOSKEY macros - See macroLib_Base.bat for full documentation
margs - Convenient access to a list of available macros and their arguments.
mhelp - Convenient access to detailed macro usage information.
mload - Convenient access to the list of macro libraries that have been loaded.

Adopted use of ECHO( instead of ECHO:
- defined echo=echo( in macroLib_base.bat.
- replaced all instances of echo: with %echo% in all macro libraries.


2) General Macro Development Design considerations

Need for local argument naming convention when passing multiple variables by reference.Macros (and functions) typically have local variables that are supposed to have no impact on the caller. But when passing multiple variables by reference we can get in trouble if the variable names passed in happen to match the names of local variables.

I have a convention that all reference argument values are read into local variables that are prefixed with the macro name like so: macro.Name.LocalVar. Once all of the argument values have been transferred, the macro is free to define any local variable using any name. This strategy eliminates the risk of name collision problems as long as the caller never passes in a variable that is prefixed with the called macro name.

Macros make heavy use of delayed expansion: We need the ability to set a value within a macro and then subsequently read the value in the same macro. Since the macro is all within one statement block we cannot use %var% notation. We don't want to use CALL %%var%% notation because it is slow and a major purpose of defining a macro in the first place is for speed. This means we almost always reference variables using delayed expansion as !var!. This leads to two constructs that are not normally used for function development but are often critical to macro development.

Parametized substring (or parametized search and replace):
- in a function we use SET variable=!variable:~%start%,%length%!
- in a macro we use FOR /F "tokens=1,2" %%A IN ("!start! !length!") DO set variable=!variable:~%%A,%%B!

Passing Numbers and simple strings across the ENDLOCAL border:
- in a function we use (ENDLOCAL & SET "var=%val%")
- in a macro we use FOR /F %macro_ForEntireLine% %%v ("!val!") DO (ENDLOCAL & SET "var=%%v")


3) Open Issues

Dealing with multiple optional arguments - In functions we can shift varying numbers of leading option arguments so that required arguments are always in standard positions after options have been processed. We don't have any correlary to the SHIFT command for macro options.

:idea: I am toying with the idea of treating the macro as an object and storing options as macro attributes - a limited adoption of what Ed Dyreen has been doing. The options would be set prior to the call using syntax like SET macro.MacroName.OptionName=value. The options could possibly reset automatically to default values upon macro completion.

Passing string literals as arguments - There is no simple way to quote or escape argument delimiters so that they may be included in an argument value. We can define different macro_Call variants that use different delimiters, but there is not a generic solution that can receive any combination of characters. For this reason all my macros have been restricted to passing unconstrained string values by reference.

:idea: We could use the object attribute strategy here as well, but I don't really like it. I have a vague idea to pass all arguments as one string and then process the arguments with a dedicated "load arguments" macro. This macro would use a simple FOR loop to parse the arguments, but it would need special code to preserve * and ? characters. I'm not sure when (or if) I will get around to testing this.


4) The Code :!: (finally :roll:)

I've broken up the code into multiple libraries based on functionality:
macroLib_Base.bat - basic definitions needed by every library, plus new help macros
macroLib_Return.bat - new macros for returning values across ENDLOCAL barrier
macroLib_String.bat - macros for basic string operations, including new macros to demo macro.Rtn1 and macro.AnyRtn1
macroLib_Swap.bat - new macros of limited functionality to demo usage of macro.RtnN and macro.AnyRtnN
macroLib_Num.bat - macros dealing with numbers (Random and Num2Hex)
macroLib_Time.bat - macros for dealing with time, useful for timing code
callMacro.bat - command used to call macros when %macro_call% cannot be used

macroLib_Base.bat

Code: Select all

@echo off
:: File = macroLib_Base.bat
:: Dependencies: <none>
:: This batch file will fail if called while delayed expansion is enabled.
::
:: This library defines basic macros and variables that are used by all other
:: macro libraries.
::
:: The following DOSKEY macros are installed to provide help on all currently
:: loaded macro libraries. These DOSKEY macros are only available from the
:: command line. They cannot be used within a batch file.
::
::   margs [MacroNameFilter]
::
::     List all macros along with their arguments.
::
::     If the MacroNameFilter is specified then only list macros whose name
::     begins with macro.MacroNameFilter or macro_MacroNameFilter.
::
::   mhelp [MacroNameFilter]
::
::     Display help for all macros.
::
::     If the MacroNameFilter is specified then only display help for macros
::     whose name begins with macro.MacroNameFilter or macro_MacroNameFilter.
::
::  mload [LibraryNameFilter]
::
::    Displays a list of all macro libraries that are currently loaded.
::
::    If the LibraryNameFilter is specified then only display loaded libraries
::    whose name begins with LibraryNameFilter.
::
:: The library is designed to be installed in a directory in your PATH.
:: Any batch file that requires it can include it by simply placing the
:: following line of code at the top before any SETLOCAL:
::
::   IF NOT DEFINED macro\load.macroLib_Base CALL macroLib_Base
::
:: In this way the library becomes resident in your command shell environment
:: where it is available to any batch file that may need it. The IF condition
:: prevents unneccessary reloads of the same library.
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

::define a Carriage Return string, only useable as !CR!
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"

::define a Line Feed (newline) string (normally only used as !LF!)
set LF=^


::Above 2 blank lines are required - do not remove

::define a Line Feed string that can be used as %xLF%
set ^"xLF=^^^%LF%%LF%^%LF%%LF%"

::define a newline with line continuation
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

::define a version of ECHO that supports displaying a blank line
set "echo=echo("

set "esc1=^"
set "esc2=^^^"
set "esc3=^^^^^^^"
set "esc4=^^^^^^^^^^^^^^^"

set "macro\args_ForEntireLine= "
set macro\help_ForEntireLine=%\n%
  %\n%
  A simple macro to be used with the FOR /F command to disable the EOL and%\n%
  DELIMS options, thus enabling preservation of the entire line regardless of%\n%
  what the leading character is.%\n%
  %\n%
  There are two versions of this macro:%\n%
  %\n%
  ForEntireLine - For normal use within a batch file or on the command line.%\n%
    If this macro is included in a macro definition then it must be referenced%\n%
    as !ForEntireLine! and %%macro_BeginDef%% and %%macro.EndDef%% must be used%\n%
    before and after the macro definition.%\n%
  %\n%
  macro_ForEntireLine - This version is used exclusively within a macro%\n%
    definition as %%macro_ForEntireLine%% when macro_BeginDef and macro.EndDef%\n%
    are not needed.%\n%
  %\n%
  Sample use from the command line:%\n%
  %\n%
    for /f %%ForEntireLine%% %%a in (";Preserve this entire line") do @echo %%a%xLF%
set macro_ForEntireLine=^^^^^^^"eol^^^^=^^^^^^^%LF%%LF%^%LF%%LF%^^^%LF%%LF%^%LF%%LF%^^^^ delims^^^^=^^^^^^^"
set ForEntireLine=%macro_ForEntireLine%

set "macro\args_Call= "
set macro\help_Call=%\n%
  %\n%
  A simple macro used to call macros with arguments from within a batch file.%\n%
  This is the fastest way to call a macro from within a batch file.%\n%
  %\n%
  Usage:%\n%
  %\n%
    %%macro_Call%% ("arg1 arg2 arg3...") %%macro.macroName%%%\n%
  %\n%
  To call a macro from the command line use the callMacro.bat command instead.%\n%
  Note that the macro. prefix is excluded from the macroName when using the%\n%
  callMacro.bat command.%\n%
  %\n%
    callMacro macroName arg1 arg2 arg3...%\n%
  %\n%
  The callMacro.bat command can also be used within a batch as follows:%\n%
  %\n%
    call callMacro macroName arg1 arg2 arg3...%\n%
  %\n%
  There is a small performance penalty when calling a macro this way, but it is%\n%
  usually still faster than calling a function with the same functionality.%\n%
  The callMacro.bat command is especially useful within a batch file under any%\n%
  of the following circumstances:%\n%
  %\n%
   - Performance is not overly important and the callMacro command is more%\n%
     natural than the %%macro_Call%% syntax.%\n%
  %\n%
   - The macro must be called within a code block and the code block is%\n%
     exceeding the ~8K limit.%\n%
  %\n%
   - An "Any" macro must be called within a code block. This is possible%\n%
     because the callMacro.bat command implicitly calls macro_EndAnyRtn%\n%
     outside of the calling code block.%\n%
  %\n%
   - The macro must be called within a macro definition and the macro%\n%
     definition is exceeding the ~8K limit.%xLF%
set macro_Call=for /f "tokens=1-26" %%a in


::-----------------------------------------------------
:: DEFINE HELP MACROS (true DOSKEY macros)

doskey margs=echo off$T%echo%$T2$Gnul set macro\args.$1$T2$Gnul set macro\args_$1$Techo on
doskey mhelp=echo off$T%echo%$T2$Gnul set macro\help.$1$T2$Gnul set macro\help_$1$Techo on
doskey mload=echo off$T%echo%$Tset macro\load.$1$Techo on

::----------------------------------------------------
:: Mark that this library has been loaded.

set macro\load.%~n0=1


macroLib_Return.bat

Code: Select all

@echo off
:: File = macroLib_Return.bat
:: Dependencies: macroLib_Base.bat
:: This batch file will fail if called while delayed expansion is enabled.
::
:: This library defines macros that enable the return of any string value(s)
:: across the ENDLOCAL border. These macros are very useful for creating
:: other macros and functions. Routines built with these macros are safe to
:: call even when delayed expansion is enabled.
::
:: The library is designed to be installed in a directory in your PATH.
:: Any batch file that requires it can include it by simply placing the
:: following line of code at the top before any SETLOCAL:
::
::   IF NOT DEFINED macro\load.MacroLib_Return CALL macroLib_Return
::
:: In this way the library becomes resident in your command shell environment
:: where it is available to any batch file that may need it. The IF condition
:: prevents unneccessary reloads of the same library.
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

:: Conditionally load dependencies (normally residing somewhere in the PATH)
if not defined macro\load.macroLib_Base call macroLib_Base

set "macro\args_InitRtn= "
set macro\help_InitRtn=%\n%
  %\n%
  A simple macro used at the top of a macro definition to prepare%\n%
  for a return by any of the Rtn macros.%\n%
  %\n%
  See any of the following for more information:%\n%
    macro.Rtn1%\n%
    macro.RtnN%\n%
    macro.AnyRtn1%\n%
    macro.AnyRtnN%xLF%
set macro_InitRtn=setlocal^&set "macro_NotDelayed=!"^&set "macro_inFcn="

set "macro\args_InitFcnRtn= "
set macro\help_InitFcnRtn=%\n%
  %\n%
  A simple macro used at the top of a function definition to prepare%\n%
  for a return by any of the Rtn macros.%\n%
  %\n%
  See any of the following for more information:%\n%
    macro.Rtn1%\n%
    macro.RtnN%\n%
    macro.AnyRtn1%\n%
    macro.AnyRtnN%xLF%
set macro_InitFcnRtn=setlocal^&set "macro_NotDelayed=!"^&set "macro_inFcn=true"

set "macro\args_EndAnyRtn= "
set macro\help_EndAnyRtn=%\n%
  %\n%
  A temporary simple macro that must immediately follow calls to any%\n%
  of the following (or any macro that uses the following):%\n%
    macro.AnyRtn1%\n%
    macro.AnyRtnN%\n%
    macro.EndDef%\n%
  %\n%
  The macro is not defined until one of the above macros is called,%\n%
  and macro_EndAnyRtn undefines itself once it is called.%\n%
  See any of the above macros for more information.%xLF%

:: A simple macro for Rtn macro internal use
set "macro_EndAnyRtnPrefix=for /f "tokens=1-3" %%1 in ("!replace!") do for %%4 in ("!LF!") do "

set macro\args.AnyRtn1=  ErrLvl  EndLocalCnt  ValueVar  [RtnVar]
set macro\help.AnyRtn1=  ErrLvl  EndLocalCnt  ValueVar  [RtnVar]%\n%
  %\n%
  Returns the contents of variable ValueVar across the ENDLOCAL border at the%\n%
  end of a function or macro. The number of ENDLOCAL executions is controlled%\n%
  by the EndLocalCnt which should match the number of times SETLOCAL was used%\n%
  within the calling function/macro.%\n%
  %\n%
  The return value is stored in RtnVar%\n%
  or the value is echoed if RtnVar is not specified.%\n%
  The ERRORLEVEL is set to ErrLvl%\n%
  %\n%
  Numeric values ErrLvl and EndLocalCnt may be passed using any expression%\n%
  supported by SET /A.%\n%
  %\n%
  This macro can return a string containing any combination of characters%\n%
  supported by DOS, and the macro works regardless whether the target return%\n%
  environment has enabled or disabled delayed expansion.%\n%
  %\n%
  In order for a function to use AnyRtn1, the calling function must start with%\n%
  %%macro_InitFcnRtn%% at the top, and %%macro_EndAnyRtn%% must immediately follow%\n%
  the call to %%macro.AnyRtn1%%.%\n%
   %\n%
    :func inputVar [RtnVar]%\n%
      %%macro_InitFcnRtn%%%\n%
      setlocal%\n%
      {do whatever you need to do}%\n%
      set rtnValue={your return value}%\n%
      %%macro_Call%% ("errorlevel 1 rtnValue %%~2") %%macro.AnyRtn1%%%\n%
      %%macro_EndAnyRtn%%%\n%
    exit /b%\n%
  %\n%
  In order for a macro to use AnyRtn1, the calling macro must start with%\n%
  %%macro_InitRtn%% at the top. In the macro template below, assume the %%%%c%\n%
  argument holds the name of the variable to receive the result.%\n%
  %\n%
    set macro.Name= do(%%\n%%%\n%
      !macro_InitRtn!%%\n%%%\n%
      setlocal%%\n%%%\n%
      {do whatever you need to do}%\n%
      set rtnValue={your return value}%%\n%%%\n%
      !macro_Call! ("errorlevel 1 rtnValue %%%%~c") !macro.AnyRtn1!%%\n%%%\n%
    )%\n%
  %\n%
  Since this is a case of a macro "calling" a macro, the macro definition must%\n%
  be preceded by %%macro_BeginDef%% and followed by a call to %%macro.EndDef%%%\n%
  and %%macro_EndAnyRtn%%. See macro.EndDef for more information.%\n%
   %\n%
  Any call to a macro that uses AnyRtn1 must be followed by %%macro_EndAnyRtn%%.%\n%
  The macro call and %%macro_EndAnyRtn%% must not share a common statement block.%\n%
  This limitation can be overcome by calling the macro through the callMacro%\n%
  function which implicitly calls %%macro_EndAnyRtn%%%xLF%
set macro.AnyRtn1=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.AnyRtn1.ErrLvl=(%%~a), macro.AnyRtn1.EndLocalCnt=(%%~b)"%\n%
  set "rtn=!%%~c!"%\n%
  set ^"replace=%% ^"^"^" !CR!!CR!^"%\n%
  set "macro_EndAnyRtn=!macro_EndAnyRtnPrefix!endlocal&endlocal"%\n%
  for /l %%N in (1,1,!macro.AnyRtn1.EndLocalCnt!) do set "macro_EndAnyRtn=!macro_EndAnyRtn!&endlocal"%\n%
  if "%%~d" equ "" (%echo%!rtn!) else (%\n%
    if defined rtn (%\n%
      set "rtn=!rtn:%%=%%~1!"%\n%
      set ^"rtn=!rtn:^"=%%~2!^"%\n%
      if defined CR for %%A in ("!CR!") do set "rtn=!rtn:%%~A=%%~3!"%\n%
      for %%A in ("!LF!") do set "rtn=!rtn:%%~A=%%~4!"%\n%
      if not defined macro_NotDelayed (%\n%
        set "rtn=!rtn:^=^^!"%\n%
        call set "rtn=%%^rtn:^!=""^!%%" ! %\n%
        set "rtn=!rtn:""=^!"%\n%
      )%\n%
    )%\n%
    set "macro_EndAnyRtn=!macro_EndAnyRtn!&set "%%~d=!rtn!" ^!"%\n%
  )%\n%
  if defined macro_inFcn (%\n%
      set "macro_EndAnyRtn=!macro_EndAnyRtn!&exit /b !macro.AnyRtn1.ErrLvl!"%\n%
  ) else if "!macro.AnyRtn1.ErrLvl!" == "1" (%\n%
      set "macro_EndAnyRtn=!macro_EndAnyRtn!&(2>nul set =)"%\n%
  ) else if "!macro.AnyRtn1.ErrLvl!" neq "0" (%\n%
      set "macro_EndAnyRtn=!macro_EndAnyRtn!&cmd /d /c exit !macro.AnyRtn1.ErrLvl!"%\n%
  )%\n%
)

set macro\args.AnyRtnN=  ErrLvl  EndLocalCnt  ValVar1:RtnVar1[,ValVar2:RtnVar2]...
set macro\help.AnyRtnN=  ErrLvl  EndLocalCnt  ValVar1:RtnVar1[,ValVar2:RtnVar2]...%\n%
  %\n%
  Returns the contents of multiple variables across the ENDLOCAL border at%\n%
  the end of a function or macro. The number of ENDLOCAL executions is%\n%
  controlled by the EndLocalCnt which should match the number of times%\n%
  SETLOCAL was used within the calling function/macro. The errorlevel is%\n%
  set to ErrLvl.%\n%
  %\n%
  A pair of variable names must be specified for each output value - the%\n%
  name of the variable containing the value followed by a colon followed%\n%
  by the name of the variable that is to receive the value. Multiple pairs%\n%
  are delimited by commas. The list of outputs cannot contain any spaces,%\n%
  and the variable names cannot contain asterisk ^(*^), question mark ^(?^),%\n%
  colon ^(:^) or comma ^(,^).%\n%
  %\n%
  Numeric values ErrLvl and EndLocalCnt may be passed using any expression%\n%
  supported by SET /A.%\n%
  %\n%
  This macro can return strings containing any combination of characters%\n%
  supported by DOS, and the macro works regardless whether the target return%\n%
  environment has enabled or disabled delayed expansion.%\n%
  %\n%
  In order for a function to use AnyRtnN, the calling function must start with%\n%
  %%macro_InitFcnRtn%% at the top, and %%macro_EndAnyRtn%% must immediately follow%\n%
  the call to %%macro.AnyRtn1%%.%\n%
   %\n%
    :func inputVar RtnVar1 RtnVar2%\n%
      %%macro_InitFcnRtn%%%\n%
      setlocal%\n%
      {do whatever you need to do}%\n%
      set rtnVal1={your first return value}%\n%
      set rtnVal2={your second return value}%\n%
      %%macro_Call%% ("errorlevel 1 rtnVal1:%%~2,rtnVal2:%%~3") %%macro.AnyRtnN%%%\n%
      %%macro_EndAnyRtn%%%\n%
    exit /b%\n%
   %\n%
  In order for a macro to use AnyRtnN, the calling macro must start with%\n%
  %%macro_InitRtn%% at the top. In the macro template below, assume the %%%%c%\n%
  and %%%%~d arguments hold the name of the variables to receive the results.%\n%
  %\n%
    set macro.Name= do(%%\n%%%\n%
      !macro_InitRtn!%%\n%%%\n%
      setlocal%%\n%%%\n%
      {do whatever you need to do}%\n%
      set rtnVal1={your first return value}%%\n%%%\n%
      set rtnVal2={your second return value}%%\n%%%\n%
      !macro_Call! ("errorlevel 1 rtnVal1:%%%%~c,rtnVal2:%%%%~d") !macro.AnyRtnN!%%\n%%%\n%
    )%\n%
  %\n%
  Since this is a case of a macro "calling" a macro, the macro definition must%\n%
  be preceded by %%macro_BeginDef%% and followed by a call to %%macro.EndDef%%%\n%
  and %%macro_EndAnyRtn%%. See macro.EndDef for more information.%\n%
   %\n%
  Any call to a macro that uses AnyRtnN must be followed by %%macro_EndAnyRtn%%.%\n%
  The macro call and %%macro_EndAnyRtn%% must not share a common statement block.%\n%
  This limitation can be overcome by calling the macro through the callMacro%\n%
  function which implicitly calls %%macro_EndAnyRtn%%%xLF%
set macro.AnyRtnN=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.AnyRtn1.ErrLvl=(%%~a), macro.AnyRtn1.EndLocalCnt=(%%~b)"%\n%
  set "macro_EndAnyRtn=!macro_EndAnyRtnPrefix!endlocal&endlocal"%\n%
  for /l %%N in (1,1,!macro.AnyRtn1.EndLocalCnt!) do set "macro_EndAnyRtn=!macro_EndAnyRtn!&endlocal"%\n%
  for %%c in (%%~c) do for /f "tokens=1,2 eol=: delims=:" %%c in ("%%c") do (%\n%
    set "macro.AnyRtnN.rtn=!%%~c!"%\n%
    if defined macro.AnyRtnN.rtn (%\n%
      set "macro.AnyRtnN.rtn=!macro.AnyRtnN.rtn:%%=%%~1!"%\n%
      set ^"macro.AnyRtnN.rtn=!macro.AnyRtnN.rtn:^"=%%~2!^"%\n%
      if defined CR for %%A in ("!CR!") do set "macro.AnyRtnN.rtn=!macro.AnyRtnN.rtn:%%~A=%%~3!"%\n%
      for %%A in ("!LF!") do set "macro.AnyRtnN.rtn=!macro.AnyRtnN.rtn:%%~A=%%~4!"%\n%
      if not defined macro_NotDelayed (%\n%
        set "macro.AnyRtnN.rtn=!macro.AnyRtnN.rtn:^=^^!"%\n%
        call set "macro.AnyRtnN.rtn=%%^macro.AnyRtnN.rtn:^!=""^!%%" ! %\n%
        set "macro.AnyRtnN.rtn=!macro.AnyRtnN.rtn:""=^!"%\n%
      )%\n%
    )%\n%
    set "macro_EndAnyRtn=!macro_EndAnyRtn!&set "%%~d=!macro.AnyRtnN.rtn!" ^!"%\n%
  )%\n%
  if defined macro_inFcn (%\n%
      set "macro_EndAnyRtn=!macro_EndAnyRtn!&exit /b !macro.AnyRtn1.ErrLvl!"%\n%
  ) else if "!macro.AnyRtn1.ErrLvl!" == "1" (%\n%
      set "macro_EndAnyRtn=!macro_EndAnyRtn!&(2>nul set =)"%\n%
  ) else if "!macro.AnyRtn1.ErrLvl!" neq "0" (%\n%
      set "macro_EndAnyRtn=!macro_EndAnyRtn!&cmd /d /c exit !macro.AnyRtn1.ErrLvl!"%\n%
  )%\n%
  set ^"replace=%% ^"^"^" !CR!!CR!^"%\n%
)

set macro\args.Rtn1=  ErrLvl  EndLocalCnt  ValueVar  [RtnVar]
set macro\help.Rtn1=  ErrLvl  EndLocalCnt  ValueVar  [RtnVar]%\n%
  %\n%
  Returns the contents of variable ValueVar across the ENDLOCAL border at the%\n%
  end of a function or macro. The number of ENDLOCAL executions is controlled%\n%
  by the EndLocalCnt which should match the number of times SETLOCAL was used%\n%
  within the calling function/macro.%\n%
  %\n%
  The return value is stored in RtnVar%\n%
  or the value is echoed if RtnVar is not specified.%\n%
  The ERRORLEVEL is set to ErrLvl%\n%
  %\n%
  Numeric values ErrLvl and EndLocalCnt may be passed using any expression%\n%
  supported by SET /A.%\n%
  %\n%
  This macro can return a string containing any combination of characters%\n%
  supported by DOS, except for 0x0A ^<Line Feed^> or 0x0D ^<Carriage Return^>.%\n%
  The macro works regardless whether the target return environment has%\n%
  enabled or disabled delayed expansion.%\n%
  %\n%
  In order for a function to use Rtn1, the calling function must start with%\n%
  %%macro_InitFcnRtn%% at the top.%\n%
  %\n%
    :func inputVar [RtnVar]%\n%
      %%macro_InitFcnRtn%%%\n%
      setlocal%\n%
      {do whatever you need to do}%\n%
      set rtnValue={your return value}%\n%
      %%macro_Call%% ("errorlevel 1 rtnValue %%~2") %%macro.Rtn1%%%\n%
    exit /b%\n%
  %\n%
  In order for a macro to use Rtn1, the calling macro must start with%\n%
  %%macro_InitRtn%% at the top. In the macro template below, assume the %%%%c%\n%
  argument holds the name of the variable to receive the result.%\n%
  %\n%
    set macro.Name=do (%%\n%%%\n%
      !macro_InitRtn!%%\n%%%\n%
      setlocal%%\n%%%\n%
      {do whatever you need to do}%\n%
      set rtnValue={your return value}%%\n%%%\n%
      !macro_Call! ("errorlevel 1 rtnValue %%%%~c") !macro.Rtn1!%%\n%%%\n%
    )%\n%
  %\n%
  Since this is a case of a macro "calling" a macro, the macro definition%\n%
  must be preceded by %%macro_BeginDef%% and followed by a call to %%macro.EndDef%%%\n%
  and %%macro_EndAnyRtn%%. See macro.EndDef for more information.%xLF%
set macro.Rtn1=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.Rtn1.ErrLvl=(%%~a), macro.Rtn1.EndLocalCnt=(%%~b)+2"%\n%
  set "rtn=!%%~c!"%\n%
  if defined macro_inFcn (%\n%
      set "errCmd=exit /b !macro.Rtn1.ErrLvl!"%\n%
  ) else if "!macro.Rtn1.ErrLvl!" == "0" (%\n%
      set "errCmd=rem"%\n%
  ) else if "!macro.Rtn1.ErrLvl!" == "1" (%\n%
      set "errCmd=set ="%\n%
  ) else (%\n%
      set "errCmd=cmd /d /c exit !macro.Rtn1.ErrLvl!"%\n%
  )%\n%
  if "%%~d" equ "" (%echo%!rtn!) else if defined rtn if not defined macro_NotDelayed (%\n%
    set "rtn=!rtn:^=^^!"%\n%
    set "rtn=!rtn:"=""Q!^"%\n%
    call set "rtn=%%^rtn:^!=""E^!%%" ! %\n%
    set "rtn=!rtn:""E=^!"%\n%
    set "rtn=!rtn:""Q="!^"%\n%
  )%\n%
  for /f "delims=" %%e in ("!errCmd!") do for /f %macro_ForEntireLine% %%v in (""!rtn!"") do (%\n%
    for /l %%n in (1,1,!macro.Rtn1.EndLocalCnt!) do endlocal%\n%
    if "%%~d" neq "" set "%%~d=%%~v" !%\n%
    %%e 2^>nul%\n%
  )%\n%
)

set macro\args.RtnN=  ErrLvl  EndLocalCnt  ValVar1:RtnVar1[,ValVar2:RtnVar2]...
set macro\help.RtnN=  ErrLvl  EndLocalCnt  ValVar1:RtnVar1[,ValVar2:RtnVar2]...%\n%
  %\n%
  Returns the contents of multiple variables across the ENDLOCAL border at%\n%
  the end of a function or macro. The number of ENDLOCAL executions is%\n%
  controlled by the EndLocalCnt which should match the number of times%\n%
  SETLOCAL was used within the calling function/macro. The errorlevel is%\n%
  set to ErrLvl.%\n%
  %\n%
  A pair of variable names must be specified for each output value - the%\n%
  name of the variable containing the value followed by a colon followed%\n%
  by the name of the variable that is to receive the value. Multiple pairs%\n%
  are delimited by commas. The list of outputs cannot contain any spaces,%\n%
  and the variable names cannot contain asterisk ^(*^), question mark ^(?^),%\n%
  colon ^(:^) or comma ^(,^).%\n%
  %\n%
  Numeric values ErrLvl and EndLocalCnt may be passed using any expression%\n%
  supported by SET /A.%\n%
  %\n%
  This macro can return strings containing any combination of characters%\n%
  supported by DOS, except for 0x0A ^<Line Feed^> or 0x0D ^<Carriage Return^>.%\n%
  The macro works regardless whether the target return environment has%\n%
  enabled or disabled delayed expansion.%\n%
  %\n%
  In order for a function to use RtnN, the calling function must start with%\n%
  %%macro_InitFcnRtn%% at the top.%\n%
  %\n%
    :func inputVar RtnVar1 RtnVar2%\n%
      %%macro_InitFcnRtn%%%\n%
      setlocal%\n%
      {do whatever you need to do}%\n%
      set rtnVal1={your first return value}%\n%
      set rtnVal2={your second return value}%\n%
      %%macro_Call%% ("errorlevel 1 rtnVal1:%%~2,rtnVal2:%%~3") %%macro.RtnN%%%\n%
    exit /b%\n%
  %\n%
  In order for a macro to use RtnN, the calling macro must start with%\n%
  %%macro_InitRtn%% at the top. In the macro template below, assume the %%%%c%\n%
  and %%%%~d arguments hold the name of the variables to receive the results.%\n%
  %\n%
    set macro.Name=do (%%\n%%%\n%
      !macro_InitRtn!%%\n%%%\n%
      setlocal%%\n%%%\n%
      {do whatever you need to do}%\n%
      set rtnVal1={your first return value}%%\n%%%\n%
      set rtnVal2={your second return value}%%\n%%%\n%
      !macro_Call! ("errorlevel 1 rtnVal1:%%%%~c,rtnVal2:%%%%~d") !macro.RtnN!%%\n%%%\n%
    )%\n%
  %\n%
  Since this is a case of a macro "calling" a macro, the macro definition%\n%
  must be preceded by %%macro_BeginDef%% and followed by a call to %%macro.EndDef%%%\n%
  and %%macro_EndAnyRtn%%. See macro.EndDef for more information.%xLF%
set macro.RtnN=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.RtnN.ErrLvl=(%%~a), macro.RtnN.EndLocalCnt=(%%~b)+2"%\n%
  set "macro.RtnN.cmd="%\n%
  for /l %%n in (1,1,!macro.RtnN.EndLocalCnt!) do set "macro.RtnN.cmd=!macro.RtnN.cmd!endlocal!lf!"%\n%
  for %%c in (%%~c) do for /f "tokens=1,2 eol=: delims=:" %%c in ("%%c") do (%\n%
    set "macro.RtnN.rtn=!%%~c!"%\n%
    if defined macro.RtnN.rtn if not defined macro_NotDelayed (%\n%
      set "macro.RtnN.rtn=!macro.RtnN.rtn:^=^^!"%\n%
      set "macro.RtnN.rtn=!macro.RtnN.rtn:"=""Q!^"%\n%
      call set "macro.RtnN.rtn=%%^macro.RtnN.rtn:^!=""E^!%%" ! %\n%
      set "macro.RtnN.rtn=!macro.RtnN.rtn:""E=^!"%\n%
      set "macro.RtnN.rtn=!macro.RtnN.rtn:""Q="!^"%\n%
    )%\n%
    set "macro.RtnN.cmd=!macro.RtnN.cmd!set "%%~d=!macro.RtnN.rtn!"^!!lf!"%\n%
  )%\n%
  if defined macro_inFcn (%\n%
      set "macro.RtnN.cmd=!macro.RtnN.cmd!exit /b !macro.RtnN.ErrLvl!!lf!"%\n%
  ) else if "!macro.RtnN.ErrLvl!" == "0" (%\n%
      rem errorlevel set to 0 by default%\n%
  ) else (%\n%
      set "macro.RtnN.cmd=!macro.RtnN.cmd!cmd /d /c exit !macro.RtnN.ErrLvl!!lf!"%\n%
  )%\n%
  for /f "delims=" %%v in ("!macro.RtnN.cmd!") do %%v%\n%
)

set "macro\args_BeginDef= "
set macro\help_BeginDef=%\n%
  %\n%
  Define a simple macro to be used before defining a macro that includes%\n%
  another macro in its definition. See macro.EndDef for more information%xLF%
set macro_BeginDef=(%\n%
  setlocal enableDelayedExpansion%\n%
  set "macro_NotDelayed=1"%\n%
  set "macro_inFcn="%\n%
)

set macro\args.EndDef=  MacroName
set macro\help.EndDef=  MacroName%\n%
  %\n%
  One of three macros needed to define a macro that "calls" another macro%\n%
  in its definition. %%macro_BeginDef%% is used before the macro definition%\n%
  and %%macro.EndDef%% and %%macro_EndAnyRtn%% are used after the macro definition.%\n%
  %\n%
  Macro calling macro template:%\n%
  %\n%
    %%macro_BeginDef%%%\n%
    set macro.Name=do (%%\n%%%\n%
      {macro definition goes here}%\n%
    )%\n%
    %%macro_Call%% ("macro.Name") %%macro.EndDef%%%\n%
    %%macro_EndAnyRtn%%%\n%
  %\n%
  Calls to a macro within the definition should use delayed expansion:%\n%
  %\n%
    !macro_SimpleMacroName!%%\n%%%\n%
    !macro_Call! ("{macro aruments}") !macro.MacroName!%%\n%%%\n%
  %\n%
  Carets and exclamation points not involved with a macro call must be%\n%
  escaped. Escape once if within quotes, twice if not within quotes:%\n%
  %\n%
    set "caret=^^"%%\n%%%\n%
    set "exclamation=^!"%%\n%%%\n%
    set caret=^^^^^^^^%%\n%%%\n%
    set exclamation=^^^^^^!%%\n%%%xLF%
%macro_BeginDef%
set macro.EndDef=do !macro_call! ("errorlevel 0 %%a %%a") !macro.AnyRtn1!
%macro_Call% ("errorlevel 0 macro.EndDef macro.EndDef") %macro.AnyRtn1%
%macro_EndAnyRtn%

set macro\args.SetErr=  ErrLvl
set macro\help.SetErr=  ErrLvl%\n%
  %\n%
  Sets ERRORLEVEL to the integral value ErrLvl%xLF%
set macro.SetErr=do (%\n%
  if "%%~a" equ "0" (%\n%
    verify ^>nul%\n%
  ) else if "%%~a" equ "1" (%\n%
    set = 2^>nul%\n%
  ) else (%\n%
    cmd /d /c exit %%~a%\n%
  )%\n%
)

::----------------------------------------------------
:: Mark that this library has been loaded.

set macro\load.%~n0=1


macroLib_String.bat

Code: Select all

@echo off
:: File = macroLib_String.bat
:: Dependencies: macroLib_Base.bat, macroLib_Return.bat
:: This batch file will fail if called while delayed expansion is enabled.
::
:: This library defines macros that are useful for working with strings.
::
:: The library is designed to be installed in a directory in your PATH.
:: Any batch file that requires it can include it by simply placing the
:: following line of code at the top before any SETLOCAL:
::
::   IF NOT DEFINED macro\load.MacroLib_String CALL macroLib_String
::
:: In this way the library becomes resident in your command shell environment
:: where it is available to any batch file that may need it. The IF condition
:: prevents unneccessary reloads of the same library.
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

:: Conditionally load dependencies (normally residing somewhere in the PATH)
if not defined macro\load.macroLib_Return call macroLib_Return

set macro\args.StrLen=  StrVar  [RtnVar]
set macro\help.StrLen=  StrVar  [RtnVar]%\n%
  %\n%
  Computes the length of the string within variable StrVar%\n%
  %\n%
  Sets RtnVar = result%\n%
  or displays result if RtnVar not specified%xLF%
set macro.StrLen=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=A!%%~a!"%\n%
  set "len=0"%\n%
  for /l %%A in (12,-1,0) do (%\n%
    set /a "len|=1<<%%A"%\n%
    for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
  )%\n%
  for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~b=%%v") else %echo%%%v%\n%
)

set macro\args.AnyToLower=  StrVar  [RtnVar]
set macro\help.AnyToLower=  StrVar  [RtnVar]%\n%
  %\n%
  Converts the string within variable StrVar to lower case%\n%
  %\n%
  Sets RtnVar = result%\n%
  or displays result if RtnVar not specified%\n%
  %\n%
  Like all "Any" macros, this version of ToLower supports all characters%\n%
  supported by DOS, including 0x0A ^<LF^> and 0x0D ^<CR^>.%\n%
  %\n%
  %%macro_EndAnyRtn%% must follow a call to this macro, and it cannot share%\n%
  a code block with the call.%xLF%
%macro_BeginDef%
set macro.AnyToLower=do (%\n%
  !macro_InitRtn!%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=^!%%~a^!"%\n%
  for %%A in (%\n%
    "A=a" "B=b" "C=c" "D=d" "E=e" "F=f" "G=g" "H=h" "I=i"%\n%
    "J=j" "K=k" "L=l" "M=m" "N=n" "O=o" "P=p" "Q=q" "R=r"%\n%
    "S=s" "T=t" "U=u" "V=v" "W=w" "X=x" "Y=y" "Z=z" "Ä=ä"%\n%
    "Ö=ö" "Ü=ü"%\n%
  ) do set "str=^!str:%%~A^!"%\n%
  !macro_Call! ("^!errorlevel^! 1 str %%~b") !macro.AnyRtn1!%\n%
)
%macro_Call% ("macro.AnyToLower") %macro.EndDef%
%macro_EndAnyRtn%

set macro\args.AnyToUpper=  StrVar  [RtnVar]
set macro\help.AnyToUpper=  StrVar  [RtnVar]%\n%
  %\n%
  Converts the string within variable StrVar to upper case%\n%
  %\n%
  Sets RtnVar = result%\n%
  or displays result if RtnVar not specified%\n%
  %\n%
  Like all "Any" macros, this version of ToUpper supports all characters%\n%
  supported by DOS, including 0x0A ^<LF^> and 0x0D ^<CR^>.%\n%
  %\n%
  %%macro_EndAnyRtn%% must follow a call to this macro, and it cannot share%\n%
  a code block with the call.%xLF%
%macro_BeginDef%
set macro.AnyToUpper=do (%\n%
  !macro_InitRtn!%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=^!%%~a^!"%\n%
  for %%A in (%\n%
    "a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I"%\n%
    "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R"%\n%
    "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z" "ä=Ä"%\n%
    "ö=Ö" "ü=Ü"%\n%
  ) do set "str=^!str:%%~A^!"%\n%
  !macro_Call! ("^!errorlevel^! 1 str %%~b") !macro.AnyRtn1!%\n%
)
%macro_call% ("macro.AnyToUpper") %macro.EndDef%
%macro_EndAnyRtn%

set macro\args.ToLower=  StrVar  [RtnVar]
set macro\help.ToLower=  StrVar  [RtnVar]%\n%
  %\n%
  Converts the string within variable StrVar to lower case%\n%
  %\n%
  Sets RtnVar = result%\n%
  or displays result if RtnVar not specified%xLF%
%macro_BeginDef%
set macro.ToLower=do (%\n%
  !macro_InitRtn!%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=^!%%~a^!"%\n%
  for %%A in (%\n%
    "A=a" "B=b" "C=c" "D=d" "E=e" "F=f" "G=g" "H=h" "I=i"%\n%
    "J=j" "K=k" "L=l" "M=m" "N=n" "O=o" "P=p" "Q=q" "R=r"%\n%
    "S=s" "T=t" "U=u" "V=v" "W=w" "X=x" "Y=y" "Z=z" "Ä=ä"%\n%
    "Ö=ö" "Ü=ü"%\n%
  ) do set "str=^!str:%%~A^!"%\n%
  !macro_Call! ("^!errorlevel^! 1 str %%~b") !macro.Rtn1!%\n%
)
%macro_Call% ("macro.ToLower") %macro.EndDef%
%macro_EndAnyRtn%

set macro\args.ToUpper=  StrVar  [RtnVar]
set macro\help.ToUpper=  StrVar  [RtnVar]%\n%
  %\n%
  Converts the string within variable StrVar to upper case%\n%
  %\n%
  Sets RtnVar = result%\n%
  or displays result if RtnVar not specified%xLF%
%macro_BeginDef%
set macro.ToUpper=do (%\n%
  !macro_InitRtn!%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=^!%%~a^!"%\n%
  for %%A in (%\n%
    "a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I"%\n%
    "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R"%\n%
    "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z" "ä=Ä"%\n%
    "ö=Ö" "ü=Ü"%\n%
  ) do set "str=^!str:%%~A^!"%\n%
  !macro_Call! ("^!errorlevel^! 1 str %%~b") !macro.Rtn1!%\n%
)
%macro_Call% ("macro.ToUpper") %macro.EndDef%
%macro_EndAnyRtn%

::------------------------------------------
:: MARK THAT THIS LIBRARY HAS BEEN LOADED

set macro\load.%~n0=1


macroLib_Swap.bat

Code: Select all

@echo off
:: File = macroLib_Swap.bat
:: Dependencies: macroLib_Base.bat, macroLib_Return.bat
:: This batch file will fail if called while delayed expansion is enabled.
::
:: This library defines macros that swap values between two variables.
::
:: The library is designed to be installed in a directory in your PATH.
:: Any batch file that requires it can include it by simply placing the
:: following line of code at the top before any SETLOCAL:
::
::   IF NOT DEFINED macro\load.MacroLib_Swap CALL macroLib_Swap
::
:: In this way the library becomes resident in your command shell environment
:: where it is available to any batch file that may need it. The IF condition
:: prevents unneccessary reloads of the same library.
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

:: Conditionally load dependencies (normally residing somewhere in the PATH)
if not defined macro\load.macroLib_Return call macroLib_Return

set macro\args.Swap=  Var1  Var2
set macro\help.Swap=  Var1  Var2%\n%
  %\n%
  Swap the contents of variable Var1 with variable Var2.%\n%
  %\n%
  The variables may contain any combination of characters supported by DOS%\n%
  except for 0x0A ^<Line Feed^> or 0x0D ^<Carriage Return^>.%xLF%
%macro_BeginDef%
set macro.Swap=do (%\n%
  !macro_InitRtn!%\n%
  setlocal enableDelayedExpansion%\n%
  set "macro.swap.b=^!%%~a^!"%\n%
  set "macro.swap.a=^!%%~b^!"%\n%
  !macro_Call! ("errorlevel 1 macro.swap.a:%%~a,macro.swap.b:%%~b") !macro.RtnN!%\n%
)
%macro_Call% ("macro.Swap") %macro.EndDef%
%macro_EndAnyRtn%

set macro\args.AnySwap=  Var1  Var2
set macro\help.AnySwap=  Var1  Var2%\n%
  %\n%
  Swap the contents of variable Var1 with variable Var2.%\n%
  %\n%
  The variables may contain any combination of characters supported by DOS%\n%
  including 0x0A ^<Line Feed^> and 0x0D ^<Carriage Return^>.%\n%
  %\n%
  %%macro_RtnAny%% must follow a call to this macro, and it cannot share a code%\n%
  block with the call.%xLF%
%macro_BeginDef%
set macro.AnySwap=do (%\n%
  !macro_InitRtn!%\n%
  setlocal enableDelayedExpansion%\n%
  set "macro.anyswap.b=^!%%~a^!"%\n%
  set "macro.anyswap.a=^!%%~b^!"%\n%
  !macro_Call! ("errorlevel 1 macro.anyswap.a:%%~a,macro.anyswap.b:%%~b") !macro.AnyRtnN!%\n%
)
%macro_Call% ("macro.AnySwap") %macro.EndDef%
%macro_EndAnyRtn%

::----------------------------------------------------
:: Mark that this library has been loaded.

set macro\load.%~n0=1


macroLib_Num.bat

Code: Select all

@echo off
:: File = macroLib_Num.bat
:: Dependencies: macroLib_Base.bat
:: This batch file will fail if called while delayed expansion is enabled.
::
:: This library defines macros that are useful for working with numbers.
::
:: The library is designed to be installed in a directory in your PATH.
:: Any batch file that requires it can include it by simply placing the
:: following line of code at the top before any SETLOCAL:
::
::   IF NOT DEFINED macro\load.MacroLib_Num CALL macroLib_Num
::
:: In this way the library becomes resident in your command shell environment
:: where it is available to any batch file that may need it. The IF condition
:: prevents unneccessary reloads of the same library.
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

:: Conditionally load dependencies (normally residing somewhere in the PATH)
if not defined macro\load.macroLib_Base call macroLib_Base

set macro\args.Num2Hex=  NumVal  [RtnVar]
set macro\help.Num2Hex=  NumVal  [RtnVar]%\n%
  %\n%
  Converts the integer value NumVal to the hexadecimal representation%\n%
  of a signed 32 bit integer.%\n%
  %\n%
  Sets RtnVar = result%\n%
  or displays result if RtnVar not specified%\n%
  %\n%
  NumVal may be passed as any expression supported by SET /A%xLF%
set macro.Num2Hex=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "dec=(%%~a)"%\n%
  if defined hex set "hex="%\n%
  set "map=0123456789ABCDEF"%\n%
  for /l %%n in (1,1,8) do (%\n%
    set /a "d=dec&15,dec>>=4"%\n%
    for %%d in (!d!) do set "hex=!map:~%%d,1!!hex!"%\n%
  )%\n%
  for %%v in (!hex!) do endlocal^&if "%%~b" neq "" (set "%%~b=%%v") else %echo%%%v%\n%
)

set macro\args.Random=  MinVal  MaxVal  [RtnVar]
set macro\help.Random=  MinVal  MaxVal  [RtnVar]%\n%
  %\n%
  Compute a pseudo random integral value between numeric values%\n%
  MinVal and MaxVal.%\n%
  %\n%
  Sets RtnVar = result%\n%
  or displays result if RtnVar not specified%\n%
  %\n%
  MinVal and MaxVal may be specified using any expression supported%\n%
  by SET /A%xLF%
set macro.Random=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.Random.rtn=!random! %% ((%%~b)-(%%~a)+1) + (%%~a)"%\n%
  for /f %%v in ("!macro.Random.rtn!") do endlocal^&if "%%~c" neq "" (set %%~c=%%v) else %echo%%%v%\n%
)


::------------------------------------------
:: MARK THAT THIS LIBRARY HAS BEEN LOADED

set macro\load.%~n0=1


macroLib_Time.bat

Code: Select all

@echo off
:: File = macroLib_Time.bat
:: Dependencies: macroLib_Base.bat
:: This batch file will fail if called while delayed expansion is enabled.
::
:: This library defines macros that are useful for working with Time.
::
:: The library is designed to be installed in a directory in your PATH.
:: Any batch file that requires it can include it by simply placing the
:: following line of code at the top before any SETLOCAL:
::
::   IF NOT DEFINED macro\load.MacroLib_Time CALL macroLib_Time
::
:: In this way the library becomes resident in your command shell environment
:: where it is available to any batch file that may need it. The IF condition
:: prevents unneccessary reloads of the same library.
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

:: Conditionally load dependencies (normally residing somewhere in the PATH)
if not defined macro\load.macroLib_Base call macroLib_Base

set macro\args.GetTime=  [RtnVar]
set macro\help.GetTime=  [RtnVar]%\n%
  %\n%
  Computes the current time of day measured as 1/100th seconds past midnight%\n%
  %\n%
  Sets RtnVar = result%\n%
  or displays result if RtnVar not specified ("""")%xLF%
set macro.GetTime=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set "t=0"%\n%
  for /f "tokens=1-4 delims=:." %%A in ("!time: =0!") do set /a "t=(((1%%A*60)+1%%B)*60+1%%C)*100+1%%D-36610100"%\n%
  for %%v in (!t!) do endlocal^&if "%%~a" neq "" (set "%%~a=%%v") else %echo%%%v%\n%
)

set macro\args.DiffTime=  StartTime  StopTime  [RtnVar]
set macro\help.DiffTime=  StartTime  StopTime  [RtnVar]%\n%
  %\n%
  Computes the elapsed time between StartTime and%\n%
  StopTime and formats the result as HH:MM:SS.DD%\n%
  %\n%
  StartTime and StopTime must be integral values%\n%
  representing 1/100th seconds past midnight. These%\n%
  values are typically gotten via calls to GetTime.%\n%
  %\n%
  Sets RtnVar=result%\n%
  or displays result if RtnVar not specified%\n%
  %\n%
  DiffTime will properly handle elapsed times that span%\n%
  midnight. However it cannot handle times that%\n%
  reach 24 hours or more.%\n%
  %\n%
  Note that StartTime and StopTime may be passed using%\n%
  any numeric expression supported by SET /A%xLF%
set macro.DiffTime=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "DD=(%%~b)-(%%~a)"%\n%
  if !DD! lss 0 set /a "DD+=24*60*60*100"%\n%
  set /a "HH=DD/360000, DD-=HH*360000, MM=DD/6000, DD-=MM*6000, SS=DD/100, DD-=SS*100"%\n%
  if "!HH:~1!"=="" set "HH=0!HH!"%\n%
  if "!MM:~1!"=="" set "MM=0!MM!"%\n%
  if "!SS:~1!"=="" set "SS=0!SS!"%\n%
  if "!DD:~1!"=="" set "DD=0!DD!"%\n%
  for %%v in (!HH!:!MM!:!SS!.!DD!) do endlocal^&if "%%~c" neq "" (set "%%~c=%%v") else %echo%%%v%\n%
)

::------------------------------------------
:: MARK THAT THIS LIBRARY HAS BEEN LOADED

set macro\load.%~n0=1


callMacro.bat

Code: Select all

@echo off
  if "%~1"=="" goto :help
  setlocal enableDelayedExpansion
  set "macro.temp=!macro.%~1!"
  endlocal & for /f "tokens=2-27" %%a in ("%*") %macro.temp%
  %macro_EndAnyRtn%
exit /b

  :help
    echo(
    for /f "tokens=1* eol=: delims=:" %%a in ('findstr /bn : %~f0') do echo(%%b
  exit /b

:callMacro  MacroName  Arg1  [ArgN]...
::
::  Calls macro MacroName via a function.
::  The MacroName should not include the macro. prefix.
::
::  Macros have restrictions on where they may be used. Calling a macro
::  through this function eliminates most of the restrictions at a cost of
::  reduced speed due to the function call overhead.
::
::  Macros are normally called inside a batch file using the macro_Call macro.
::
::  Use the following macro help command to get more information on options
::  for calling a macro:
::
::    mhelp call
::
::  For a full list of available macros to call, use:
::
::    margs
::
::  For detailed help on a particular macro, use:
::
::    mhelp macroName


2011-Jun-26: Fixed major bug in macroLib_Return.bat - macro.Rtn1

Enjoy! (maybe)

Dave Benham
Last edited by dbenham on 26 Jun 2011 14:54, edited 2 times in total.

Acy Forsythe
Posts: 126
Joined: 10 Jun 2011 10:30

Re: Batch "macros" with arguments

#34 Post by Acy Forsythe » 23 Jun 2011 21:40

Holy Crap!

I just asked for an updated example of a macro you already wrote. What happens if I ask for a whole function library? :)

But seriously Dave that's just awsome. Now I just have to forget half of what I've learned in the past few weeks and wrap my brain around this!

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#35 Post by Ed Dyreen » 24 Jun 2011 12:03


Dave, this is GOOD input, I have been avoiding the PassAnyVariable over EndLocal for a while.
Like Jeb, I just didn't think it was possible. :roll:
I need some time off now to study and crush this code down, see the basic tricks in action, assimilate. :mrgreen:

dbenham
Expert
Posts: 2283
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Batch "macros" with arguments

#36 Post by dbenham » 24 Jun 2011 12:29

Ed Dyreen wrote:I have been avoiding the PassAnyVariable over EndLocal for a while. Like Jeb, I just didn't think it was possible. :roll:

Actually I am using jeb's algorithms. He is the one that showed me how to do this for functions. I just encapsulated his methods into macros, with a few modifications to account for peculiarities of macro design.

Dave Benham

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#37 Post by Ed Dyreen » 26 Jun 2011 08:10

'
I will need to take your code apart benham, I can't run this batch on XP !
You violated some simple rules that make it won't work on XP !
first of all, this can't be done in XP like that:

Code: Select all

set "^getvar=do ( %\n%
    set return=!%%a! %\n%
)"

for %%a in ("var") %@getvar%
%%a won't expand on XP like that, I don't know why, but I need to replace it with %%b to get the desired results on an XP and there are more pecularities...

Ok, so i figured out you replace <LF> with %~4, and I can get the desired result with

Code: Select all

for %%4 in ( "!$LF!" ) do set "result=%result%"
But it only works outside my macro ?, I tried this inside the macro and it fail:

Code: Select all

for %%4 in ( "!$LF!" ) do call set "result=%%result%%"

Cleptography
Posts: 287
Joined: 16 Mar 2011 19:17
Location: scriptingpros.com
Contact:

Re: Batch "macros" with arguments

#38 Post by Cleptography » 26 Jun 2011 08:30

:mrgreen:

dbenham
Expert
Posts: 2283
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Batch "macros" with arguments

#39 Post by dbenham » 26 Jun 2011 16:23

OK, sorry everyone. At one point I had everything working but not with a consistent design and style. In trying to make everything consistent I broke the macro.Rtn1 routine and didn't test enough before posting. :evil: :oops:

I edited my earlier post and fixed the bug.
Here is the bugged line in macro.Rtn1 in macroLib_Return.bat:

Code: Select all

  if "%%~d" equ "" (%echo%!macro.Rtn1.rtn!) else if defined macro.Rtn1.rtn if not defined macro_NotDelayed (%\n%

And here is the fixed line:

Code: Select all

  if "%%~d" equ "" (%echo%!rtn!) else if defined rtn if not defined macro_NotDelayed (%\n%


Ed - I've verified that the macros do work just fine on XP.

Upon request -below is a single file that demonstrates how to use macro.Rtn1 and macro.AnyRtn1. All of the macros are straight from my (fixed) earlier posted libraries. I only included what was necessary to make the test work, and everything included is necessary. The macros in this test batch are local to the batch - they will not persist after the batch terminates because they were defined after SETLOCAL.

Macro definitions that include other macros are defined with delayed expansion enabled. See the documentation on macro.EndDef in macroLib_Return.bat for help on how to make these macros persist after the batch file terminates.

After the sample output I have the exact same test file except it doesn't include the macros definitions. Instead it loads the macros using the libraries as originally intended.

Code: Select all

@echo off
cls

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: FIRST DEFINE THE MACROS
::
setlocal disableDelayedExpansion

set LF=^


::Above 2 blank lines are required - do not remove
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
set "echo=echo("
set macro_ForEntireLine=^^^^^^^"eol^^^^=^^^^^^^%LF%%LF%^%LF%%LF%^^^%LF%%LF%^%LF%%LF%^^^^ delims^^^^=^^^^^^^"
set macro_Call=for /f "tokens=1-26" %%a in

set macro_InitRtn=setlocal^&set "macro_NotDelayed=!"^&set "macro_inFcn="
set "macro_EndAnyRtnPrefix=for /f "tokens=1-3" %%1 in ("!replace!") do for %%4 in ("!LF!") do "

set macro.Rtn1=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.Rtn1.ErrLvl=(%%~a), macro.Rtn1.EndLocalCnt=(%%~b)+2"%\n%
  set "rtn=!%%~c!"%\n%
  if defined macro_inFcn (%\n%
      set "errCmd=exit /b !macro.Rtn1.ErrLvl!"%\n%
  ) else if "!macro.Rtn1.ErrLvl!" == "0" (%\n%
      set "errCmd=rem"%\n%
  ) else if "!macro.Rtn1.ErrLvl!" == "1" (%\n%
      set "errCmd=set ="%\n%
  ) else (%\n%
      set "errCmd=cmd /d /c exit !macro.Rtn1.ErrLvl!"%\n%
  )%\n%
  if "%%~d" equ "" (%echo%!rtn!) else if defined rtn if not defined macro_NotDelayed (%\n%
    set "rtn=!rtn:^=^^!"%\n%
    set "rtn=!rtn:"=""Q!^"%\n%
    call set "rtn=%%^rtn:^!=""E^!%%" ! %\n%
    set "rtn=!rtn:""E=^!"%\n%
    set "rtn=!rtn:""Q="!^"%\n%
  )%\n%
  for /f "delims=" %%e in ("!errCmd!") do for /f %macro_ForEntireLine% %%v in (""!rtn!"") do (%\n%
    for /l %%n in (1,1,!macro.Rtn1.EndLocalCnt!) do endlocal%\n%
    if "%%~d" neq "" set "%%~d=%%~v" !%\n%
    %%e 2^>nul%\n%
  )%\n%
)

set macro.AnyRtn1=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.AnyRtn1.ErrLvl=(%%~a), macro.AnyRtn1.EndLocalCnt=(%%~b)"%\n%
  set "rtn=!%%~c!"%\n%
  set ^"replace=%% ^"^"^" !CR!!CR!^"%\n%
  set "macro_EndAnyRtn=!macro_EndAnyRtnPrefix!endlocal&endlocal"%\n%
  for /l %%N in (1,1,!macro.AnyRtn1.EndLocalCnt!) do set "macro_EndAnyRtn=!macro_EndAnyRtn!&endlocal"%\n%
  if "%%~d" equ "" (%echo%!rtn!) else (%\n%
    if defined rtn (%\n%
      set "rtn=!rtn:%%=%%~1!"%\n%
      set ^"rtn=!rtn:^"=%%~2!^"%\n%
      if defined CR for %%A in ("!CR!") do set "rtn=!rtn:%%~A=%%~3!"%\n%
      for %%A in ("!LF!") do set "rtn=!rtn:%%~A=%%~4!"%\n%
      if not defined macro_NotDelayed (%\n%
        set "rtn=!rtn:^=^^!"%\n%
        call set "rtn=%%^rtn:^!=""^!%%" ! %\n%
        set "rtn=!rtn:""=^!"%\n%
      )%\n%
    )%\n%
    set "macro_EndAnyRtn=!macro_EndAnyRtn!&set "%%~d=!rtn!" ^!"%\n%
  )%\n%
  if defined macro_inFcn (%\n%
      set "macro_EndAnyRtn=!macro_EndAnyRtn!&exit /b !macro.AnyRtn1.ErrLvl!"%\n%
  ) else if "!macro.AnyRtn1.ErrLvl!" == "1" (%\n%
      set "macro_EndAnyRtn=!macro_EndAnyRtn!&(2>nul set =)"%\n%
  ) else if "!macro.AnyRtn1.ErrLvl!" neq "0" (%\n%
      set "macro_EndAnyRtn=!macro_EndAnyRtn!&cmd /d /c exit !macro.AnyRtn1.ErrLvl!"%\n%
  )%\n%
)

::---------------------------------------------------------------------
:: The next 2 macros inlclude other macros so we need delayed expansion.
:: Normally this is done with %macro_BeginDef% at beginning and
:: %macro.EndDef% and %macro_EndAnyRtn% at end so that the macros
:: are installed in the cmd shell. But since these macros are only local
:: to this batch file I'll simply enable delayed expansion directly.

setlocal enableDelayedExpansion

set macro.ToUpper=do (%\n%
  !macro_InitRtn!%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=^!%%~a^!"%\n%
  for %%A in (%\n%
    "a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I"%\n%
    "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R"%\n%
    "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z" "ä=Ä"%\n%
    "ö=Ö" "ü=Ü"%\n%
  ) do set "str=^!str:%%~A^!"%\n%
  !macro_Call! ("^!errorlevel^! 1 str %%~b") !macro.Rtn1!%\n%
)

set macro.AnyToUpper=do (%\n%
  !macro_InitRtn!%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=^!%%~a^!"%\n%
  for %%A in (%\n%
    "a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I"%\n%
    "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R"%\n%
    "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z" "ä=Ä"%\n%
    "ö=Ö" "ü=Ü"%\n%
  ) do set "str=^!str:%%~A^!"%\n%
  !macro_Call! ("^!errorlevel^! 1 str %%~b") !macro.AnyRtn1!%\n%
)
::
:: END OF MACRO DEFINITIONS
:::::::::::::::::::::::::::::::::::::::::::::::::::

:::::::::::::::::::::::::::::::::::::::::::::::::::
:: NOW TEST THE MACROS
::
echo before ToUpper:
echo(
set macro_Call
echo(
set macro.Rtn1
echo(

echo --------------------------------------------------
echo 1st with delayed expansion ENABLED
echo(
echo ToUpper Result - no return variable
%macro_Call% ("macro_Call") %macro.ToUpper%
echo(
echo ToUpper Result - return in test
%macro_Call% ("macro_Call test") %macro.ToUpper%
set test
:: The next calls return <LF> so we need the ANY version
:: followed by %macro_EndAnyRtn%
echo(
echo AnyToUpper Result - no return variable
%macro_Call% ("macro.Rtn1") %macro.AnyToUpper%
%macro_EndAnyRtn%
echo(
echo AnyToUpper Result - return in test
%macro_Call% ("macro.Rtn1 test") %macro.AnyToUpper%
%macro_EndAnyRtn%
set test

setlocal disableDelayedExpansion
echo(
echo --------------------------------------------------
echo Now with delayed expansion DISABLED
echo(
echo ToUpper Result - no return variable
%macro_Call% ("macro_Call") %macro.ToUpper%
echo(
echo ToUpper Result - return in test
%macro_Call% ("macro_Call test") %macro.ToUpper%
set test
:: The next calls return <LF> so we need the ANY version
:: followed by %macro_EndAnyRtn%
echo(
echo AnyToUpper Result - no return variable
%macro_Call% ("macro.Rtn1") %macro.AnyToUpper%
%macro_EndAnyRtn%
echo(
echo AnyToUpper Result - return in test
%macro_Call% ("macro.Rtn1 test") %macro.AnyToUpper%
%macro_EndAnyRtn%
set test

Output:

Code: Select all

before ToUpper:

macro_Call=for /f "tokens=1-26" %a in

macro.Rtn1=do (
  setlocal enableDelayedExpansion
  set /a "macro.Rtn1.ErrLvl=(%~a), macro.Rtn1.EndLocalCnt=(%~b)+2"
  set "rtn=!%~c!"
  if defined macro_inFcn (
      set "errCmd=exit /b !macro.Rtn1.ErrLvl!"
  ) else if "!macro.Rtn1.ErrLvl!" == "0" (
      set "errCmd=rem"
  ) else if "!macro.Rtn1.ErrLvl!" == "1" (
      set "errCmd=set ="
  ) else (
      set "errCmd=cmd /d /c exit !macro.Rtn1.ErrLvl!"
  )
  if "%~d" equ "" (echo(!rtn!) else if defined rtn if not defined macro_NotDelayed (
    set "rtn=!rtn:^=^^!"
    set "rtn=!rtn:"=""Q!"
    call set "rtn=%^rtn:^!=""E^!%" !
    set "rtn=!rtn:""E=^!"
    set "rtn=!rtn:""Q="!"
  )
  for /f "delims=" %e in ("!errCmd!") do for /f ^"eol^=^

^ delims^=^" %v in (""!rtn!"") do (
    for /l %n in (1,1,!macro.Rtn1.EndLocalCnt!) do endlocal
    if "%~d" neq "" set "%~d=%~v" !
    %e 2>nul
  )
)

--------------------------------------------------
1st with delayed expansion ENABLED

ToUpper Result - no return variable
FOR /F "TOKENS=1-26" %A IN

ToUpper Result - return in test
test=FOR /F "TOKENS=1-26" %A IN

AnyToUpper Result - no return variable
DO (
  SETLOCAL ENABLEDELAYEDEXPANSION
  SET /A "MACRO.RTN1.ERRLVL=(%~A), MACRO.RTN1.ENDLOCALCNT=(%~B)+2"
  SET "RTN=!%~C!"
  IF DEFINED MACRO_INFCN (
      SET "ERRCMD=EXIT /B !MACRO.RTN1.ERRLVL!"
  ) ELSE IF "!MACRO.RTN1.ERRLVL!" == "0" (
      SET "ERRCMD=REM"
  ) ELSE IF "!MACRO.RTN1.ERRLVL!" == "1" (
      SET "ERRCMD=SET ="
  ) ELSE (
      SET "ERRCMD=CMD /D /C EXIT !MACRO.RTN1.ERRLVL!"
  )
  IF "%~D" EQU "" (ECHO(!RTN!) ELSE IF DEFINED RTN IF NOT DEFINED MACRO_NOTDELAYED (
    SET "RTN=!RTN:^=^^!"
    SET "RTN=!RTN:"=""Q!"
    CALL SET "RTN=%^RTN:^!=""E^!%" !
    SET "RTN=!RTN:""E=^!"
    SET "RTN=!RTN:""Q="!"
  )
  FOR /F "DELIMS=" %E IN ("!ERRCMD!") DO FOR /F ^"EOL^=^

^ DELIMS^=^" %V IN (""!RTN!"") DO (
    FOR /L %N IN (1,1,!MACRO.RTN1.ENDLOCALCNT!) DO ENDLOCAL
    IF "%~D" NEQ "" SET "%~D=%~V" !
    %E 2>NUL
  )
)

AnyToUpper Result - return in test
test=DO (
  SETLOCAL ENABLEDELAYEDEXPANSION
  SET /A "MACRO.RTN1.ERRLVL=(%~A), MACRO.RTN1.ENDLOCALCNT=(%~B)+2"
  SET "RTN=!%~C!"
  IF DEFINED MACRO_INFCN (
      SET "ERRCMD=EXIT /B !MACRO.RTN1.ERRLVL!"
  ) ELSE IF "!MACRO.RTN1.ERRLVL!" == "0" (
      SET "ERRCMD=REM"
  ) ELSE IF "!MACRO.RTN1.ERRLVL!" == "1" (
      SET "ERRCMD=SET ="
  ) ELSE (
      SET "ERRCMD=CMD /D /C EXIT !MACRO.RTN1.ERRLVL!"
  )
  IF "%~D" EQU "" (ECHO(!RTN!) ELSE IF DEFINED RTN IF NOT DEFINED MACRO_NOTDELAYED (
    SET "RTN=!RTN:^=^^!"
    SET "RTN=!RTN:"=""Q!"
    CALL SET "RTN=%^RTN:^!=""E^!%" !
    SET "RTN=!RTN:""E=^!"
    SET "RTN=!RTN:""Q="!"
  )
  FOR /F "DELIMS=" %E IN ("!ERRCMD!") DO FOR /F ^"EOL^=^

^ DELIMS^=^" %V IN (""!RTN!"") DO (
    FOR /L %N IN (1,1,!MACRO.RTN1.ENDLOCALCNT!) DO ENDLOCAL
    IF "%~D" NEQ "" SET "%~D=%~V" !
    %E 2>NUL
  )
)

--------------------------------------------------
Now with delayed expansion DISABLED

ToUpper Result - no return variable
FOR /F "TOKENS=1-26" %A IN

ToUpper Result - return in test
test=FOR /F "TOKENS=1-26" %A IN

AnyToUpper Result - no return variable
DO (
  SETLOCAL ENABLEDELAYEDEXPANSION
  SET /A "MACRO.RTN1.ERRLVL=(%~A), MACRO.RTN1.ENDLOCALCNT=(%~B)+2"
  SET "RTN=!%~C!"
  IF DEFINED MACRO_INFCN (
      SET "ERRCMD=EXIT /B !MACRO.RTN1.ERRLVL!"
  ) ELSE IF "!MACRO.RTN1.ERRLVL!" == "0" (
      SET "ERRCMD=REM"
  ) ELSE IF "!MACRO.RTN1.ERRLVL!" == "1" (
      SET "ERRCMD=SET ="
  ) ELSE (
      SET "ERRCMD=CMD /D /C EXIT !MACRO.RTN1.ERRLVL!"
  )
  IF "%~D" EQU "" (ECHO(!RTN!) ELSE IF DEFINED RTN IF NOT DEFINED MACRO_NOTDELAYED (
    SET "RTN=!RTN:^=^^!"
    SET "RTN=!RTN:"=""Q!"
    CALL SET "RTN=%^RTN:^!=""E^!%" !
    SET "RTN=!RTN:""E=^!"
    SET "RTN=!RTN:""Q="!"
  )
  FOR /F "DELIMS=" %E IN ("!ERRCMD!") DO FOR /F ^"EOL^=^

^ DELIMS^=^" %V IN (""!RTN!"") DO (
    FOR /L %N IN (1,1,!MACRO.RTN1.ENDLOCALCNT!) DO ENDLOCAL
    IF "%~D" NEQ "" SET "%~D=%~V" !
    %E 2>NUL
  )
)

AnyToUpper Result - return in test
test=DO (
  SETLOCAL ENABLEDELAYEDEXPANSION
  SET /A "MACRO.RTN1.ERRLVL=(%~A), MACRO.RTN1.ENDLOCALCNT=(%~B)+2"
  SET "RTN=!%~C!"
  IF DEFINED MACRO_INFCN (
      SET "ERRCMD=EXIT /B !MACRO.RTN1.ERRLVL!"
  ) ELSE IF "!MACRO.RTN1.ERRLVL!" == "0" (
      SET "ERRCMD=REM"
  ) ELSE IF "!MACRO.RTN1.ERRLVL!" == "1" (
      SET "ERRCMD=SET ="
  ) ELSE (
      SET "ERRCMD=CMD /D /C EXIT !MACRO.RTN1.ERRLVL!"
  )
  IF "%~D" EQU "" (ECHO(!RTN!) ELSE IF DEFINED RTN IF NOT DEFINED MACRO_NOTDELAYED (
    SET "RTN=!RTN:^=^^!"
    SET "RTN=!RTN:"=""Q!"
    CALL SET "RTN=%^RTN:^!=""E^!%" !
    SET "RTN=!RTN:""E=^!"
    SET "RTN=!RTN:""Q="!"
  )
  FOR /F "DELIMS=" %E IN ("!ERRCMD!") DO FOR /F ^"EOL^=^

^ DELIMS^=^" %V IN (""!RTN!"") DO (
    FOR /L %N IN (1,1,!MACRO.RTN1.ENDLOCALCNT!) DO ENDLOCAL
    IF "%~D" NEQ "" SET "%~D=%~V" !
    %E 2>NUL
  )
)


Here is the same test using the libraries:

Code: Select all

@echo off
cls

if not defined macro\load.macroLib_String call macroLib_String

echo before ToUpper:
echo(
set macro_Call
echo(
set macro.Rtn1
echo(

setlocal enableDelayedExpansion
echo --------------------------------------------------
echo 1st with delayed expansion ENABLED
echo(
echo ToUpper Result - no return variable
%macro_Call% ("macro_Call") %macro.ToUpper%
echo(
echo ToUpper Result - return in test
%macro_Call% ("macro_Call test") %macro.ToUpper%
set test
:: The next calls return <LF> so we need the ANY version
:: followed by %macro_EndAnyRtn%
echo(
echo AnyToUpper Result - no return variable
%macro_Call% ("macro.Rtn1") %macro.AnyToUpper%
%macro_EndAnyRtn%
echo(
echo AnyToUpper Result - return in test
%macro_Call% ("macro.Rtn1 test") %macro.AnyToUpper%
%macro_EndAnyRtn%
set test
echo(

setlocal disableDelayedExpansion
echo --------------------------------------------------
echo Now with delayed expansion DISABLED
echo(
echo ToUpper Result - no return variable
%macro_Call% ("macro_Call") %macro.ToUpper%
echo(
echo ToUpper Result - return in test
%macro_Call% ("macro_Call test") %macro.ToUpper%
set test
:: The next calls return <LF> so we need the ANY version
:: followed by %macro_EndAnyRtn%
echo(
echo AnyToUpper Result - no return variable
%macro_Call% ("macro.Rtn1") %macro.AnyToUpper%
%macro_EndAnyRtn%
echo(
echo AnyToUpper Result - return in test
%macro_Call% ("macro.Rtn1 test") %macro.AnyToUpper%
%macro_EndAnyRtn%
set test


Edited 2011-Jun-27 - Removed unrelated additional commands from end of sample output. Eliminated unfinished comment from code.

Dave Benham

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#40 Post by Ed Dyreen » 28 Jun 2011 12:18

'
I created some macros, and succesfully pushed them over the endlocal border 8)

But now I am trying to push a macro that seems to be too big.
Not at definition phase, not at execution phase, but while pushing it over it grows too big.
For every " is replaced with %~1 = 3chars instead of one etc...

I succeeded in getting the macro replaced by using arrays, but the final line is too big &I can't solve it

Code: Select all

for %%! in ( "!$replace!" ) do (

   set "@Macro=%@Macro.1%%@Macro.2%"
)
error: The inputline is too big

I was thinking that maybe using a helperfile ?, but I really have no idea :|

dbenham
Expert
Posts: 2283
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Batch "macros" with arguments

#41 Post by dbenham » 28 Jun 2011 15:01

Ed Dyreen wrote:I created some macros, and succesfully pushed them over the endlocal border 8)
I'm glad you got it working :D


Ed Dyreen wrote:But now I am trying to push a macro that seems to be too big.
Not at definition phase, not at execution phase, but while pushing it over it grows too big.
For every " is replaced with %~1 = 3chars instead of one etc...

I succeeded in getting the macro replaced by using arrays, but the final line is too big &I can't solve it
Do you mean that the macro gets defined, but when you try to use %macro.EndDef% followed by %macro_EndAnyRtn% ( or whatever you have replaced them with) it fails :?:

Whatever your situation, I think the size of the string that can be passed will be less than 8192 depending on the amount of substitution that must take place. If it is failing for you then you are probably near the hard limit anyway even without substitution. The substitution just takes it over the edge. (I'm sure someone could design a worst case problematic string that is far less than 8192 in length, but that doesn't seem like a real world situation).


Ed Dyreen wrote:I was thinking that maybe using a helperfile ?, but I really have no idea :|
You mean a temporary batch file :?: That seems like it should work, but I try to avoid them.

If this is a failure of defining a macro "calling" another macro at macro.EndDef time, then maybe it is time to switch to actually calling the referenced macro via your function instead of including it via delayed expansion.

Dave Benham

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#42 Post by Ed Dyreen » 28 Jun 2011 15:15


If this is a failure of defining a macro "calling" another macro at macro.EndDef time, then maybe it is time to switch to actually calling the referenced macro via your function instead of including it via delayed expansion.
Function ?, how do I write a function in batch ? :twisted: I know of only one function, a function that calls macros.

Oops, you assume I used your code. No I just studied it for 3days, crushed it down to it's elements and rebuild it using my own preferences. I never use other peoples codes, especially if I don't understand it. This is how I build it, it needs an index:

Code: Select all

   set ^"@AdvancedReturn=do ( %$n1c%

      set "$NotDelayedFlag=!" %$n1c%
      setlocal EnableDelayedExpansion %$n1c%

      set "$token=%%~!" %$n1c%

      set "$RetVar=!$token!" %$n1c%
      set "$RetVal=!%%~!!" %$n1c%
      ^>nul echo.$RetVal=!$RetVal! %$n1c%

      if defined $RetVal ( %$n1c%

         set "$RetVal=!$RetVal:%%=%%~1!"          %Replace by injection% %$n1c%
         set ^"$RetVal=!$RetVal:^"=%%~2!^" %$n1c%
         if defined $CR for %%r in ("!$CR!") do set "$RetVal=!$RetVal:%%~r=%%~3!" %$n1c%
         for %%r in ("!$LF!") do set "$RetVal=!$RetVal:%%~r=%%~4!" %$n1c%

         if defined $NotDelayedFlag ( %$n1c%

            set "$RetVal=!$RetVal:^=^^!"          %Replace directly% %$n1c%
            call set "$RetVal=%%^$RetVal:^!=""^!%%" ! %$n1c%
            set "$RetVal=!$RetVal:""=^!" %$n1c%
            set "$RetVal=!$RetVal!^!"          %Make sure our escapes dissapear% %$n1c%

         ) else ( %$n1c%

            set "$RetVal=!$RetVal:^=^^^^!"          %Replace directly% %$n1c%
            call set "$RetVal=%%^$RetVal:^!=""^!%%" ! %$n1c%
            set "$RetVal=!$RetVal:""=^^^!" %$n1c%
            set "$RetVal=!$RetVal!^^^!"          %Make sure our escapes dissapear% %$n1c%

         ) %$n1c%

      ) %$n1c%

      for %%? in ( %$n1c%

         "!$RetVar!" %$n1c%

      ) do    %@MForEntireLine% ( %$n1c%

         ""!$RetVal!"" %$n1c%

      ) do ( %$n1c%

         endlocal %$n1c%
         set "#%%~?=%%~r" %$n1c%
         echo.set "#%%~?=%%~r" %$n1c%

      ) %$n1c%
   )"

Code: Select all

   set $Variable.Index=
   ::
   echo. Building Index...
   set $Variable.Index=^
%   % $LF^
%   % $CR^
%   % $n0^
etc..
%   % @AdvancedReturn^
%   % @TestAdvancedReturn^
%   % $Variable.Index
   ::
   echo. Preparing involved variables...
   for %%! in ( %$Variable.Index% ) %@AdvancedReturn%
   echo. Making permanent...
   setlocal EnableDelayedExpansion

   set "$replace=%% """ ^!$CR^!^!$CR^!"
   ::
   for /f "tokens=1-3" %%1 in (

      "!$replace!"

   ) do    for %%4 in (

      "!$LF!"

   ) do (
      endlocal
      endlocal

      set "$Variable.Index=%#$Variable.Index%"
      set "$LF=%#$LF%"
      set "$CR=%#$CR%"
etc..
      set "@21nicho_#AsDLT=%#@21nicho_#AsDLT%"
      set "@AdvancedReturn=%#@AdvancedReturn%"
      set "@TestAdvancedReturn=%#@TestAdvancedReturn%"
   )
   ::
   %@echo% This Works
I really don't care about the overhead a helperfile produces, It will only happen once for all involved variables !
I just don't know (yet) how it could be done easily with the above example.

I decided not to use your method because I simply want to push any variable over endlocal.
This way I find simpler, can you explain to me the cons and pros of my version against yours ?
Dealing with multiple optional arguments - In functions we can shift varying numbers of leading option arguments so that required arguments are always in standard positions after options have been processed. We don't have any correlary to the SHIFT command for macro options.
You really should take a look at my 'Ed's CMD v2.0 UDF beta' at http://www.scriptingpros.com/viewforum.php?f=152
:idea: We could use the object attribute strategy here as well, but I don't really like it. I have a vague idea to pass all arguments as one string and then process the arguments with a dedicated "load arguments" macro. This macro would use a simple FOR loop to parse the arguments, but it would need special code to preserve * and ? characters. I'm not sure when (or if) I will get around to testing this.
That's exactly what @LeadIn and @LeadsIn is doing :)

Code: Select all

!@forEL! ( '%$Defines%, "r#Name=%%~a", "o#error.bool=%%~b"' ) !@LeadIn! %$n1c%
%   %  #macro     : required: callerfunc to push on $Debug.Array %$n1c%
%   %  r          : optional: for element is required %$n1c%
%   %  o          : optional: for element is optional %$n1c%
%   %  b          : optional: 0 for optional, 1 for required %$n1c%
%   %  #          : optional: for element is of type string %$n1c%
%   %  $          : optional: for element is of type variable %$n1c%
%   %  ?          : optional: for element is of type string v variable %$n1c%

dbenham
Expert
Posts: 2283
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Batch "macros" with arguments

#43 Post by dbenham » 30 Jun 2011 23:16

Ed Dyreen wrote:Function ?, how do I write a function in batch ? I know of only one function, a function that calls macros.
Umm, like the functions that DosTips takes great pains to explain... And your function that calls a macro is exactly what I had in mind.

Ed Dyreen wrote:Oops, you assume I used your code.
No, I've seen enough of your posts to know better, hence my statement - "or whatever you have replaced them with".

Ed Dyreen wrote:I decided not to use your method because I simply want to push any variable over endlocal.
That is mostly all I want to do, I just added the ability to set the errorlevel as well.

Ed Dyreen wrote:This way I find simpler, can you explain to me the cons and pros of my version against yours ?
I haven't completely traced your code, but it looks largely similar in function (certainly not style, but we knew that from before :lol: ) Here are major differences that I see:
  • You only encapsulated the 1st half of my RtnAny1 (or RtnAnyN) where you prepare the string to be returned. The last bit at the very end you have hard coded for specific variables. My goal was to build this last bit of code dynamically so that any macro or function can use it in a consistent manner without the need to write custom code. (what I call macro_EndAnyRtn). It seems to me your version requires new code be written every time you want to call @AdvancedReturn.
  • You are not allowing for a variable number of ENDLOCAL at the end. I sometimes find I need delayed expansion both enabled and disabled within one macro or function, so the ENDLOCAL count can vary.
  • You are missing the SETLOCAL prior to setting $NotDelayedFlag. This could cause problems if a macro or function calls another macro or function. The second call could corrupt the setting for the first call.

It took me a while to realize you were using percents to add comments to your code - and it doesn't add to the length of the macro because it expands to nothing :!: I hadn't seen that before 8)

You need to be careful - your comment style doesn't seem to work if the comment contains a colon (:).

Also, someone could define a variable name that matches your comment. You can prevent that if you add = since we can't include = in user defined variables. %= A comment like this with equals at beginning is safer% but %=c:% is a valid variable (there are a few more examples). To be absolutely sure your comment is safe you could include two = as in %= A secure way of commenting =%. Again, you still have problems if the comment contains a colon.

Dave Benham

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Batch "macros" with arguments

#44 Post by Ed Dyreen » 01 Jul 2011 00:04


I've seen you doing it somewhere, but I can't find it anymore. I'm having a little problem:

Code: Select all

   set ^"@Debug.on=( %$n1c%

      set "@DNul="                                        %= A comment the way benham suggests :) =% %$n1c%
   )"
   set ^"@Debug.off=( %$n1c%

      set "@DNul=>nul" %$n1c%
   )"
   set ^"@TraceIn=do ( %$n1c%

      ^^^!@DNul^^^! echo.hello world %$n1c%
   )"
   ::
   %@Debug.on%
   %@forTS% ( "yo" ) %@TraceIn%
   %@Debug.off%
   %@forTS% ( "yo" ) %@TraceIn%
   %@endoftest%
It doesn't work as expected, I need the contents send to nul if debugger is off :(
This must be possible without using an if statement !

dbenham
Expert
Posts: 2283
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Batch "macros" with arguments

#45 Post by dbenham » 01 Jul 2011 06:46

That only work with immediate expansion:

Code: Select all

echo stuff %@Dnul%

The following all fail because the redirection needs to be parsed before the expansion occurs:

Code: Select all

echo stuff !@Dnul!
call echo stuff !@Dnul!
call echo stuff %%@Dnul%%

I recommend defining an @dbug macro using IF and a $dbug switch:

Code: Select all

@echo off
setlocal
cls
set "@dbug=if defined $dbug echo("

::test it
set "@testDbug=%@dbug%Hello World
set "$dbug="
echo First we get silence.
%@testDbug%
set "$dbug=1"
echo And finally a greeting!
%@testDbug%


Ed Dyreen wrote:

Code: Select all

%= A comment the way benham suggests :) =% 
I do NOT recommend that comment because it contains a colon. It will not work properly :!:
Take out the : and it works fine.

Dave Benham

Post Reply