diff --git a/tconfpy.3 b/tconfpy.3 index dd64d3b..62f0f70 100644 --- a/tconfpy.3 +++ b/tconfpy.3 @@ -60,13 +60,13 @@ syntax: .nf -from tconfpy import * + from tconfpy import * .fi If you prefer the more pedestrian: .nf -import tconfpy + import tconfpy .fi you will have to prepend all references to a \*(TC object with @@ -85,15 +85,15 @@ and default as described below: .nf -from tconfpy import * + from tconfpy import * -retval = ParseConfig(cfgfile, - InitialSymTbl={}, - AllowNewVars=True, - AllowNewNamespaces=True, - Debug=False, - LiteralVars=False - ) + retval = ParseConfig(cfgfile, + InitialSymTable={}, + AllowNewVars=True, + AllowNewNamespaces=True, + Debug=False, + LiteralVars=False + ) where: @@ -106,7 +106,7 @@ The the name of a file containing configuration information .TP -.B InitialSymTbl (Default: {}) +.B InitialSymTable (Default: {}) A pre-populated symbol table (a Python dictionary). As described @@ -147,14 +147,14 @@ An object of type \'tconfpy.RetObj\' used to return parsing results. -.SS Reasons For Passing An Initial Symbol Table +.SS The Initial Symbol Table API Option The simplest way to parse a configuration file is just to call the parser with the name of that file: .nf -retval = ParseConfig("myconfigfile") + retval = ParseConfig("MyConfigFile") .fi Assuming your configuration file is valid, \'ParseConfig()\' will @@ -169,42 +169,46 @@ your own choosing. Why on earth would you want to do this? There are a number of reasons: + +.IP \(bu 4 +You may wish to write a configuration file which somehow depends +on a pre-defined variable that only the calling program can know: + .nf -1) You may wish to write a configuration file which somehow depends - on a pre-defined variable that only the calling program can know: - - .if [APPVERSION] == 1.0 - # Set configuration for older application releases - .else - # Set configuration for newer releases - .endif - - In this example, only the calling application can know its own - version, so it sets the variable APPVERSION in a symbol table - which is passed to \'ParseConfig()\'. - -2) You may wish to "protect" certain variable names be creating - them ahead of time and marking them as "Read Only". This is - useful when you want a variable to be available for use - within a configuration file, but you do not want users to - be able to change its value. In this case, the variable can - be referenced in a string substitution or conditional test, - but cannot be changed. - -3) You may want to place limits on what values can be assigned to - a particular variable. When a variable is newly defined in a - a configuration file, it just defaults to being a string - variable without any limits on its length or content. But - variables that are created by a program have access to the - variable's "descriptor". By setting various attribues - of the variable descriptor you can control variable type, - content, and range of values. In other words, you can have - \*(TC "validate" what values the user assigns to particular - variables. This substantially simplifies your application because - no invalid variable value will ever be returned from the parser. - + .if [APPVERSION] == 1.0 + # Set configuration for older application releases + .else + # Set configuration for newer releases + .endif .fi +In this example, only the calling application can know its own +version, so it sets the variable APPVERSION in a symbol table +which is passed to \'ParseConfig()\'. + +.IP \(bu 4 +You may wish to "protect" certain variable names be creating +them ahead of time and marking them as "Read Only". This is +useful when you want a variable to be available for use +within a configuration file, but you do not want users to +be able to change its value. In this case, the variable can +be referenced in a string substitution or conditional test, +but cannot be changed. + +.IP \(bu 4 +You may want to place limits on what values can be assigned to +a particular variable. When a variable is newly defined in a +a configuration file, it just defaults to being a string +variable without any limits on its length or content. But +variables that are created by a program have access to the +variable's "descriptor". By setting various attribues +of the variable descriptor you can control variable type, +content, and range of values. In other words, you can have +\*(TC "validate" what values the user assigns to particular +variables. This substantially simplifies your application because +no invalid variable value will ever be returned from the parser. + + .SS How To Create An Initial Symbol Table A \*(TC "Symbol Table" is really nothing more than a Python @@ -214,20 +218,24 @@ nothing more than this: .nf + + from tconfpy import * + + # Create an empty symbol table + MySymTable = {} + + # Create descriptor for new variable + MyVarDes = VarDescriptor() + + # Code to fiddle with descriptor contents goes here + MyVarDes.Value = "MyVal" -from tconfpy import * + # Now load the variable into the symbol table + MySymTable["MyVariableName"] = MyVarDes -# Create an empty symbol table -MySymTable = {} - -# Create descriptor for new variable -MyVarDes = VarDescriptor() + # Repeat this process for all variables, then call the parser -# Code to fiddle with descriptor contents goes here -MyVarDes.Value = "MyVal" - -# Now load the variable into the symbol table -MySymTable["MyVariableName"] = MyVarDes + retval = ParseConfig("MyConfigFile", InitialSymTable=MySymTable) .fi @@ -287,13 +295,13 @@ suppose you did this when defining the variable, \'foo\': .nf -VarDescriptor.Type = TYPE_INT + VarDescriptor.Type = TYPE_INT .fi Now suppose the user puts this in the configuration file: .nf -foo = bar + foo = bar .fi This will cause a type mismatch error because \'bar\' cannot be coerced @@ -345,11 +353,6 @@ every legal value for the variable in question. If the list is empty,then this validation check is skipped. -.B IMPORTANT: -If you change the content of \'LegalVals\', make sure it is always a -Python list. \*(TC's validation logic presumes this attribute -to be a list and will blow up nicely if it is not. - The exact semantics of LegalVals varies depending on the type of the variable. @@ -451,15 +454,71 @@ .nf - .if [boolvar] != False # This works fine + .if [boolvar] != False # This works fine - .if [boolvar] != FALSE # This does not work - Case is not being observed + .if [boolvar] != FALSE # This does not work - Case is not being observed - .if [boolvar] != Off # Neither does this - Only True and False can be tested + .if [boolvar] != Off # Neither does this - Only True and False can be tested .fi +.SS The Initial Symbol Table And Lexical Namespaces + +The +.B CONFIGURATION LANGUAGE REFERENCE +section below discusses lexical namespaces in some detail from the +user's point-of-view. However, it is useful for the programmer +to understand how they are implemented. + +\*(TC is written to use a pre-defined variable named \'NAMESPACE\' as +the place where the current namespace is kept. If you do not +define this variable in the initial symbol table passed to the parser, +\*(TC will create it automatically with an initial value of "". + +From a programmer's perspective, there are are few important things +to know about namespaces and the \'NAMESPACE\' variable: + + +.IP \(bu 4 +You can manually set the initial namespace to something other than +"". You do this by creating the \'NAMESPACE\' variable in the initial +symbol table passed to the parser, and setting the \'Value\' attribute +of its descriptor to whatever you want as the initial namespace. + +.IP \(bu 4 +Each time the user enters a new namespace, it is added to the +\'LegalVals\' attribute of the \'NAMESPACE\' variable descriptor. +This is done to keep a "history" of all namespaces encountered during +parsing. + +.IP \(bu 4 +If you are inhibiting the creation of new namespaces (via the +\'AllowNewNamespaces = False\' option described below) you can +enumerate the namespaces the user +.B is +allowed to use by pre-defining them. You do this by adding each +permitted namespace to the \'LegalVals\' attribute of the +\'NAMESPACE\' variable descriptor in the initial symbol table. + +.IP \(bu 4 +When the call to \'ParseConfig()\' completes, the \'Value\' +attribute of the \'NAMESPACE\' variable descriptor will contain +the namespace that was in effect when the parse completed. i.e., +It will contain the last namespace used. + +.IP \(bu 4 +Similarly, on parser completion, the \'LegalVals\' attribute of the +\'NAMESPACE\' variable descriptor will contain the name of all the +namespaces encountered during parsing. This is +.B not +true if you suppressed new namespace creation with the +\'AllowNewNamespaces = False\' API option. In that case, the +\'LegalVals\' attribute will contain only those namespaces you +pre-defined. All of these may- or may not have been used in the +configuration file, there is no way to tell. + + .SS How The \*(TC Parser Validates The Initial Symbol Table When you pass an initial symbol table to the parser, \*(TC does some @@ -476,21 +535,21 @@ is outside the range of \'Min\' to \'Max\', \*(TC will accept it quietly. -.SS The \'AllowNewVars\' Option +.SS The \'AllowNewVars\' API Option By default, \*(TC lets the user define any new variables they wish in a configuration file, merely by placing a line in the file in this form: .nf - Varname = Value + Varname = Value .fi However, you can disable this capability by calling the parser like this: .nf - retval = ParseConfig("myconfigfile", AllowNewVars=False) + retval = ParseConfig("myconfigfile", AllowNewVars=False) .fi This means that the configuration file can "reference" @@ -504,24 +563,22 @@ defined by the user. Why? There are several possible uses for this option: -.nf +.IP \(bu 4 +You know every configuration variable name the calling program +will use ahead of time. Disabling new variable names keeps +the configuration file from getting cluttered with variables +that the calling program will ignore anyway, thereby +keeping the file more readable. -1) You know every configuration variable name the calling program - will use ahead of time. Disabling new variable names keeps - the configuration file from getting cluttered with variables - that the calling program will ignore anyway, thereby - keeping the file more readable. - -2) You want to insulate your user from silent errors caused - by misspellings. Say your program looks for a configuration - variable called \'MyEmail\' but the user enters something - like \'myemail = foo@bar.com\'. \'MyEmail\' and \'myemail\' - are entirely different variables and only the former is - recognized by your calling program. By turning off new - variable creation, the user's inadvertent misspelling of - the desired variable name will be flagged as an error. - -.fi +.IP \(bu 4 +You want to insulate your user from silent errors caused +by misspellings. Say your program looks for a configuration +variable called \'MyEmail\' but the user enters something +like \'myemail = foo@bar.com\'. \'MyEmail\' and \'myemail\' +are entirely different variables and only the former is +recognized by your calling program. By turning off new +variable creation, the user's inadvertent misspelling of +the desired variable name will be flagged as an error. Note, however, that there is one big drawback to disabling new variable creation. \*(TC processes the configuration file on a @@ -533,22 +590,22 @@ .nf -inter1 = Really, really, really, really, long argument #1 -inter2 = Really, really, really, really, long argument #2 + inter1 = Really, really, really, really, long argument #1 + inter2 = Really, really, really, really, long argument #2 -realvar = command [inter1] [inter2] + realvar = command [inter1] [inter2] .fi If you disable new variable creation you can't do this anymore unless all the variables \'inter1\', \'inter2\', -and \'realvar\' are predefined in the initial symbol +and \'realvar\' are pre-defined in the initial symbol table passed to the parser. -.SS The \'AllowNewNamespaces\' Option +.SS The \'AllowNewNamespaces\' API Option By default, \*(TC supports the use of an arbitrary number of -lexical namespaces. They can be predefined in an initial +lexical namespaces. They can be pre-defined in an initial symbol table passed to the parser and/or created in the configuration file as desired. (The details are described in a later section of this document.) @@ -575,7 +632,7 @@ configuration file even if \'AllowNewNamespaces\' is set to False. -.SS The \'Debug\' Option +.SS The \'Debug\' API Option \*(TC has a fairly rich set of debugging features built into its parser. It can provide some detail about each line parsed as well @@ -584,10 +641,10 @@ API call: .nf -retval = ParseConfig("myconfigfile", Debug=True) + retval = ParseConfig("myconfigfile", Debug=True) .fi -.SS The \'LiteralVars\' Option +.SS The \'LiteralVars\' API Option \*(TC supports the inclusion of literal text anywhere in a configuration file via the \'.literal\' directive. This @@ -603,7 +660,7 @@ Here is an example: .nf - MyEmail = me@here.com # This defines variable MyEmail + MyEmail = me@here.com # This defines variable MyEmail .literal printf("[MyEmail]"); /* A C Statement */ @@ -626,7 +683,7 @@ \'ParseConfig()\' call: .nf -retval = ParseConfig("myconfigfile", LiteralVars=True) + retval = ParseConfig("myconfigfile", LiteralVars=True) .fi @@ -675,44 +732,51 @@ case, we can parse and extract results like this: .nf -from tconfpy import * + from tconfpy import * -retval = ParseConfig("myconfigfile", Debug=True) + retval = ParseConfig("myconfigfile", Debug=True) .fi \'retval\' now contains the results of the parse: -.nf -retval.Errors - A Python list containing error messages. If this list - is empty, you can infer that there were no parsing - errors - i.e., The configuration file was OK. +.TP +.B retval.Errors +A Python list containing error messages. If this list is empty, you +can infer that there were no parsing errors - i.e., The configuration +file was OK. -retval.Warnings - A Python list containing warning messages. These - describe minor problems not fatal to the parse - process, but that you really ought to clean up in - the configuration file. +.TP +.B retval.Warnings +A Python list containing warning messages. These describe minor +problems not fatal to the parse process, but that you really ought to +clean up in the configuration file. -retval.SymTable - A Python dictionary which lists all the defined - symbols and their associated values. A "value" - in this case is always an object of type - tconfpy.VarDescriptor (as described above). +.TP +.B retval.SymTable -retval.Literals - As described below, the \*(TC configuration language - supports a \'.literal\' directive. This directive - allows the user to embed literal text anywhere in - the configuration file. This effectively makes - \*(TC useful as a preprocessor for any other - language or text. retval.Literals is a Python list - containing all literal text discovered during the parse. +A Python dictionary which lists all the defined symbols and their +associated values. A "value" in this case is always an object of type +tconfpy.VarDescriptor (as described above). -retval.Debug - A Python list containing detailed debug information for - each line parsed as well as some brief summary - information about the parse. retval.Debug defaults - to an empty list and is only populated if you set - \'Debug=True\' in the API call that initiated the - parse (as in the example above). -.fi +.TP +.B retval.Literals + +As described below, the \*(TC configuration language supports a +\'.literal\' directive. This directive allows the user to embed +literal text anywhere in the configuration file. This effectively +makes \*(TC useful as a preprocessor for any other language or text. +retval.Literals is a Python list containing all literal text +discovered during the parse. + +.TP +.B retval.Debug + +A Python list containing detailed debug information for each line +parsed as well as some brief summary information about the parse. +retval.Debug defaults to an empty list and is only populated if you +set \'Debug=True\' in the API call that initiated the parse (as in the +example above). .SH CONFIGURATION LANGUAGE REFERENCE @@ -746,16 +810,20 @@ whitespace matters: .nf - - Variable names may not contain whitespace + - Variable names may not contain whitespace - - Directives must be followed by whitespace if - they take other arguments. + - Directives must be followed by whitespace if they + take other arguments. - - When assigning a value to a string variable, - whitespace within the value on the right-hand-side - is preserved. Leading- and trailing whitespace - around the right-hand-side of the assignment is - ignored. + - When assigning a value to a string variable, + whitespace within the value on the right-hand-side + is preserved. Leading- and trailing whitespace around + the right-hand-side of the assignment is ignored. + + - Whitespace within both the left- and right-hand-side + arguments of a conditional comparison + (\'.if ... == / != ...\') is significant for purposes + of the comparison. .fi .IP \(bu 4 @@ -766,16 +834,20 @@ Regardless of a variable's type, all variable references return .B a string representation of the variable's value! -This is done so that the variable's value can be used for -comparison testing and string substitution/concatenation. -In other words, variables are stored in their native type -in the symbol table that is returned to the calling program, -but they are treated as strings throughout the configuration -file. +This is done so that the variable's value can be used for comparison +testing and string substitution/concatenation. In other words, +variables are stored in their native type in the symbol table that is +returned to the calling program. However, they are treated as strings +during the parsing of the configuration file whenever they are used in +a comparison test or in a substitution. + +.IP \(bu 4 +Any line which does not conform to these rules and/or is not +in the proper format for one of the operations described below, +is considered an error. - -.SS Variables And Variable References +.SS Creating Variables And Assigning A Value The heart of a configuration file is a "variable". Variables are stored in a "Symbol Table" which is returned to the calling program @@ -788,37 +860,32 @@ Variables are assigned values like this: .nf -MyVariable = Some string of text + MyVariable = Some string of text .fi If \'MyVariable\' is a new variable, \*(TC will create it on the spot. -If it already exists, \*(TC will first check and make sure that -\'Some string of text\' is a legal value for this variable. -If not, it will produce an error and refuse to change the current -value of \'MyVariable\' +If it already exists, \*(TC will first check and make sure that \'Some +string of text\' is a legal value for this variable. If not, it will +produce an error and refuse to change the current value of +\'MyVariable\'. -You can get the value of any currently defined variable by -"referencing" it like this: +Anytime you create a new variable, the first value assigned to +it is also considered its "default" value. This may (or may +not) be meaningful to the application program. -.nf - .... [MyVariable] ... -.fi +Variables which are newly-defined in a configuration file are +always understood to be +.B string +variables - i.e., They hold "strings" of text. However, it is +possible for the applications programmer to pre-define +variables with other types and place limitations on what values +the variable can take and/or how short or long a string variable +may be. (See the previous section, +.B PROGRAMMING USING THE \*(TC API +for all the gory details.) -The brackets surrounding any name are what indicate that you want that -variable's value. -You can also get the value of any Environment Variable on your -system by naming the variable with a leading \'$\': - -.nf - ... [$USER] ... # Gets the value of the USER environment variable -.fi - -However you cannot set the value of an environment variable: - -.nf -$USER = me # This is not permitted -.fi +.SS Variable Names Variables can be named pretty much anything you like, with certain restrictions: @@ -836,15 +903,143 @@ because \*(TC sees that character as the beginning a comment. .IP \(bu 4 -You cannot have a variable with no name. This is illegal: +You cannot have a variable whose name is the empty string. This is +illegal: .nf - = String + = String .fi +.SS Getting And Using The Value Of A Variable -.SS Predefined Variables +You can get the value of any currently defined variable by +"referencing" it like this: + +.nf + .... [MyVariable] ... +.fi + +The brackets surrounding any name are what indicate that you want that +variable's value. + +You can also get the value of any Environment Variable on your +system by naming the variable with a leading \'$\': + +.nf + ... [$USER] ... # Gets the value of the USER environment variable +.fi + +However you cannot set the value of an environment variable: + +.nf + $USER = me # This is not permitted +.fi + + +This ability to both set and retrieve variable content makes it easy +to combine variables through "substitution": + +.nf + + MYNAME = Mr. Tconfpy + MYAGE = 101 + + Greeting = Hello [MYNAME], you look great for someone [MYAGE]! + +.fi + + +Several observations are worth noting here: + +.IP \(bu 4 +The substitution of variables takes place as soon as the parser +processes the line \'Greeting = ...\'. That is, variable substitution +happens as it is encountered in the configuration file. The only +exception to this is if an attempt is made to refer to an +undefined/non-existent variable. This generates an error. + +.IP \(bu 4 +Unless a variable as been marked as "Read Only" by the program, +you can continue to change its value as you go. Simply adding +another line at the end of our example above will change the value +of \'Greeting\' to something new: + +.nf + Greeting = Generic Greeting Message +.fi + +In other words, the last assignment statement for a given variable +"wins". This may seem sort of pointless, but it actually has great +utility. You can use the \'.include\' directive to get, say, a +"standard" configuration provided by the system administrator for a +particular application. You can then selectively override the +variables you want to change in your own configuration file. + +.IP \(bu 4 +The variable \'Greeting\' now contains the +.B string +"Hello Mr. Tconfpy, you look great for someone 101!" This is true even +if variable \'MYAGE\' has been defined by the calling program to be +an integer type. To repeat a previously-made point: All variable substitution +and comparison operations in a configuration file are done with +.B strings +regardless of the actual type of the variables involved. + + +.SS Lexical Namespaces + +The discussion of variable assignment and reference needs to be +expanded now to include the idea of "lexical namespaces". Think of a +namespace as just being the current context for variables - a way to +group variables together. The idea is that a variable name or +reference is always relative to the current namespace. That is, the +"real" name of the variable is the concatenation of the current +namespace with the variable's name. + +For instance, suppose the current namespace is "MYNS" and we see the +following in a configuration file: + +.nf + Foo = Bar + Baz = [Foo] +.fi + +What this +.B really +means to \*(TC is this: + +.nf + MYNS.Foo = MYNS.Bar + MYNS.Baz = [MYNS.Foo] +.fi + +In other words, \*(TC always prepends the current namespace (plus a +separating period) to any variable assignment or reference it finds in +a configuration. The one exception is when the namespace is "", in +which case \*(TC does not add anything to the variable names. + +By default, the initial namespace is always "", but you can change this +as desired. Note, however, that the programmer can change this default +initial namespace if they wish. Also there is a programming option +(\'AllowNewNamespaces\', described in the previous section) which can limit +you to using only the namespaces the programmer has pre-defined. i.e., You +will not be able to create +.B new +namespaces of your own invention. + +You can change to a new namespace anytime within the configuration file +a number of ways: + + +So how do you access variables in a namespace different than the one +you are in currently? + + +What's the point of all this namespace business anyway? + + +.SS Pre-Defined Variables .SS The \'.include\' Directive @@ -854,8 +1049,6 @@ .SS Type And Value Enforcement -.SS Lexical Namespaces - .SH ADVANCED TOPICS .SS Guaranteeing A Correct Base Configuration @@ -880,13 +1073,13 @@ latest release from: .nf - http://www.tundraware.com/Software/tconfpy/ + http://www.tundraware.com/Software/tconfpy/ .fi Then unpack the contents by issuing the following command: .nf - tar -xzvf py-tconfpy-X.XXX.tar.gz (where X.XXX is the version number) + tar -xzvf py-tconfpy-X.XXX.tar.gz (where X.XXX is the version number) .fi Win32 users who do not have tar installed on their system can find @@ -952,8 +1145,8 @@ Make sure you are logged in as root, then: .nf - cd /usr/ports/devel/py-tconfpy - make install + cd /usr/ports/devel/py-tconfpy + make install .fi This is a fully-automated install that puts both code and @@ -962,7 +1155,7 @@ various formats) in: .nf - /usr/local/share/doc/py-tconfpy + /usr/local/share/doc/py-tconfpy .fi The 'man' pages will have been properly installed so either of these