Scripting

A Guile script is simply a file of Scheme code with some ‘extra information at the beginning’ which tells the OS (operating system) how to invoke Guile, and then tells Guile how to handle the Scheme code.

  _ Invoking Guile

It would be beyond the scope of this manual to expose the numerous ways one can define and invoke a Guile script, for a complete description of the subject, see Guile Scripting in The GNU Guile Reference Manual.

In G-Golf, both provided examples and in this manual, we use the so called ‘for maximum portability’ scripting technique, which is to invoke the shell to execute guile with specified command line arguments.

Here is what we do:

#! /bin/sh
# -*- mode: scheme; coding: utf-8 -*-
exec guile -e main -s "$0" "$@"
!#

In the above, the first line is to specify which shell will be used to interpret the (OS part of the) ‘extra information at the beginning’ of the script.

The second line is optional (and a comment from a shell point of view), that we use it to inform emacs (should you use emacs to edit the file) that despite the ‘extra information at the beginning’ (and the possible lack of filename extension in the script name), it should use the scheme mode as the script editing buffer mode.

The third line tells the shell to execute guile, with the following arguments:

-e main

after reading the script, apply main to command line arguments

-s "$0"

load the source code from "$0" (which by shell rules, is bound to the fullname of the script itself)

"$@"

the command line arguments

Note that the top level script lines may contain other declaration(s), like environment variable definitions. Suppose you would like to be warned if your script uses any deprecated guile functionality. In this case, you add the following export GUILE_WARN_DEPRECATED="detailed" declaration, before the exec guile … call, like this:

#! /bin/sh
# -*- mode: scheme; coding: utf-8 -*-
export GUILE_WARN_DEPRECATED="detailed"
exec guile -e main -s "$0" "$@"
!#

  _ Extra Guile information

Within the context of a G-Golf script, two other things must be taken care of - in addition to the (use-modules (g-golf)) step - so that the script runs fine: (1) set-up Guile so that generic functions are merged; (2) import (all) typelib element(s) at expand load eval time.

In a repl or in scripts, (1) is achieved by importing the (oop goops) module and calling default-duplicate-binding-handler14.

In Guile, (2) is achieved by calling the eval-when syntax15.

Now, bear with us :), since (2) will define generic functions and/or add methods to existing generic functions, we must make sure the (1) not only preceeds (2), but also happens at expand load eval time.

With all the above in mind, here is how the extra Guile information looks like, for our ‘Hello World!’ script example:

(eval-when (expand load eval)
  (use-modules (oop goops))

  (default-duplicate-binding-handler
    '(merge-generics replace warn-override-core warn last))

  (use-modules (g-golf))

  (for-each (lambda (name)
              (gi-import-by-name "Gtk" name))
      '("Application"
        "ApplicationWindow"
         "Button")))

  _ A Hello World! script

Let’s put all this together, and while doing this, enhance a little our original example.

Here is what we propose to do: (a) add a GtkLabel, (b) use a GtkBox and see how to declare its margins and orientation, (c) specify a default width and height for our application window, and (d) see how we can tell the label to horizontally and vertically expand, so it occupies the extra vertical space, while keeping the button to its minimal vertical size.

Joining (1), (2) and the small enhancement, our ‘Hello World!’ script now looks like this:

#! /bin/sh
# -*- mode: scheme; coding: utf-8 -*-
exec guile -e main -s "$0" "$@"
!#


(eval-when (expand load eval)
  (use-modules (oop goops))

  (default-duplicate-binding-handler
    '(merge-generics replace warn-override-core warn last))

  (use-modules (g-golf))

  (for-each (lambda (name)
              (gi-import-by-name "Gtk" name))
      '("Application"
        "ApplicationWindow"
        "Box"
        "Label"
        "Button")))


(define (activate app)
  (let ((window (make <gtk-application-window>
                  #:title "Hello"
                  #:default-width 320
                  #:default-height 240
                  #:application app))
        (box    (make <gtk-box>
                  #:margin-top 6
                  #:margin-start 12
                  #:margin-bottom 6
                  #:margin-end 6
                  #:orientation 'vertical))
        (label  (make <gtk-label>
                  #:label "Hello, World!"
                  #:hexpand #t
                  #:vexpand #t))
        (button (make <gtk-button>
                  #:label "Close")))

    (connect button
	     'clicked
	     (lambda (b)
               (close window)))

    (set-child window box)
    (append box label)
    (append box button)
    (show window)))


(define (main args)
  (let ((app (make <gtk-application>
               #:application-id "org.gtk.example")))
    (connect app 'activate activate)
    (let ((status (run app 0 '())))
      (exit status))))

If you save the above in a file, say hello-world, then chmod a+x hello-world and launch the script, ./hello-world, here is what you’ll get on the screen:

hello-world-2

Example 2: Hello World! (2)

  _ A last few comments

We need to make a last few comments, that also applies and will be further addressed in the next section.

Desktop Entry

If you are running a GNOME desktop, you probably noticed that in the GNOME menu bar, the application menu entry for our ‘Hello World!’ script is org.gtk.example (not Hello). This is because we’re missing a Desktop Entry. We will see how to create and install a Desktop Entry in the next section.

Command Line Arguments

As described in the first part of this section, we use the so called ‘for maximum portability’ scripting technique, and more precisely, the following incantation:

exec guile -e main -s "$0" "$@"

In the above, the last argument refers to the the command line arguments. It is actually optional, but when used, they are passed to the main (entry point) script procedure.

However, as you may have noticed, we do not pass those (if any) to the Gtk application, which we launch using (run app 0 '()).

This is intentional: (a) we (want to) always use the same incantation to invoke Guile - and sometimes. may quiclky hack something using additional debug args on the scheme side only …; (b) you may only pass those arguments to the Gtk application if you have defined the signal callback(s) to handle them.

If you pass the command line arguments to a Gtk application that does not define the appropriate signal callback procedure to handle them, you’ll get an error message in the terminal (and the application won’t be launched).

To illustrate, let’s change the g-application-run call of our script, so it becomes (run app (length args) args), then try to launch it, passing a few (fake) arguments, here is what happens:

./hello-world 1 2 3
-| (hello-world:216198): GLib-GIO-CRITICAL **: 22:26:41.135: This application can not open files.

And as mentioned above, the application is not launched.

Although scripts may (also) accept and pass command line argument(s) to the Gtk application or dialog they define, we will see how to handle those in the next section, Building Applications.


Footnotes

(14)

As seen in Configuring Guile for G-Golf (and in GOOPS Notes and Conventions - ’Merging Generics’).

(15)

See Eval-when in The GNU Guile Reference Manual for a complete description.