(c) 2018 Ivanq
BatchBuilder is a build system for BAT/CMD files.
First:
- Copy
build.cmdand thecompilerdirectory somewhere. - Create directory
srcand add someentry.cmdfile inside.entry.cmdis your BAT file.
After some changes:
- Run
build.cmd. - Directory
distwith a scriptbootstrap.cmdwill appear, run it..
File build.ini can be created inside src directory to control BatchBuilder. The following options can be used:
-
entry=<filename>Default value:entry.cmdThe script that will be ran on start.
-
delete_compiled=yes/noDefault value:yesWhether to remove compiled BAT-files. If
nois choosen, compiled code will be held incompiler\compileddirectory. -
compile_if=<rules>Default value:batch ~-4 .cmd,batch ~-4 .batSets which files will be handled by BatchBuilder.
The rules are separated by comma. The rules (
~-4 .cmdand~-4 .bat) are made of compiler name (batch), key (~-4) and value (.cmdand.bat).If the key starts with a tilda (~), and the following number is negative (-4), then the latest
-ncharacters (4) will be checked. If the number is positive, all the characters except the firstnwill be checked. Later choosen characters are checked against value.If the key doesn't start with a tilda, the rule is a replace rule. For example, the key
a=bmeansreplace all A's with B's and then compare to value.Example:
batch ~-4 .cmd,batch ~-4 .batHandle all files which have.cmdor.batas the last 4 characters (i.e. havecmdorbatextension) withbatchcompiler.Example:
batch o=a hello.batHandle all files with name =hello.batorhella.batwithbatchcompiler. -
packed=<yes/no/local>Default value:yesSets whether BatchBuilder should pack the project to a single file.
If the value is
no, filebootstrap.cmdand thecontentsfolder with compiled scripts will be created. When ran, thecontentsfolder will be copied to%TEMP%.If the value is
local,bootstrap.cmdandcontentswill also be created, but the script will be ran fromcontents, not%TEMP%.And if the value is
yes,bootstrap.cmdwill contain an unpacker and an archive, and the script will be ran from%TEMP%.
To simplify work, BatchBuilder introduce modules. A module is just a BAT-file. Here is an example a module:
+------------------------------------------------+
| hi.bat |
+------------------------------------------------+
| export say_hello |
| echo Hello |
| end export |
+------------------------------------------------+
Let's save it to src directory. Now let's write a script using say_hello:
+------------------------------------------------+
| entry.cmd |
+------------------------------------------------+
| import say_hello |
+------------------------------------------------+
BatchBuilder compiler will replace import say_hello with echo Hello.
When calling import say_hello, we could pass additional data, for example, a name:
+------------------------------------------------+
| entry.cmd |
+------------------------------------------------+
| import say_hello Kirill |
| import say_hello Vanya |
+------------------------------------------------+
Then the first value can be accessed as %1, the second as %2, and so on.
+------------------------------------------------+
| hi.bat |
+------------------------------------------------+
| export say_hello |
| echo Hello, %1! |
| end export |
+------------------------------------------------+
Some parameters can be named, then %1, %2 etc. will mean "1/2 argument after last named":
+------------------------------------------------+
| hi.bat |
+------------------------------------------------+
| export say_hello name |
| echo Hello, %name%! |
| end export |
+------------------------------------------------+
Modules can be useful for simple tasks, for example, delete question.
+------------------------------------------------+
| delete.cmd |
+------------------------------------------------+
| export delete_dir dir |
| set /p agree=Do you want to delete %dir%? |
| if "%agree%" == "yes" ( |
| rmdir /S /Q %1 |
| ) |
| end export |
+------------------------------------------------+
+------------------------------------------------+
| entry.cmd |
+------------------------------------------------+
| import delete_dir Documents |
| import delete_dir Desktop |
| import delete_dir Pictures |
| import delete_dir Music |
+------------------------------------------------+
BatchBuilder's batch compiler also supports operator return inside export blocks to return values from methods. To save returned value import -> variable command syntax can be used.
+------------------------------------------------+
| delete.cmd |
+------------------------------------------------+
| export ask q |
| set /p result=%q%? |
| return %result% |
| end export |
| |
| export delete_dir |
| import -> agree ask "Delete %1" |
| if "%agree%" == "yes" ( |
| rmdir /S /Q %1 |
| ) |
| end export |
+------------------------------------------------+
By default, variables defined in one function cannot be accessed from another function or recursion call of the same function.
+------------------------------------------------+
| local_vars.cmd |
+------------------------------------------------+
| export a |
| echo Enter A |
| import b |
| echo Read local variable my_var from A |
| echo my_var=%my_var% |
| echo Exit A |
| end export |
| |
| export b |
| echo Enter B |
| echo Set local variable my_var |
| set my_var=hello |
| echo Read local variable my_var from B |
| echo my_var=%my_var% |
| echo Exit B |
| end export |
| |
| import a |
+------------------------------------------------+
| Enter A |
| Enter B |
| Set local variable my_var |
| Read local variable my_var from B |
| my_var=hello |
| Exit B |
| Read local variable my_var from A |
| my_var= |
| Exit A |
+------------------------------------------------+
Though sometimes you may want to set a variable for all functions. global keyword can be used for this.
+------------------------------------------------+
| global_vars.cmd |
+------------------------------------------------+
| export a |
| echo Enter A |
| import b |
| echo Read global variable my_var from A |
| echo my_var=%my_var% |
| echo Exit A |
| end export |
| |
| export b |
| echo Enter B |
| echo Set global variable my_var |
| global my_var=hello |
| echo Read global variable my_var from B |
| echo my_var=%my_var% |
| echo Exit B |
| end export |
| |
| import a |
+------------------------------------------------+
| Enter A |
| Enter B |
| Set global variable my_var |
| Read global variable my_var from B |
| my_var=hello |
| Exit B |
| Read global variable my_var from A |
| my_var=hello |
| Exit A |
+------------------------------------------------+
A command like @directive <something> can go between export, which means "set @something directive for the current function". You can set several directives at once: for example: @directive <something> <something2>.
-
@safe_recursionBy default, a new context is created to use local variables. To fasten it,
setlocalandendlocalcommands are used. However,setlocalhas depth limit.If you don't need the limit, it makes sense to use
@safe_recursiondirective which stores and loads variables using files, notsetlocalandendlocal. However,@safe_recursioncan slow the script. -
@follow_localTo make coding easier, all caller's variables are removed, and after returning they are restored. For example:
+------------------------------------------------+
| non_local.cmd |
+------------------------------------------------+
| export a |
| echo Enter A |
| echo Set my_var=hello |
| set my_var=hello |
| import b |
| echo Exit A |
| end export |
| |
| export b |
| echo Enter B |
| echo Read local variable my_var |
| echo my_var=%my_var% |
| echo Exit B |
| end export |
| |
| import a |
+------------------------------------------------+
| Enter A |
| Set my_var=hello |
| Enter B |
| Read local variable my_var |
| my_var= |
| Exit B |
| Exit A |
+------------------------------------------------+
However, to make recursion calls faster, @follow_local directive can be used, if empty variables don't have special meaning:
+------------------------------------------------+
| local.cmd |
+------------------------------------------------+
| export a |
| echo Enter A |
| echo Set my_var=hello |
| set my_var=hello |
| import b |
| echo Exit A |
| end export |
| |
| @directive follow_local |
| export b |
| echo Enter B |
| echo Read local variable my_var |
| echo my_var=%my_var% |
| echo Exit B |
| end export |
| |
| import a |
+------------------------------------------------+
| Enter A |
| Set my_var=hello |
| Enter B |
| Read local variable my_var |
| my_var=hello |
| Exit B |
| Exit A |
+------------------------------------------------+
You can fasten your script by using @follow_local and unsafe recursion.
batch compiler supports classes. To create a class, class/end class commands are used:
+------------------------------------------------+
| class.cmd |
+------------------------------------------------+
| class ClassTest |
| end class |
+------------------------------------------------+
Methods can be added using export command.
+------------------------------------------------+
| class.cmd |
+------------------------------------------------+
| class ClassTest |
| export say_hello |
| echo Hello, %1! |
| end export |
| export say_bye |
| echo Bye, %1! |
| end export |
| end class |
+------------------------------------------------+
new keyword is used to create a class, and call/import can be done using a new instance:
+------------------------------------------------+
| class.cmd |
+------------------------------------------------+
| echo Create ClassTest and put it to my_class |
| new -> my_class ClassTest |
| |
| echo Call say_hello and then say_bye |
| import %my_class%.say_hello Ivan |
| import %my_class%.say_bye Ivan |
+------------------------------------------------+
It should be noticed that objects are created in heap, so they aren't linked to variable name. That means that instances are passed by reference.
+------------------------------------------------+
| reference_class.cmd |
+------------------------------------------------+
| export by_reference |
| echo Inside: %1 |
| end export |
| |
| new -> my_class ClassTest |
| echo Outside: %my_class% |
| import by_reference %my_class% |
+------------------------------------------------+
| Outside: __instance_343654654__ |
| Inside: __instance_343654654__ |
+------------------------------------------------+
%this% variable can be used inside methods, having a link to current instance:
+------------------------------------------------+
| class.cmd |
+------------------------------------------------+
| class ClassTest |
| export save |
| set "%this%.data=%~1" |
| echo Saved %~1 |
| end export |
| |
| export load |
| call set "data=%%%this%.data%%" |
| return %data% |
| end export |
| |
| export echo |
| import -> data %this%.load |
| echo Loaded %data% |
| end export |
| end class |
| |
| new -> instance ClassTest |
| import %instance%.save One |
| import %instance%.echo |
+------------------------------------------------+
| Saved One |
| Loaded One |
+------------------------------------------------+
Sometimes non-standard operations should be done without calling import. Magic methods can be used for this.
-
magic_initCalled when
newoperator is used. Arguments can be passed vianewcommand:
+------------------------------------------------+
| magic.cmd |
+------------------------------------------------+
| class ClassTest |
| export magic_init |
| echo Created %~1 |
| end export |
| end class |
| |
| new -> instance ClassTest "for test" |
+------------------------------------------------+
| Created for test |
+------------------------------------------------+
Examples named like src-admin, src-alert and so on are distributed with BatchBuilder. To run them, rename the folder to src, run build.cmd and then dist\bootstrap.cmd.