The GCI command grammar describes a language for interacting with applications, files, and processes on a modern Linux desktop. This document describes that language. Where unspecified, things in GCI happen like in Python (e.g. string concatenation with +, equality testing with ==, etc.).
GCI's primary goal is simple interaction with objects in the system, so we begin by describing the basic type of statement made in GCI. An unbound statement in GCI is a statement that executes in a semantic grammar context determined by the type of the first argument. Types are marked using a prefix: notation. For instance, take the hypothetical command line:
app:xserver set resolution 1280x1024
When evaluating this statement, GCI sees that the first argument's type is marked as 'app'. GCI delegates parsing of this line to the 'app' schema, which uses d-bus to request the set of verbs available for the 'xserver' application. Perhaps {set,get,restart} is returned. When the user types one of these, GCI uses d-bus to request the semantic grammar for that verb. 'xserver' might return a string over d-bus containing the following grammar:
@set := resolution ("1280x1024"|"1024x768"|"800x600")
| depth ("24"|"16"|"8")
The returned semantic grammar is used for completion and grammaticality tests on the rest of the line. Thus, GCI knows that 'resolution' and 'depth' can be accepted as the first argument, and which options are valid for each of those fields. When the user presses enter, the command is executed by calling the 'set' method over d-bus.
In the example above, we used the app: prefix to mark the otherwise ambiguous word "xserver" as a d-bus communicable application name. This and two other executable object types are described below:
app:
Used to communicate with applications over dbus. Examples:
app:xserver set resolution 1280x1024
app:nautilus empty trash
app:ooffice new text document "Document 1.doc"
app:firefox download http://www.stanford.edu/index.html as stanford.html
sh:
A unix process invocation. The process is invoked in the current sh-context, which includes the current directory and environmental variables. Examples:
sh:ls
sh:gedit --new-window *.py
sh:cd ~
file:
A path to a file on disk. This type prefix is affected by the current directory. What actions are available for that file are determined by lookup against its mimetype. Actual implementation of these commands will probably be over d-bus. Examples:
file:music.mp3 play
file:music.mp3 get artist
file:music.mp3 enqueue
file:README edit
file:README print
The architecture for building new invokable object types could easily be extended to support "sql:select" or other types of magic.
Each statement in GCI returns a value with an associated return type. We can assign the result of a statement to a variable with an assignment expression:
res = {app:xserver get resolution}
listing = {sh: ls -a}
GCI interprets a curly-braced block as an expression to evaluate. After execution, res will contain the current xserver resolution, and listing will contain an iterable of strings (output of ls split on \n). We can use the values in those variables in later expressions:
app:xserver set resolution {res}
All executable object types described above are valid types of variables. This enables statements like:
browser = app:firefox
{browser} show http://www.stanford.edu/
Variables in GCI are dynamically typed. That type might be an executable type (app:, file:, sh:), value type (string, double, int, void), or a collection type (iterable, list, map). Because GCI supports semantic grammars for each expression, these types are strongly enforced when passing arguments to executable object types.
The GCI command language supports control structures in a python-like syntax for affecting the context of the GCI parser or modifying control-flow (looping, branching).
tell @application
Chooses an application to receive a list of commands. Accepts a (possibly in-lined) block.
tell xserver:
set resolution 1280x1024
set depth 24
tell xserver set resolution 1280x1024
Because tell expects its argument to be an 'app:', we can optionally leave off the 'app:' prefix of 'xserver'.
file @file [as @type]
Sets the context to be commands you can run on the given file. Accepts a (possibly in-lined) block.
file music.mp3:
set artist "RJD2"
play in mplayer
file music.mp3 as text/html print # a bad idea, but why not?
Because 'file' expects its argument to be a 'file:', we can optionally leave off the 'file:' prefix of 'music.mp3'. We can specify a mime-type manually if an override is needed (or if the file name comes from a variable not visible until runtime).
exec [in @folder]
Executes a list of system commands.
exec in ~:
ls -al
cp README README.old
for loopvar in @iterable
Loops over a collection type (iterable, list, map
for contact in {app:evolution list contacts}:
app:evolution send mail to {contact} with subject "hi"
for filename in {sh:ls} sh:mv {filename} {filename+".old"}
if @expression
Branches if a condition is true.
if file.exists("a.txt"):
sh:rm a.txt
Enqueue all mp3's by RJD2.
exec in ~/Music:
for filename in {find . -name '*.mp3'}:
if {file:{filename} get artist} == 'RJD2'
file:{filename} enqueue
Send a message to all contacts.
body = {sh:cat body.txt}
subject = "Hi there"
tell evolution:
for contact in {get contacts}:
send message to {contact} with subject {subject} and body {body}