CFG2 1.2.042 - Tutorial


Contents


Introduction

CFG2 is a program that will generate files following a simple script language. Specifically, it was designed to facilitate the generation of configuration files. The following are all examples of files that have been generated via CFG2: MSdos AUTOEXEC.BAT and CONFIG.SYS files, compiler MAKEFILEs, linker "response" files, program source files, web site and documentation HTML files and AWK script files. The uses of CFG2 are only limited by your imagination.

There is very little required to run CFG2 - all you need is an executable suitable for your operating system. Since we develop under an MSdos-compatible operating system, for the purposes of this tutorial, we'll assume that's what you have operating system, we assume that you're au fait with any other requirements for your particular system.

Furthermore, we assume that you've had the sense to make a temporary directory in which to follow this tutorial and have a multitasking operating system so that you can copy and paste stuff directly from this tutorial into one or more CFG2 script files.

Please note that all source examples are indented by a few spaces. You cannot do this in a "real" source - the directives must start in column one.


File Handling

Concepts

As we've stated, CFG2's main purpose is to create files and, therefore, we introduce the file-handling script concepts first. The program distinguishes between three sorts of files: output, input and source. The most common file type is the "output file". This is the type of file "declared" when you wish to generate a file. The other two file types, input and source, are read by CFG2 and are different only in that the former is read as "raw data" whereas latter is scanned for "directives".

For each file declared, you must supply a file name (acceptable to your operating system, of course) and a "tag". Once the file has been declared, only the tag is used to refer to a file within the rest of the CFG2 script. This keeps files citations concise and, more importantly, one can refer to these files via a wild-card naming system independent of their "true" file names.

Output Files

It's time for our first example. Let's assume that you want to generate a file called "uptime.txt" and you want the text "success" to appear in it. This code will suffice:

        #file   UpTime uptime.txt
        #send   UpTime
                success
        #close  UpTime

This code fragment should be pretty simple to understand. The first line uses the #FILE directive to declare a file tag called UPTIME with a "real" file name of "uptime.txt". The following line uses a #SEND directive to start sending output to that file. In fact, one can send output to several files at once. (The number of files allowed to be open at a time is limited only by free memory and your operating system.) The last line, of course, closes the file. So, what about the line that just contains "success"?

Note that this line is indented by eight spaces. When processing a line of text, CFG2 first checks to make sure that the line doesn't start by a directive. In this example, all but one of the lines start with a directive. When CFG2 finds a directive, it obeys it. However, when the "margin" (that is, the first few columns of text) contains no text at all, the rest of the line of text is sent to all currently open files. In the above example, by the way, the margin is the default of eight spaces wide

Now, paste this example text it into a file called X.INI, making sure you remove the superfluous indentation. Then, try running CFG2 using something like this:

        CFG2 -FX.INI

When I did this on my machine, I got:

        CFG2 Version 1.1.184

CFG2 has merely displayed its version number. If I look in my temporary directory, I now have a file called UPTIME.TXT with the word "success" in it.

Well, this is all very well but what if we'd wanted to generate two files that contained this text? Well, we could do this:

        #file   UpTime  uptime.txt
        #file   UpTime2 uptime2.txt
        #send   UpTime UpTime2
                success
        #close  UpTime UpTime2

If you run this as before, you now have two files that contain "success".

This would seem to be a good point to introduce the wild-carding facilities of CFG2. In the above example, citing two files on the #SEND line wasn't any trouble. However, what if we wanted to send the stuff to, say, ten? What if we wanted to open and close all ten files many times? Well, CFG2 allows you to specify a wild-card tag in all of the file directives. Hence, the above example could have been re-written:

        #file   UpTime  uptime.txt
        #file   UpTime2 uptime2.txt
        #send   Up*
                success
        #close  Up*

Indeed, a single asterisk on its own could have been used because that would match all qualifying file tags.

Please note that all output files can be opened and closed willy-nilly subject, ultimately, only to operating system constraints. In this following example, the file OUT is generated in two separate lumps. (The first time that an output file opened, if it already exists, it will be truncated. Thereafter, output will be appended onto the end of the file.)

        #file   out x.txt
        #send   out
                Line One.
                Line Two.
        #close  out
        ;       Some other processing here...
        #send   out
                Line Three.
                Line Four.
        #close  out

(You can place comments in a CFG2 script by writing them after a semi-colon character.)

Input Files

Assuming that it resides in a file called X.INI, consider this next example and see if you can work out what it will do before you run it:

        #data   in  x.ini
        #file   out outfile.txt
        #send   out
        #read   in
        #close  out

If you guessed that you'd end up with a "clone" of X.INI, you'd be right. The #FILE, #SEND and #CLOSE directives, we've seen before. The new items here are the #DATA and #READ directives.

You use the #DATA directive to declare an input file. Input files are treated as raw text - no interpretation is done on their data at all. In this instance, we actually opened a CFG2 source file as an input file but, of course, the contents of an input are irrelevant to CFG2 - when it finds a corresponding #READ, it just opens the file, reads the data sending each line to all open files and closes the file at the end of the data stream.

Note that we've already noted, the above example could've been written as:

        #data   in  x.ini
        #file   out outfile.txt
        #send   *
        #read   *
        #close  *

Beware of one thing, here: when matching wild-card file tags, CFG2 matches only tags of the correct type. So, in the above example, tag IN does not match the "#SEND *" because it's not an output file. Similarly, file tag OUT does not match the "#READ *" line because it's not an input file. In general, we've tried to make the wild-card matching do as you'd expect to make the script source simpler.

Deleting Files

By using the #DELETE directive, one can delete files. There are two main uses for this facility: it can be used to remove temporary files that have been generated in a script and are no longer required. It can also be used when a "clean up" operation is wanted - the point is that a large CFG2 script will quite likely to rise to many smaller files. Since they can all be generated at will, it may be cleaner to provide a "make clean" switch in the script so that, when one backup up the system, you only backup what is required rather than the whole directory, or whatever.

Once again, wild-cards can be used with #DELETE but, of course, one must be careful when using this. (Note that you can only delete output files with #DELETE.)

A very simple example:

        #file   out y.ini
        #delete *

You will, of course, have to have a file called Y.INI hanging around to actually see the effect of this directive.

Source Files

From within CFG2, you can handle CFG2 source files as well as plain old output and input files. The main difference between an input file and a source file is that the latter is treated as if one had pasted the text straight into the script that included it. In this respect, the #INCLUD directive behaves much as a "C" language #INCLUDE pre-processor command except that one can't place the file name after the #INCLUD - you have to use a tag. (Or, in fact, a wild-card tag.)

An example follows (paste the text into the correctly named files):

File X.INI:

        #source src y.ini
        #file   out outfile.txt
        #send   *
        #includ src
        #close  *

File Y.INI:

                Mary had a little lamb,
        #say We're half way through the source file...
                It's fleece was white as snow.

File OUT was opened and the source of Y.INI was read just as if it had been pasted into the middle of X.INI using a text editor. Note the user message that is generated by the #SAY directive. If a #DATA-#READ pair had been used instead of the #SOURCE-#INCLUD pair, the #SAY message line would have ended up in the output data stream.

Once again, we could have used wild-card patterns with the #INCLUD. However, unlike with the #SEND and #READ directives, there is a little trap waiting for the unwary with a wild-carded #INCLUD. Since, when using an #INCLUD, we are dealing with source files, they have to be interpreted. This means that the program can not treat their operations as "atomic". In practice, when CFG2 comes across a wild-carded #INCLUD, it opens all of the matching source files at the same time. As each source stream is exhausted, then it is closed. However, the other streams will still be open until it is their turn to be read. Hence, it can be a bit "expensive" on files handles to do wild-card #INCLUDing.

Generating Source

One trick that can be very useful in a CFG2 script is the ability to generate a CFG2 script file and read it back later on. This is usually done to provide a crude "subroutine" facility. In order to make real use of this facility, we need to use CFG2's macro capability, so we'll leave an example to a later point in the tutorial. However, for now, remember that, within the same script, a file name can be associated with an input file tag and to a source file tag...


Switches And Configs

Concepts

Sending output to files is all very well but it'd be nice to be able to configure what one sends. This is precisely what CFG2's "switches" and "configs" are. We'll cover switches first because configs are merely a convenient way of settings many switches in one go.

If you've been working linearly through this tutorial, you've already seen an example with a switch in it how to use switches. Now, we'll see how to declare them.

Switches come in two basic varieties: "derived" and "simple". We'll cover simple switches first.

Simple Switches

Anyone who has done any high level programming will be familiar with "Boolean" or "logical" variables. These are variables that can hold either a TRUE or FALSE value and no others are allowed. In essence, a simple switch is a Boolean variable.

For our first switch example, let's define a simple switch:

        #switch Toggle

This would declare a switch called TOGGLE that was on by default. In order to make it be off by default, one would have to alter the example thus:

        #switch !Toggle

When we come to use switches, you'll find that the exclamation mark is CFG's Boolean "not" operator. In this instance, it's just used as an obvious extension to indicate "turn this switch off by default". This same notation is used when setting switch (and configs) from the command-line and in the #SET directive.

Let's say that we had used to above example where the simple switch TOGGLE was off by default and we wanted it turned on. How could we do this? Well, we could do it on the command-line:

        cfg2 -fx Toggle

This would "run" CFG2 script file X.INI and, at the correct juncture, apply all the settings on the command-line. In this instance, there's only one; viz, the citing of TOGGLE. If, by the way, we'd wanted TOGGLE turned off on the command-line, then we'd use this instead:

        cfg2 -fx !Toggle

We can also affect the value of TOGGLE from within a running CFG2 script by using the #SET directive:

        #set Toggle

By now, I expect you can guess how you turn a switch off by using the #SET word:

        #set !Toggle

Hence, the real power of CFG2 doesn't become apparent until you realise that switches and their close relative, configs, can be set and reset on the command-line, from within a CFG2 script or merely by using the default values when they are declared. Having declared a switch it can be used in an expression and, hence, to control the file generation process.

Since an understanding of expressions is a pretty basic requirement before one can use CFG2 and since one can't really do much with switches until they are understood, it's time to introduce an example of an expression along with an example of a derived switch.

Derived Switches

Derived switches, too, take only a TRUE or FALSE value. However, the user can not set them explicitly - they are called "derived" switches because they each have a derivation expression associated with them. When the value of a derived switch is required, the expression is evaluated and that is used as the switch's value.

Let's have an example. Let's say we want a switch called BREWING that is TRUE if two other switches, HEATING and PRIMED, are both TRUE:

        #switch BREWING HEATING PRIMED &

Here, the switch's name, BREWING, is followed by a Reverse Polish Notation (RPN) expression. Note that unlike ordinary high level language expression syntax, with RPN the operands come first and the operator last. (This notation was adopted for use in CFG2 simply because it is so much easier to program and that parentheses and operator precedence rules are not needed.)

Other than the fact that you cannot explicitly change the value of a derived switch, it behaves much as a simple switch does.

Before covering expressions in detail, we'd better get configs out of the way:

Configs

Although, at first sight, a config declaration may look similar to a derived switch declaration, it isn't. Here is an example of a config declaration:

        #config ALLBUTTHREE one two !three four five

Configs are a very convenient way to affect the settings of many switches by merely referencing one config tag name. In the above example, the config being declared is ALLBUTTHREE. What looks like an expression is, in fact, a list of switch or config names. (That's right - you can reference one config from a different config.)

When the config is cited - and this is done in exactly the same way as you'd alter the value of a simple switch - each item in the tag list is, in turn, set or reset depending on whether the name in the list has a leading "not" sign.

Assuming that X.INI contained the code in the above example, I could use this command-line:

        cfg2 -fname.ini ALLBUTTHREE

When CFG2 wanted to "execute" the setting of ALLBUTTHREE, it would travel down the list of switch names setting them all except THREE which would be turned off. Note that it is legal to use the following in your CFG2 script:

        #set ALLBUTTHREE

This would have the same effect as the above command-line example. You can also use a not sign before the config name, like this:

        cfg2 -fname.ini !ALLBUTTHREE

Or, indeed, this:

        #set !ALLBUTTHREE

In these two cases, switch THREE would be turned on and all the other switches would be turned off - the leading not sign inverts the actions of all the items in the list.

It is important to realise that this "toggling" action will ripple through all items affected by a config being altered. For example, consider this:

        #switch !SWITCH1
        #switch !SWITCH2
        #switch !SWITCH3
        #config CONFIG1 SWITCH1 SWITCH2
        #config CONFIG2 !CONFIG1 SWITCH3

Here, we have three switches that default off. Let us say that this command-line is used:

        cfg2 -fname.ini !CONFIG2

Well, it seems clear that, after the citation of CONFIG2, that SWITCH3 will be off. (There is a leading not sign before the "CONFIG2" so the action of the switch is reversed.) But what about that citation of CONFIG1 in the declaration of CONFIG2? What effect will that have?

Well, remember we've said that the inversion request on the command-line (or in the use of a #SET word) ripples down to the lower "levels". In this instance, the inversion of CONFIG2 will cause CONFIG1 to be turned on - remember, there was a not sign before the use of CONFIG2 and a not sign before the use of CONFIG1 in the declaration of CONFIG2. Two inversions, of course, will cancel each other out.

Having got switches and configs out of the way, it's time to look at expressions in more detail.


Expressions

Concepts

We have touched lightly on the subject of expressions in the previous couple of sections. However, now it's time to investigate them in detail.

As we have already said, CFG2 ignores the more normal "infix" notation in favour of Reverse Polish Notation. It is much easier to build an expression parser for RPN because it doesn't need complicated operator precedence rules and, as a result, doesn't need precedence-busting parentheses either. RPN can, for the user new to them, seem a little strange but the expressions that you're likely to use from within a CFG2 script will probably be pretty simple so it shouldn't present too much of a problem.

In CFG2 script, expressions can appear in several different places. If a switch declaration doesn't have one, then the switch is merely a simple switch and, hence, all derived switch declarations will contain an expression. Each #IF directive must have an expression following the keyword. Also, on a "normal" line of text, that is, one that does not start with a CFG2 directive, an expression can appear in the first few columns of the line - that is, within the "margin". With one minor exception, the expression syntax is identical in all of these places.

CFG2 maintains an expression "stack" onto which it pushes the results of the expression as it is deduced. Operands are evaluated and pushed straight onto the stack. Operators cause CFG2 to retrieve one or more items from the stack and, after working out the result of the calculations, it places the result back onto the stack. Hence, at the end of the expression evaluation, there should be precisely one item on the stack and that item will, indeed, be the "value" of the expression. (If there is not exactly one item on the stack at the end of the expression evaluation, CFG2 will produce an error message and abort the run.)

So, what do expressions look, like? Well, first, we have to introduce the operators to you. The operators are much what you'd expect from a high-level language compiler. There are symbols for all of the normal logical operators: "and", "or", "xor" and "not". ("C" programmers should not that there is no short-circuiting of the expression evaluations - it always carries on to the bitter end.)

The operators take a varying number of operands and it's convenient to group them together as such:

Monadic Operators

The monadic operators take only one operand and, at present, there is only one monadic operator (how apt) - the "not" sign. This, very simply, takes the previous expression (this is RPN, after all) and negates it. Thus, if the previous expression evaluated to TRUE, the result is FALSE and vice versa.

Here's an example - if switch RUNNING is TRUE, then the expression will be FALSE and so on:

        RUNNING !

In fact, there is a little departure from true RPN, here. Because CFG2 expressions tend to be very simple and since the not sign is used, elsewhere in CFG2's syntax, as a pre-fix operator, we relax the rules and allow a switch to have a not sign placed in front of the tag name if there is no space between the not sign and the switch tag name. Thus, the above example could be written as:

        !RUNNING

Remember, though, the "space rule" - if you were to leave a space after the not sign and before the tag name, CFG2 would try to "apply" the not operation to that part of the expression before the not sign. This is wrong :

        ! RUNNING       ; Wrong! This will invoke an error message.

Diadic Operators

The majority of CFG2 operators take two operands. These include all the normal logical operators - "and", "or" and "xor". The following code fragment shows an example of each:

        SWA SWB & ; TRUE if switch A *and* switch B are TRUE.
        SWA SWB | ; TRUE if switch A *or*  switch B are TRUE.
        SWA SWB ^ ; TRUE if switch A *xor* switch B are TRUE.

Note that no relaxation of the rules are allowed - the operator must be after the operands.

N-adic Operators

CFG2 has a special operator that grabs all of the existing items off of the expression stack. It is called "JUSTONE" and will return TRUE if one of the items it "pops" is TRUE. You will find this operator useful when you have a set of mutually-exclusive switches and you want an easy way to see if the user has only selected one of them. (It will also return FALSE if none of the switches is selected.) Here is an example:

        #switch !BORLAND
        #switch !WATCOM
        #switch !MICROSOFT
        #if     BORLAND WATCOM MICROSOFT JUSTONE !
        #error  You *must* select *one* compiler.
        #endif

Last Value Operator

This operator isn't, in fact, part of the expression syntax but, since it can only be used where an expression can be used and it doesn't really fit anywhere else, we document it here.

As you will shortly find out, expressions can be used to "filter" whether lines of normal text should be sent to the current set of open files. If you have several lines that all depend on the same expression, you can enter that expression on the first line of text and employ the "use last expression value" operator on the rest. In fact, you've already seen an example of this during the discussion of output files. Here's another example:

        A B &   First line of text.
        A B &   Second line of text.
        A B &   Third line of text.

This can be re-written as:

        A B &   First line of text.
        =       Second line of text.
        =       Third line of text.

The latter is, of course, more lucid and easier to alter if the expression has to be changed.


Miscellaneous

Text Macros

Many programming languages allow the programmer to define "text macros". That is, they allow the programmer to associate a given piece of text with an identifier and to be able to retrieve and use that text in a simple fashion.

CFG2 has such a facility via the #DEFINE word. Once defined by supplying the text and tag name to a #DEFINE directive, the associated text can be retrieved by employing the macro substitution characters, as detailed in a separate section of the tutorial. This is an example of a macro being defined:

        #define A_MACRO_TAG some macro text

This would associate the tag A_MACRO_TAG with the text string "some macro text".

Macro may be "nested"; that is, you may nest a reference to a macro tag in the definition of another macro tag. You can either arrange for the macro expansion to be done as the referrer is defined or have macro expansion done only when the referrer is expanded.

If you try to re-define a macro using the #DEFINE word, CFG2 will give you a warning about duplicate definitions. If you had intended to re-define a macro tag name, a better way to do it is to use the #REDEF word instead:

        ; This will result in a warning message!

        #define MACRO1 Definition number one
        <more_code>
        #define MACRO1 Definition number two

        ; This is how it *should* be done.

        #define MACRO1 Definition number one
        <more_code>
        #redef  MACRO1 Definition number two

Please note that you cannot re-define a macro tag until it has been defined. Hence, each re-definition of a tag must be preceded by a "normal" define of that tag.

Conditionals

One very useful construct in the CFG2 armoury is the IF-ELSE-ENDIF construct. This allows conditional execution of script text. This facility operates in a very similar manner to the items of the same name in the "C" pre-processor language.

Conditionals can be nested but, of course, the items must be nested correctly. This is an example of a simple IF-ELSE-ENDIF construct:

        #switch RAINING

        #if     RAINING
        #say    Take a brolly with you.
        #else
        #say    Leave the brolly at home.
        #endif

This, if executed, would do the expected - given that you leave the switch in its default state, then you'll get:

        CFG2 Version 1.1.194
        Take a brolly with you.

If you put a "!RAINING" on the end of the command-line, you'll get this instead:

        CFG2 Version 1.1.194
        Leave the brolly at home.

You should note that, unlike versions of CFG2 prior to 1.0.000, the IF-ELSE-ENDIF directives now take priority over all other directives. Thus, it is perfectly legal to do something like this:

        #switch GENC

        #if     GENC

        #define EXTN .C
        #define LANGUAGE C
        #file   SOURCE_OUT ROOTNAME.C

        #else

        #define EXTN .PAS
        #define LANGUAGE Pascal
        #file   SOURCE_OUT ROOTNAME.PAS

        #endif

        #say    Language is '%LANGUAGE%'
        #say    Extension is '%EXTN%'
        #say    File name is '%SOURCE_OUT%'

This would fail, by the way, on older CFG2s. If you run this in the default state, it'll produce:

        CFG2 Version 1.1.194
        Language is 'C'
        Extension is '.C'
        File name is 'ROOTNAME.C'

Whereas, if you add a "!GENC" on the end of your command-line, you'll get this instead:

        CFG2 Version 1.1.194
        Language is 'Pascal'
        Extension is '.PAS'
        File name is 'ROOTNAME.PAS'

Please note that, unlike in "C", you cannot indent nested conditionals (or, indeed, any directives). Therefore, this is not legal:

        #switch A
        #switch B

        #if     A
        #  if   B
        #    say A and B
        #  else
        #    say A and *not* B
        #  endif
        #else
        #  if   B
        #    say *not* A and B
        #  else
        #    say *not* A and *not* B
        #  endif
        #endif

I'm afraid, it would have to be written something like this, instead:

        #switch A
        #switch B

        #if A

        #if B
        #say A and B
        #else
        #say A and *not* B
        #endif

        #else

        #if B
        #say *not* A and B
        #else
        #say *not* A and *not* B
        #endif

        #endif

Margin Width

As you may have seen, CFG2 scans the first few columns of a line of "normal" text for a "margin expression". (Lines containing directives are, of course, treated differently.) You may wish to vary the width of this margin and that is the purpose of the #MARGIN directive.

The default width is eight characters (that same as the default tab width) but sometimes I change it to a zero width or to a slightly wider width.

Why would you want to do this? Well, one example of where this has been done is the text that you are reading! On this occasion, the generated text does not have any configurable items. That is, I'm only using the macro facilities of CFG2 to provide a "tidy" way to maintain the sources of these HTML files. (I could, of course, maintain all the separate HTML sources individually. This is a good example, though, of where CFG2 can be used to change, say, the "style" or layout of the HTML without having to do a lot of search-and-replace text edits. In this case, an edit of a couple of lines of CFG2 script, a re-generation and it's done!)

The following example shows how to change the margin to a zero-width margin. In other words, to the width used in the CFG2 sources that you are reading:

        #margin 0

We've covered making the margin smaller (or even removing it) but why would you want to make it bigger? If you are generating files that are heavily configurable, then your margin expressions may become so complicated that you run out of room when using the default margin width. Of course, altering the margin width isn't the only alternative in this situation - you could instead, for those few occasions where the margin isn't large enough, define a derived switch, and use that as the margin expression. It's your choice...

As an example, let's assume that we've defined three switches and want to send the text "OUTPUT TEXT" to the output files if all three switches are on. (We also assume the we are currently using a margin width of eight characters.) Our first attempt might be this:

        #switch A
        #switch B
        #switch C
        A B & C &OUTPUT TEXT

However, in this case the margin is too small for the expression to fit into it. In this case, the expression "seen" by CFG2 is, in fact, "A B & C " - because the margin width is eight characters, the final ampersand will be considered as part of the output text. Therefore, we have to do one of two things. Our first choice is to use a bigger margin, either temporarily or throughout the document:

        #switch A
        #switch B
        #switch C
        #margin 16
        A B & C &       OUTPUT TEXT

Or, if the expression is fairly common, we might want to define a derived switch and use it as necessary. (This is, in fact, a pretty good idea anyway - it may help make the script more lucid for the benefit of others.)

        #switch A
        #switch B
        #switch C
        #switch ALLON A B & C &
        ALLON   OUTPUT TEXT

Empty Lines

By default, CFG2 will ignore source lines that, after comment stripping and so on, result in an empty line. That is, if there isn't a single "useful" character of output, then no output will be generated. Adoption of this convention means that one can use liberal amounts of "white space" in the layout of the CFG2 source with no worry about it affecting the resulting output files.

Sometimes, however, one might want the generated text to be (human) readable. This is the case, for example, with the HTML sources for the ChAoS web site - even if a user doesn't have an HTML browser to hand, I still want the pages to be formatted such that they are at least amenable to scanning via a plain text editor, for example. Hence, the source of my web pages contains a line of source something akin to this:

        #empty  1

This forces CFG2 to make blank lines in the source generate a corresponding blank line in the output. There is, though, one caveat when running with #EMPTY set to a non-zero ("on") state. This is detailed in the section on comments.

Diagnostics

Various directives come under the blanket header of "diagnostic aids". Some are intended for diagnostic of CFG2 internal problems but most are intended for users to debug their scripts. Even so, the "heavy" CFG2 diagnostics mode can be useful if you really don't know what's going on. The #SHOW and #DUMP words can be used to get a "snapshot" of the state of the script.

The #SHOW word list the currently selected (that is, TRUE) switches:

        #switch X
        #switch !Y
        #switch Z
        #config G !X Y Z
        #define A 123
        #define B 456
        #file P P.TXT
        #file Q Q.TXT
        #file R R.TXT
        #show

The above gives, as output:

        CFG2 Version 1.1.194
        Selected switches: X Z

Note that switch Y is not represented because I defaulted it to a FALSE state.

The #DUMP word list everything in the CFG2 data structures. In the above example, if we replace the #SHOW word with a #DUMP word, the output changes to:

        CFG2 Version 1.1.194

        Configurations:
        G -> !X Y Z
        Macros:
        A -> 123
        B -> 456
        Defined switches:

        X Y Z

        Open Used Type Trun CRLF Tag->Name

        no   no   OUT  YES  YES  P 'P.TXT'
        no   no   OUT  YES  YES  Q 'Q.TXT'
        no   no   OUT  YES  YES  R 'R.TXT'

One can use the #SAY word to make CFG2 display a message on the uses console. This can be useful when finding out what the current state of, say, selected switches are or it can be useful just to provide the user of your script with a little "comfort" text. Either way, you just place the message text after the #SAY word and CFG2'll stick it on the console at the appropriate juncture. Note that you can use text expansion in the message text.

To quote an example from the very file that generates the text that you are reading:

        #say    Generating index HTML file.

This merely tells me when CFG2 is actually working on the main HTML file of my web site.

Another, related, word is the #ERROR word. This is similar to #SAY except that it will signal an error condition (and so will cause CFG2 to terminate) and that it'll print out the current source file name and line number:

        #error  We've found an error!

The above line, if run from a file called X.INI, produces:

        CFG2 Version 1.1.194

        Error at x.ini(1): We've found an error!

Executing Command Lines

It's sometimes useful to be able to execute commands from within a CFG2 script file. The #RUN and #RDRUN words fulfil this role. For example, if we want a listing of script files in the current directory, under MSdos, we could use this:

        #run    dir *.ini

Resulting, on my system, in the following output:

        CFG2 Version 1.1.194

         Volume in drive C is HEX
         Volume Serial Number is 2477-A6E6
         Directory of C:\Work\C\TC\Cfg2\z

        X        INI            19  21/02/01  19:55 X.INI
                 1 file(s)             19 bytes
                 0 dir(s)      27,951,104 bytes free

The #RDRUN word is similar except that it captures the system's output and sends it to the current set of open output files:

        #file   x x.txt
        #send   *
        #rdrun  dir *.ini

Special Characters

Comments

If you've read all of the preceding part of the tutorial, you've already seen examples of how to put a comment in CFG2 script source. The way to do it, of course, is to use a semi-colon to introduce a comment; anything up to the following end-of-line is ignored by CFG2. Here is a selection of lines, some of which are comments:

        Line one: text that will be sent to output files.
        ; Line two: a line that will be ignored.
        Line three: another line of output text.
        Line four: a line ;; that *includes* a ';;'.
        Line five: a final line of output text.
         ; Line six: a line that *may* produce output.

The first, third and fifth lines are, of course, plain text that will be sent to all open output files. But what about the second line? Well, that's a comment line and will be ignored by CFG2. Line four is a line of output that includes an instance of the comment character. Therefore, the semi-colon must be "escaped" by using two where, in fact, we only actually want one semi-colon in the output text. So, that leaves line six. Surely, you might think, this line is just another comment line?

Well, it is except that there is a complication: if #EMPTY is on (that is, blank lines are passed through to the output files unchanged), there is a problem with handling lines only contain a comment. A line that is fully commented will always be ignored. Hence, even with #EMPTY enabled, a comment that starts in the first column will produce no output at all. However, consider this:

        ; A completely ignored line.
         ; A line that'll produce a blank line.

Since this doesn't start in the very first column, CFG2 will strip the comment off and pass the resulting blank line to the code that sends output to files. Therefore, if #EMPTY is on, this comment line will generate a blank line.

Text Expansion

After reading a source line, but before the line is processed in any other way, CFG2 searches the line for the "macro" character. In this instance, this character is a per-cent sign. If it finds one, it looks for following macro character and then extracts the text between them. The program then finds suitable replacement text and slaps it in the gap vacated by the macro name. It then re-starts the search for a macro character, start at the first character of the newly-inserted text.

For example, if we want to print out the date and time when CFG2 was used to scan its source file, we could use this:

        #say Program was run on '%CFG2DATIM%'.

If you run this, it'll produce output similar to this:

        CFG2 Version 1.1.194
        Program was run on 'Wed Feb 21 16:57:43 2001'.

In common with the other special characters, you can "escape" the character (that is, prevent it having its special meaning) by using two together. For example, you can add a couple more macro characters like this:

        #say Program was run on '%%CFG2DATIM%%'.

Instead of the earlier output, this'll produce:

        CFG2 Version 1.1.194
        Program was run on '%CFG2DATIM%'.

Because the program re-scans newly inserted text, nested macro work as well as could be expected. For example:

        #define AAA a%%BBB%%a
        #define BBB b%%CCC%%b
        #define CCC c
        #say    The first macro expands to '%AAA%'.

This, if run, will produce the following:

        CFG2 Version 1.1.194
        The first macro expands to 'abcba'.

One thing to note, by the way, is use of the escaping mechanism. The above example would not work if the macro characters had not been escaped because the macro expansion would've taken place when the macros were declared. Consider such a version:

        #define AAA a%BBB%a
        #define BBB b%CCC%b
        #define CCC c
        #say    The first macro expands to '%AAA%'.

When AAA is declared, BBB has not been declared so CFG2 will expand the line such that AAA will contain only "aa" as its text. (CFG2 ignores undeclared macros when you try to expand them - it does not produce a error message when this happens.) A similar argument would apply to the following line.

For a full list of the items that can be "expanded", see the special macro names in the relevant section in the reference manual.

Line Termination

Lines sent to the list of open output files are terminated in the way that the operating system considers "normal" unless the special line termination character is used. The processing of this character, the under-line ('_'), is unusual in that it is only significant when it occurs as the last character of the script source line. (That is, after comments have been stripped.)

The function of the character is to inhibit the emission of the normal end-of-line sequence. Hence, the following code would generate one line of output text:

        #file Example XXX
        #send   *
                AAA_
                BBB_
                CCC

The results, in file EXAMPLE, would be:

        AAABBBCCC

As usual, you can escape the special character:

        #file Example XXX
        #send   *
                AAA__
                BBB__
                CCC

This gives, as output:

        AAA_
        BBB_
        CCC

There is, however, a complication. Using this scheme, one can generate lines that end with a normal termination sequence. One can generate lines that are normal and end with an under-line. Finally, one can generate lines that have the normal termination sequence inhibited and do not end with an under-line. One cannot, though, (easily) generate lines that have the termination sequence inhibited and end with an under-line.

So far, thankfully, I've never wanted to generate such a line whereas I quite often wanted to generate a normal line ending with an under-line. (It has to be said that I usually want to generate a line ending with an under-line while writing the documentation for CFG2 - I use CFG2 to generates its own documentation...)

You will find a good example of the inhibition of line termination in a later code example.

Tab Expansion

In common with many text handling programs, CFG2 will expand all "tab" (ASCII 0x09) characters it finds into a number of spaces. The number of spaces that it'll use depends upon the current "tab-stop" settings.

By default, it will use a tab-stop of eight characters. This can be changed by using the #TAB word. For example, to set the tab-stop back to the default, you would use this:

        #tab 8

Examples

Our Web Site

Please note that most of the documentation for the ChAoS utilities and HTML files on the www.glod.net web site are generated using CFG2. As a result, for examples of really large CFG2 script files, you could do worse than have a rummage about on that site.

Margin Expressions

This is an example of a simple margin expression. The following lines have been extracted from the CFG2 script that I use to configure the files on my main development machine:

        #switch !2940scsi ; Adaptec 2940 SCSI driver.
        #switch !lean     ; REALLY stripped down mode.
        #switch !ice      ; Soft Ice.

Note that I usually prefer to have all switches off by default. The file continues:

        #switch lice lean ice |

This little bit of code merely creates a short-cut for the expression "lean ice |". (That is, the expression "switch LEAN is TRUE or switch ICE is TRUE".) Note that in this last code fragment, the margin has been increased from eight to sixteen characters:

        !lice 2940scsi |c:\bin\ecs\mscdex _
        !lice           /d:ecscd003 _
        2940scsi        /d:mscd001 _
        !lice 2940scsi |/e /L:r

This code uses separate expressions for each line of the file. Hence, any of these lines could be discarded depending upon the settings of the three switches, LEAN, ICE and 2940SCSI.

Note that the "or" symbols are in the very last column of the margin - the characters after the "or" symbols are, in fact, part of the data that may (or may not) be sent to the set of currently open files.

Boot Disk Config

The following is the CFG2 script that I use on my MSdos boot floppy. It provides only two switches that the user can use: CD and DELETE. By default, it'll make CFG2 generate an AUTOEXEC.BAT and CONFIG.SYS for my floppy with no CD-ROM support. However, if one adds the "CD" switch onto the CFG2 command-line, then the relevant lines magically appear!

If one cites the DELETE switch on the command-line, it'll remove the two files leaving zero-length "stub" files instead - it leaves stubs so that, on boot, MSdos doesn't start asking for the date and time and suchlike. (Please note that I've removed all formatting and comment text and, instead, provided replacement comments for tutorial purposes.)

First, we actually declare the two switches that we need, Note that both default to an off state:

        #switch !delete
        #switch !cd

In this case, we only have two output files - the AUTOEXEC.BAT batch file and the corresponding CONFIG.SYS file (I've changed their names so that the script won't overwrite your start-up files if you accidentally run it in the root of your boot drive):

        #file autoexec  autoexec.bax
        #file config    config.syx

Quite often, when using CFG2 to generate these MSdos system files, I find that the margin expressions can become long enough to require a wider margin. In this script file, it's not really necessary, but I do it anyway just to be consistent:

        #margin 16

Now, we want to generate the CONFIG.SYS file. So, we #SEND to it:

        #send           CONFIG

Next, we send the required output to the file. Note that only one line has a margin expression and that's the line that should only go to the CONFIG.SYS file if we are generating a CD-ROM enabled system:

                        device=himem.sys /testmem:off
                        dos=umb
        CD              device=fdatacd.sys /d:mscd000
                        lastdrive=z

That's the CONFIG.SYS file wrapped up. Now, we close the old file and open the AUTOEXEC.BAT instead:

        #close          *
        #send           AUTOEXEC

Here, we send the output to the AUTOEXEC.BAT file, Once again, there's only a single line of text that is dependant upon the switch settings:

                        @echo off
                        mode con: rate=30 delay=1
        CD              lh mscdex /d:mscd000 /e /L:r
                        lh doskey /insert

Of course, when we are finished with the AUOTEXEC.BAT file, we close it:

        #close          *

The rest of the script is dedicated to providing the delete facility. To recap: if the DELETE switch is present, then we want to leave dummy files for MSdos to find when it boots. Now, we could have had a great big IF-ELSE-ENDIF construct around the previous code so that no output was generated if the delete option was selected. However, in large script files, it's usually easier to let the rest of the process run as normal and, if the delete switch is set, merely delete everything and re-generate the files but this time with zero content. So, is the delete switch set?:

        #if             delete

Now, we need to delete all the files generated so far:

        #delete         *

That done, we need to open all the output files. Closing them straight away will leave them zero-length:

        #send           *
        #close          *

It doesn't hurt to tell the user what's happened:

        #say
        #say            Deleted the configuration files having left dummy
        #say            %AUTOEXEC% and %CONFIG% files.

Finally, we have to provide a matching #ENDIF for the above #IF:

        #endif

That completes the script file. For completeness, we present the full text of the script file and the the results you'd get.

The full script of the file source looks like this:

        #switch !delete
        #switch !cd
        #file autoexec  autoexec.bax
        #file config    config.syx
        #margin 16
        #send           CONFIG
                        device=himem.sys /testmem:off
                        dos=umb
        CD              device=fdatacd.sys /d:mscd000
                        lastdrive=z
        #close          *
        #send           AUTOEXEC
                        @echo off
                        mode con: rate=30 delay=1
        CD              lh mscdex /d:mscd000 /e /L:r
                        lh doskey /insert
        #close          *
        #if             delete
        #delete         *
        #send           *
        #close          *
        #say
        #say            Deleted the configuration files having left dummy
        #say             and  files.
        #endif

On my machine, when I use:

        CFG2 -FX

The AUTOEXEC.BAT and CONFIG look like:

        @echo off
        mode con: rate=30 delay=1
        lh doskey /insert

And:

        device=himem.sys /testmem:off
        dos=umb
        lastdrive=z

If I use a command-line citing the CD switch instead:

        CFG2 -FX CD

I get:

        @echo off
        mode con: rate=30 delay=1
        lh mscdex /d:mscd000 /e /L:r
        lh doskey /insert

And:

        device=himem.sys /testmem:off
        dos=umb
        device=fdatacd.sys /d:mscd000
        lastdrive=z

Of course, using this:

        CFG2 -FX DELETE

Results in two empty files being generated.

Path Variables

One area where CFG2 really helps is in the creation of the MSdos PATH environment variable. This usually has to change depending upon the system setup. You can avoid having to go through MSdos' "menu" system at start up by using CFG2 to generate your AUTOEXEC.BAT file.

Consider the following example - this is one instance, by the way, where the comment escaping mechanism is required since the items in an MSdos PATH environment variable are separated by semi-colons which is CFG2's comment character:

        #switch !perl
        #switch !win
        #switch  tse
        #switch  tc
        #file x x.txt
        #send   *
                path %%path%%;;_
                   c:\bin\usr;;_
        tse        c:\bin\tse;;_
        perl       c:\lang\perl;;_
        win        c:\windows;;_
        tc         c:\lang\tc\bin;;_
                   c:\bin\dbase

Here, we have set our PATH up with various items being optional, depending upon the current set of selected switches. In addition, note that all except the last line terminate in an under-line because, when fully "constructed", the command to change the PATH must be all on one line.

If you run this script file, you'll find that, by default, it'll generate a file containing this single line of text:

        path %path%;c:\bin\usr;c:\bin\tse;c:\lang\tc\bin;c:\bin\dbase