Batch template
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Template batch script ::
:: ::
:: Author: Jacob I. Tate <jacob.tate@digipen.edu> ::
:: Date: 10/24/2019 ::
:: Version: 1.0.0 ::
:: ::
:: Changelog ::
:: 1.0.0 ::
:: - Created the script ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: NOTES: ::
:: - To create more parameters modify the __OPTIONS variable as specified ::
:: - To get the value from a parameter use if defined %-varname then expand ::
:: like normal variables %-varname% or DelayedExpansion notation !-varname!::
:: - Set the log file with __LOG_FILE and enable levels there too ::
:: - Log with call :LogLevel "My Message!" ::
:: - Run commands only in debug mode via call :runDebug command param1 param2::
:: - Naming convension ::
:: - __SCREAMING_SNAKE_CASE: Constant variables which need to be removed ::
:: - -xxx: Parameters ::
:: - lowerCamelCase: Functions intended for direct calls ::
:: - lower_snake_case: Functions intended for direct calls ::
:: - __lower_snake_case: Functions for internal use only ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
@echo off
setlocal
setlocal enableDelayedExpansion
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Script setup
call :init %*
if defined %-help (
echo.
echo This is the help prompt
echo.
call :clean_exit
)
if defined %-version (
echo %__VERSION%
call :clean_exit
)
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Do work!
goto :clean_exit
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: FUNCTIONS
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Initializes the script this should be the only part you need to modify ::
:: ::
:: Variables: ::
:: [out] __NAME ::
:: [out] __VERSION ::
:: [out] __BAT_FILE ::
:: [out] __BAT_PATH ::
:: [out] __BAT_NAME ::
:: [out] __LOG_FILE ::
:: [out] __LOG_DEBUG ::
:: [out] __LOG_INFO ::
:: [out] __LOG_ERROR ::
:: [out] __OPTIONS ::
:: [out] __INTERACTIVE ::
:: [out] -xx ::
:: Usage: ::
:: call :init ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:init
:: Useful variables
set "__NAME=%~n0"
set "__VERSION=1.0.0"
:: Batch file related stuff
set "__BAT_FILE=%~n0"
set "__BAT_PATH=%~dp0"
set "__BAT_NAME=%~nx0"
:: Logger related
:: If the log file doesnt already exist it will be created
:: Set the values to 1 to enable the various levels
set "__LOG_FILE=C:\test.log"
set "__LOG_DEBUG="
set "__LOG_INFO=1"
set "__LOG_ERROR=1"
:: Modified from https://stackoverflow.com/a/8162578
:: Define the option names along with the default values, using a <space>
:: delimiter between options
::
:: Each option has the format -name:[default]
::
:: The option names are NOT case sensitive
::
:: Options that have a default value expect the subsequent command line to
:: contain the value. If the option is not provided then the option is set
:: to the default. If the default contains spaces, special characters,
:: or starts with a colon, then it should be enclosed with double quotes.
:: The default can be undefined by specifying the default as empty quotes ""
:: NOTE: The default cannot contain * or ?
::
:: Options that are specified without any default value are simply flags that
:: are either defined or undefined. All flags start out undefined by default
:: and become defined if the option is supplied
::
:: The order is no important
::
set "__OPTIONS=-help: -version:"
:: Initialization functions
call :logInit
call :parse_arguments %*
:: Determine if the file is interactive to pause on exits if so
:: Interactive means they double clicked it to open it
set "__INTERACTIVE=1"
echo %cmdcmdline% | find /i "%__BAT_FILE%" >nul
if not errorlevel 1 set "__INTERACTIVE=0"
exit /b 0
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Cleans up all the things we did in the script namely deleting vars ::
:: NOTE: We assume all vars to delete are either __xx or -xx ::
:: ::
:: Variables: ::
:: [in/out] -xx ::
:: [in/out] __xx ::
:: Usage: ::
:: call :cleanup ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:cleanup
:: Remove the internal variables
for /f "tokens=1* delims==" %%a in ('set __ 2^>nul') do (
set %%a=
call :logDebug "Deleting %%a"
)
:: Remove the parameter variables
for /f "tokens=1* delims==" %%a in ('set - 2^>nul') do (
set %%a=
call :logDebug "Deleting %%a"
)
exit /b 0
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Parses the command line arguments creating a coresponding -xx variable ::
:: ::
:: Variables: ::
:: [in] __OPTIONS ::
:: [out] -xx ::
:: Usage: ::
:: call :parse_arguments %* ::
:: echo %-test% ::
:: if defined %-help echo help flag detected! ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:parse_arguments [args]
:: Set the default option values
:: Iterate over all the defaults
for %%O in (%__OPTIONS%) do (
REM Iterate over the tokens within the token
REM These tokens are delimited by a colon ":"
for /f "tokens=1,* delims=:" %%A in ("%%O") do (
set "%%A=%%~B"
)
)
:: This will output all the created default args
call :runDebug set -
:: Validate and store the options, one at a time, using a loop
:: Options start at arg1
:parse_arguments_loop
if not "%~1"=="" (
REM Create a tester for the argument to determine if the one we are
REM currently on is known or not
set "__ARG_TEST=!__OPTIONS:*%~1:=! "
call :logDebug "__ARG_TEST=!__ARG_TEST!"
if "!__ARG_TEST!"=="!__OPTIONS! " (
REM No substitution was made so this is an invalid option
REM Currently error handling just outputs this
REM Any other handling can also go here!
echo Error: Invalid option %~1
) else if "!__ARG_TEST:~0,1!"==" " (
REM Set the flag options using the options name
REM The value shouldnt matter, they just need to be defined
set "%~1=1"
) else (
REM Set the option value using the option as the name
REM Here we are disabling delayed expansion to allow for the use
REM of ! and ^ in variables
setlocal disableDelayedExpansion
REM Escape the ! and ^ characters
set "__TEMP_VAR=%~2"
call :parse_arguments_escape_val
setlocal enableDelayedExpansion
for /f delims^=^ eol^= %%A in ("!__TEMP_VAR!") do (
endlocal&endlocal&set "%~1=%%A" !
)
REM Preform the shift operation
shift /1
)
REM Preform the shift operation
shift /1
goto :parse_arguments_loop
)
:parse_arguments_escape_val
set "__TEMP_VAR=%__TEMP_VAR:^=^^%"
set "__TEMP_VAR=%__TEMP_VAR:!=^!%"
exit /b 0
exit /b 0
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Initializes the logger creating the log file if it doesnt already exist ::
:: ::
:: Variables: ::
:: [in] __LOG_FILE ::
:: Usage: ::
:: call :logInit ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:logInit
:: Create the log file if it doesnt exist
if not exist %__LOG_FILE% (
echo %__LOG_FILE%
type nul > %__LOG_FILE%
:: Dirty exit is something went wrong
if %ERRORLEVEL% NEQ 0 (
echo Failed to create the file %__LOG_FILE%
call :dirty_exit -1
)
)
call :logDebug "Log init successful"
exit /b 0
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Logs assuming we are in info mode! ::
:: ::
:: Variables: ::
:: [in] __LOG_FILE ::
:: [in] __LOG_INFO ::
:: Usage: ::
:: REM Logs only if info mode is enabled ::
:: set "__LOG_INFO=1" ::
:: call :logInfo "Info is enabled!" ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:logInfo [message]
if defined %__LOG_INFO (
echo [%date% - %time%] INFO: %~1
echo [%date% - %time%] INFO: %~1 >> %__LOG_FILE%
)
exit /b 0
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Logs assuming we are in error mode! ::
:: ::
:: Variables: ::
:: [in] __LOG_FILE ::
:: [in] __LOG_ERROR ::
:: Usage: ::
:: REM Logs only if error mode is enabled ::
:: set "__LOG_ERROR=1" ::
:: call :logError "Error is enabled!" ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:logError [message]
if defined %__LOG_ERROR (
echo [%date% - %time%] ERROR: %~1
echo [%date% - %time%] ERROR: %~1 >> %__LOG_FILE%
)
exit /b 0
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Logs assuming we are in debug mode! ::
:: ::
:: Variables: ::
:: [in] __LOG_FILE ::
:: [in] __LOG_DEBUG ::
:: Usage: ::
:: REM Logs only if debug mode is enabled ::
:: set "__LOG_DEBUG=1" ::
:: call :logDebug "Debug is enabled!" ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:logDebug [message]
if defined %__LOG_DEBUG (
echo [%date% - %time%] DEBUG: %~1
echo [%date% - %time%] DEBUG: %~1 >> %__LOG_FILE%
)
exit /b 0
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Calls the provided function assuming we are in debug mode! ::
:: ::
:: Variables: ::
:: [in] __LOG_FILE ::
:: [in] __LOG_DEBUG ::
:: Usage: ::
:: REM Runs set __ only if on debug mode! ::
:: set "__LOG_DEBUG=1" ::
:: call :runDebug set __ ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:runDebug [command]
if defined %__LOG_DEBUG (
call :logDebug "Calling %*"
%*
)
exit /b 0
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Exits the program with a clean exit code (0) ::
:: ::
:: Usage: ::
:: call :clean_exit ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:clean_exit
:: Pause if it was a double click
if _%interactive%_==_0_ pause
endlocal
:: Functions to close up shop
call :cleanup
call :__exit_batch 0
goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Exits the program with a non clean exit code ::
:: ::
:: Usage: ::
:: call :dirty_exit -1 ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:dirty_exit [errorcode]
:: Pause if it was a double click
if _%interactive%_==_0_ pause
endlocal
:: Functions to close up shop
call :cleanup
call :__exit_batch %~1
goto :eof
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Exits the batch file with the provided exit code ::
:: NOTE: This exits the current file but not the caller! ::
:: ::
:: Usage: ::
:: call :__exit_batch -1 ::
:: call :__exit_batch 0 ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:__exit_batch [errorcode]
set _errLevel=%~1
:: Exit the script and remove all function calls from the call stack
:__exit_batch_pop_stack
(
(goto) 2>nul
setlocal DisableDelayedExpansion
call set "caller=%%~0"
call set _caller=%%caller:~0,1%%
call set _caller=%%_caller::=%%
if not defined _caller (
REM callType = func
REM set _errLevel=%_errLevel%
goto :__exit_batch_pop_stack
)
(goto) 2>nul
endlocal
cmd /c "exit /b %_errLevel%"
)
goto :eof