Console, Pyton, and C interface

Alice supports different usage modes. By default the code is compiled to a stand-alone executable. But the same source code can also be compiled into a Python module or C library.

  • Console mode: the source code is compiled as executable and a stand-alone program is offering a console shell interface, which accepts commands.
  • Python mode: the source code is compiled as Python module which offers an API according to the commands. Commands become function names, and command arguments become function arguments.
  • C library mode: the source code is compiled as C library with functions call commands. This is useful to interface with Alice CLIs from other languages.

Which mode is taken is determined by compilation. No changes in the source code are necessary, when making use of the Macro API.

Let’s say we have a source file shell.cpp, which defines an alice CLI as explained in the tutorials of this manual. Then add the following lines into a CMakeLists.txt file in order to compile it as an executable in console mode:

add_executable(shell shell.cpp)
target_link_libraries(shell alice)

The same file can be compiled as a Python module with the following command:

add_alice_python_module(shell shell.cpp)

This creates a compile target shell_python.

In order to compile it as C library one uses the following commands:

add_alice_c_library(shell shell.cpp)

This creates a compile target shell_c.

Note

The name of the Python module must equal the name of the prefix that was used in the ALICE_MAIN macro. Our example file shell.cpp must finish with ALICE_MAIN(shell).

Shell commands as Python functions

If the alice shell has the prefix shell, then the corresponding Python module has the name shell and can be imported as follows:

import shell

Commands are mapped into Python functions with the same name. Assume there is a command called command, then one can call it from Python as follows:

import shell
shell.command()

Long option and flag names are mapped into keyword arguments of the corresponding Python command. Assume that the command command has the following synopsis:

shell> command -h
A test command
Usage: command [OPTIONS]

Options:
  -h,--help                   Print this help message and exit
  -s,--sopt TEXT              A string option
  -n,--nopt INT               An integer option
  -f,--flag                   A flag

Then the individual arguments in this command can be called in Python mode as follows:

import shell
shell.command(sopt = "Some text", nopt = 42, flag = True)

The order in which the keyword arguments are passed does not matter; also, not all of them need to be provided. Note again, that the short option and flag names cannot be used in Python mode. Also flags must be assigned a Boolean value. Assigning False to a flag argument is as omitting it.

The return value of a Python function corresponds to the logging output of the corresponding command. Each command can contribute to the log by implementing the log() function. It returns a JSON object. The return value of the function in Python mode can be considered as a Python dict, in which the entries correspond to the JSON object.

Assume that the example command command implements the following log() function:

nlohmann::json log() const
{
  return nlohmann::json({
    {"str", "Some string"},
    {"number", 42}
  });
}

Then one can access these values from the return value of the Python function:

import shell
r = shell.command()
print(r["number"])    # Prints 42

C library

Assuming that the alice shell has the prefix shell, then the C library will implement the following three functions:

extern void* shell_create();
extern void shell_delete( void* cli );
extern int shell_command( void* cli, const char* command, char* log, size_t size );

The prefix is being used as prefix for the C functions. By copying the above three lines into a C file and linking to the compiled C library allows to interact with the alice CLI shell.

The first two functions shell_create and shell_delete create and delete a CLI object. Note that the object is passed as void*. The third function calls a single command. The first argument is a pointer to a CLI object and the second argument is the command as string. The third argument is a string pointer which can be passed to store the JSON log produced by the command; it can also be null. If not null, the last argument should contain the maximum size of the log string. The function returns -1, if the command was not executed successfully, 0, if the command was executed successfully, but nothing was written into the log string, and otherwise the actual size of the JSON string. The actual size may be longer than size.

Being a C library, it can also be used in other languages, e.g., in C#. In the next example, we assume that the library has been compiled on a Linux machine and has the name libshell_c.so:

using System;
using System.Runtime.InteropServices;
using System.Text;

public class Library {
  [DllImport("libshell_c.dylib", EntryPoint = "shell_create")]
  public static extern IntPtr shell_create();

  [DllImport("libshell_c.dylib", EntryPoint = "shell_delete")]
  public static extern void shell_delete(IntPtr cli);

  [DllImport("libshell_c.dylib", EntryPoint = "shell_command")]
  public static extern int shell_command(IntPtr cli, string command, StringBuilder json, int size);
}