Cfpeek User Manual (split by section):   Section:   Chapter:FastBack: Tutorial   Up: Tutorial   FastForward: Formats   Contents: Table of ContentsIndex: Concept Index

3.9 Using Scripts

Cfpeek offers a scripting facility, which can be used to easily extend its functionality beyond the basic operations, described in previous chapters. Scripts must be written in Scheme, using ‘Guile’, the GNU’s Ubiquitous Intelligent Language for Extensions. For information about the language, refer to Revised(5) Report on the Algorithmic Language Scheme. For a detailed description of Guile and its features, see Overview in The Guile Reference Manual.

This section assumes that the reader has sufficient knowledge about this programming language.

The scripting facility is enabled by the use of the --expression (-e) of --file (-f command line options. The --expression (-e) option takes as its argument a Scheme expression, which will be executed for each statement matching the supplied keys (or for each statement in the tree, if no keys were supplied). The expression can obtain information about the statement from the global variable node, which represents a node in the parse tree describing this statement. The node contains complete information about the statement, including its location in the source file, its type and neighbor nodes, etc. A number of functions is provided to retrieve that information from the node. These functions are discussed in detail in Scripting.

Let’s start from the simplest example. The following command prints all nodes in the file:

$ cfpeek --expression='(display node)(newline)' sample.conf
#<node .user: "smith">
#<node .group: "mail">
#<node .pidfile: "/var/run/example">
#<node .logging.facility: "daemon">
#<node .logging.tag: "example">
#<node .program="a".command: "a.out">
#<node .program="a".logging.facility: "local0">
#<node .program="a".logging.tag: "a">
#<node .program="b".command: "b.out">
#<node .program="b".wait: "yes">
#<node .program="b".pidfile: "/var/run/b.pid">

The format shown in this example is the default Scheme representation for nodes. You can use accessor functions to format the output to your liking. For instance, the function ‘grecs-node-locus’ returns the location of the node in the input file. The returned value is a cons, with the file name as its car and the line number as its cdr. Thus, you can print statement locations with the following command:

cfpeek --expr='(let ((loc grecs-node-locus)) 
                 (format #t "~A:~A~%"
                  (car loc) (cdr loc)))' \
       sample.conf

Complex expressions are cumbersome to type in the command line, therefore the --file (-f) option is provided. This option takes the name of the script file as its argument. This file must define the function named cfpeek which takes a node as its argument. The script file is then loaded and the cfpeek function is called for each matching node.

Now, if we put the expression used in the previous example in a script file (e.g. locus.scm):

(define (cfpeek node)
  (let ((loc grecs-node-locus))
    (format #t "~A:~A~%" (car loc) (cdr loc))))

then the example can be rewritten as:

$ cfpeek -f locus.scm sample.conf

When both --file and --expression options are used in the same invocation, the cfpeek function is not invoked by default. In fact, it even does not need to be defined. When used this way, cfpeek first loads the requested script file, and then applies the expression to each matching node, the same way it always does when --expression is supplied. It is the responsibility of the expression itself to call any function or functions defined in the file. This way of invoking ‘cfpeek’ is useful for supplying additional parameters to the script. For example:

$ cfpeek -f script.scm -e '(process-node node #t)' input.conf

It is supposed that the function process-node is defined somewhere in script.scm and takes two arguments: a node and a boolean.

The --init=expr (-i expr) option provides an initialization expression expr. This expression is evaluated once, after loading the script file, if one is specified, and before starting the main loop.

Similarly, the option --done=expr (-d expr) introduces a Scheme expression to be evaluated at the end of the run, after all nodes have been processed.

3.9.1 Example: Converter to GIT Configuration Format

Here is a more practical example of Scheme scripting. This script converts entire parse tree into a GIT configuration file format. The format itself is described in git.

The script traverses entire tree itself, so it must be called only once, for the root node of the parse tree. The root node is denoted by a single dot, so the invocation syntax is:

cfpeek -f togit.scm sample.conf .

Traversal is performed by the main function, cfpeek, using the grecs-node-next and grecs-node-down functions. The grecs-node-next function returns a node which follows its argument at the same nesting level. For example, if n is the very first node in our sample parse tree, then:

n ⇒ #<node .user: "smith">
(grecs-node-next n) ⇒ #<node .group: "mail">

Similarly, the grecs-node-down function returns the first subordinate node of its argument. For example:

n ⇒ #<node .logging>
(grecs-node-down n) ⇒ #<node .logging.facility: "daemon">

Both functions return ‘#f’ if there are no next or subordinate node, correspondingly.

The grecs-node-type function is used to determine how to handle that particular node. It returns a type of the node given to it as argument. The type is an integer constant, with the following possible values:

TypeThe node is
grecs-node-rootthe root (topmost) node
grecs-node-stmta simple statement
grecs-node-blocka compound (block) statement

The print-section function prints a GIT section header corresponding to its node. It ascends the parent node chain to find the topmost node and prints the traversed nodes in the correct order.

To summarize, here is the listing of the togit.scm script:

(define (print-section node delim)
  "Print a Git section header for the given node.
End it with delim.

The function recursively calls itself until the topmost
node is reached.
"
  (cond
   ((grecs-node-up? node)
    ;; Ascend to the parent node
    (print-section (grecs-node-up node) #\space)
    ;; Print its identifier, ...
    (display (grecs-node-ident node))
    (if (grecs-node-has-value? node)
        ;; ... value,
        (begin
          (display " ")
          (display (grecs-node-value node))))
    ;; ... and delimiter
    (display delim))
   (else              ;; mark the root node
    (display "["))))  ;;  with a [


(define (cfpeek node)
  "Main entry point.  Calls itself recursively to descend
into subordinate nodes and to iterate over nodes on the
same nesting level (tail recursion)."
  (let loop ((node node))
    (if node
        (let ((type (grecs-node-type node)))
          (cond
           ((= type grecs-node-root)
            (let ((dn (grecs-node-down node)))
              ;; Each statement in a Git config file must
              ;; belong to a section.  If the first node
              ;; is not a block statement, provide the
              ;; default [core] section:
              (if (not (= (grecs-node-type dn)
                          grecs-node-block))
                  (display "[core]\n"))
              ;; Continue from the first node
              (loop dn)))
           ((= type grecs-node-block)
            ;; print the section header
            (print-section node #\])
            (newline)
            ;; descend into subnodes
            (loop (grecs-node-down node))
            ;; continue from the next node
            (loop (grecs-node-next node)))
           ((= type grecs-node-stmt)
            ;; print the simple statement
            (display #\tab)
            (display (grecs-node-ident node))
            (display " = ")
            (display (grecs-node-value node))
            (newline)
            ;; continue from the next node
            (loop (grecs-node-next node))))))))

If run on our sample configuration file, it produces:

$ cfpeek -f togit.scm sample.conf .
[core]
        user = smith
        group = mail
        pidfile = /var/run/example
[logging]
        facility = daemon
        tag = example
[program a]
        command = a.out
[program a logging]
        facility = local0
        tag = a
[program b]
        command = b.out
        wait = yes
        pidfile = /var/run/b.pid

Cfpeek User Manual (split by section):   Section:   Chapter:FastBack: Tutorial   Up: Scripts   FastForward: Formats   Contents: Table of ContentsIndex: Concept Index