Module clingo.application

Functions and classes to implement applications based on clingo.

Examples

The following example reproduces the default clingo application:

import sys
from clingo.application import Application, clingo_main

class ClingoApp(Application):
    def __init__(self, name):
        self.program_name = name

    def main(self, ctl, files):
        for f in files:
            ctl.load(f)
        if not files:
            ctl.load("-")
        ctl.ground([("base", [])])
        ctl.solve()

clingo_main(ClingoApp(sys.argv[0]), sys.argv[1:])
Expand source code
'''
Functions and classes to implement applications based on clingo.

Examples
--------
The following example reproduces the default clingo application:

    import sys
    from clingo.application import Application, clingo_main

    class ClingoApp(Application):
        def __init__(self, name):
            self.program_name = name

        def main(self, ctl, files):
            for f in files:
                ctl.load(f)
            if not files:
                ctl.load("-")
            ctl.ground([("base", [])])
            ctl.solve()

    clingo_main(ClingoApp(sys.argv[0]), sys.argv[1:])
'''

from typing import Any, Callable, List, Optional, Sequence
from abc import ABCMeta, abstractmethod
import sys

from ._internal import _cb_error_print, _cb_error_panic, _ffi, _handle_error, _lib, _overwritten, _to_str
from .core import MessageCode
from .solving import Model
from .control import Control

__all__ = [ 'Application', 'ApplicationOptions', 'Flag', 'clingo_main' ]

class Flag:
    '''
    Helper object to parse command-line flags.

    Parameters
    ----------
    value
        The initial value of the flag.
    '''
    def __init__(self, value: bool=False):
        self._flag = _ffi.new('bool*', value)

    def __bool__(self):
        return self.flag

    @property
    def flag(self) -> bool:
        '''
        The value of the flag.
        '''
        return self._flag[0]

    @flag.setter
    def flag(self, value: bool):
        self._flag[0] = value

class ApplicationOptions(metaclass=ABCMeta):
    '''
    Object to add custom options to a clingo based application.
    '''
    def __init__(self, rep, mem):
        self._rep = rep
        self._mem = mem

    def add(self, group: str, option: str, description: str, parser: Callable[[str], bool],
            multi: bool=False, argument: Optional[str]=None) -> None:
        '''
        Add an option that is processed with a custom parser.

        Parameters
        ----------
        group
            Options are grouped into sections as given by this string.
        option
            Parameter option specifies the name(s) of the option. For example,
            `"ping,p"` adds the short option `-p` and its long form `--ping`. It is
            also possible to associate an option with a help level by adding `",@l"` to
            the option specification. Options with a level greater than zero are only
            shown if the argument to help is greater or equal to `l`.
        description
            The description of the option shown in the help output.
        parser
            An option parser is a function that takes a string as input and returns
            true or false depending on whether the option was parsed successively.
        multi
            Whether the option can appear multiple times on the command-line.
        argument
            Optional string to change the value name in the generated help.

        Notes
        -----
        The parser callback has to take care of storing the semantic value of
        the option somewhere.
        '''
        # pylint: disable=protected-access
        c_data = _ffi.new_handle(parser)
        self._mem.append(c_data)

        _handle_error(_lib.clingo_options_add(
            self._rep, group.encode(), option.encode(), description.encode(),
            _lib.pyclingo_application_options_parse, c_data,
            multi, argument.encode() if argument is not None else _ffi.NULL))

    def add_flag(self, group: str, option: str, description: str, target: Flag) -> None:
        '''
        Add an option that is a simple flag.

        This function is similar to `ApplicationOptions.add` but simpler because
        it only supports flags, which do not have values. Note that the target
        parameter must be of type Flag, which is set to true if the flag is passed on
        the command line.

        Parameters
        ----------
        group
            Options are grouped into sections as given by this string.
        option
            Same as for `ApplicationOptions.add`.
        description
            The description of the option shown in the help output.
        target
            The object that receives the value.
        '''
        # pylint: disable=protected-access
        self._mem.append(target)
        _handle_error(_lib.clingo_options_add_flag(
            self._rep, group.encode(), option.encode(), description.encode(),
            target._flag))

@_ffi.def_extern(onerror=_cb_error_panic, name='pyclingo_application_options_parse')
def _pyclingo_application_options_parse(value, data):
    return _ffi.from_handle(data)(_to_str(value))

class Application(metaclass=ABCMeta):
    '''
    Interface that has to be implemented to customize clingo.
    '''
    program_name: str
    '''
    Program name defaulting to `'clingo'` used in the help output.
    '''
    version: str
    '''
    Version string defaulting to clingo's version.
    '''
    message_limit: int
    '''
    Maximum number of messages defaulting to `20` passed to the logger.
    '''

    @abstractmethod
    def main(self, control: Control, files: Sequence[str]) -> None:
        '''
        Function to replace clingo's default main function.

        This function must be implemented.

        Parameters
        ----------
        control
            The main control object.
        files
            The files passed to clingo_main.
        '''

    def register_options(self, options: ApplicationOptions) -> None:
        '''
        Function to register custom options.

        Parameters
        ----------
        options
            Object to register additional options
        '''

    def validate_options(self) -> bool:
        '''
        Function to validate custom options.

        Returns
        -------
        This function should return false if option validation fails.
        '''

    def print_model(self, model: Model, printer: Callable[[], None]) -> None:
        '''
        Function to print additional information when the text output is used.

        Parameters
        ----------
        model
            The current model
        printer
            The default printer as used in clingo.
        '''

    def logger(self, code: MessageCode, message: str) -> None:
        '''
        Function to intercept messages normally printed to standard error.

        By default, messages are printed to standard error.

        Parameters
        ----------
        code
            The message code.
        message
            The message string.

        Notes
        -----
        This function should not raise exceptions.
        '''

def clingo_main(application: Application, arguments: Optional[Sequence[str]]=None) -> int:
    '''
    Runs the given application using clingo's default output and signal handling.

    The application can overwrite clingo's default behaviour by registering
    additional options and overriding its default main function.

    Parameters
    ----------
    application
        The application object.
    arguments
        The command line arguments excluding the program name.

        If omitted, then `sys.argv[1:]` is used.

    Returns
    -------
    The exit code of the application.

    Notes
    -----
    The main function of the `Application` interface has to be implemented. All
    other members are optional.
    '''
    if arguments is None:
        arguments = sys.argv[1:]

    # pylint: disable=dangerous-default-value,protected-access,line-too-long
    c_application = _ffi.new('clingo_application_t*', (
        _lib.pyclingo_application_program_name if _overwritten(Application, application, "program_name") else _ffi.NULL,
        _lib.pyclingo_application_version if _overwritten(Application, application, "version") else _ffi.NULL,
        _lib.pyclingo_application_message_limit if _overwritten(Application, application, "message_limit") else _ffi.NULL,
        _lib.pyclingo_application_main if _overwritten(Application, application, "main") else _ffi.NULL,
        _lib.pyclingo_application_logger if _overwritten(Application, application, "logger") else _ffi.NULL,
        _lib.pyclingo_application_print_model if _overwritten(Application, application, "print_model") else _ffi.NULL,
        _lib.pyclingo_application_register_options if _overwritten(Application, application, "register_options") else _ffi.NULL,
        _lib.pyclingo_application_validate_options if _overwritten(Application, application, "validate_options") else _ffi.NULL))

    mem: List[Any] = []
    c_data = _ffi.new_handle((application, mem))

    return _lib.clingo_main(
        c_application,
        [ _ffi.new('char[]', arg.encode()) for arg in arguments ], len(arguments),
        c_data)

@_ffi.def_extern(onerror=_cb_error_panic, name='pyclingo_application_program_name')
def _pyclingo_application_program_name(data):
    app, mem = _ffi.from_handle(data)
    mem.append(_ffi.new('char[]', app.program_name.encode()))
    return mem[-1]

@_ffi.def_extern(onerror=_cb_error_panic, name='pyclingo_application_version')
def _pyclingo_application_version(data):
    app, mem = _ffi.from_handle(data)
    mem.append(_ffi.new('char[]', app.version.encode()))
    return mem[-1]

@_ffi.def_extern(onerror=_cb_error_panic, name='pyclingo_application_message_limit')
def _pyclingo_application_message_limit(data):
    app = _ffi.from_handle(data)[0]
    return app.message_limit

@_ffi.def_extern(onerror=_cb_error_panic, name='pyclingo_application_logger')
def _pyclingo_application_logger(code, message, data):
    app = _ffi.from_handle(data)[0]
    return app.logger(MessageCode(code), _to_str(message))

@_ffi.def_extern(onerror=_cb_error_print, name='pyclingo_application_main')
def _pyclingo_application_main(control, files, size, data):
    app = _ffi.from_handle(data)[0]
    app.main(Control(control), [ _to_str(files[i]) for i in range(size) ])
    return True

@_ffi.def_extern(onerror=_cb_error_print, name='pyclingo_application_print_model')
def _pyclingo_application_print_model(model, printer, printer_data, data):
    def py_printer():
        _handle_error(printer(printer_data))
    app = _ffi.from_handle(data)[0]
    app.print_model(Model(model), py_printer)
    return True

@_ffi.def_extern(onerror=_cb_error_panic, name='pyclingo_application_register_options')
def _pyclingo_application_register_options(options, data):
    app, mem = _ffi.from_handle(data)
    app.register_options(ApplicationOptions(options, mem))
    return True

@_ffi.def_extern(onerror=_cb_error_panic, name='pyclingo_application_validate_options')
def _pyclingo_application_validate_options(data):
    app = _ffi.from_handle(data)[0]
    return app.validate_options()

Functions

def clingo_main(application: Application, arguments: Optional[Sequence[str]] = None) ‑> int

Runs the given application using clingo's default output and signal handling.

The application can overwrite clingo's default behaviour by registering additional options and overriding its default main function.

Parameters

application
The application object.
arguments

The command line arguments excluding the program name.

If omitted, then sys.argv[1:] is used.

Returns

The exit code of the application.

Notes

The main function of the Application interface has to be implemented. All other members are optional.

Expand source code
def clingo_main(application: Application, arguments: Optional[Sequence[str]]=None) -> int:
    '''
    Runs the given application using clingo's default output and signal handling.

    The application can overwrite clingo's default behaviour by registering
    additional options and overriding its default main function.

    Parameters
    ----------
    application
        The application object.
    arguments
        The command line arguments excluding the program name.

        If omitted, then `sys.argv[1:]` is used.

    Returns
    -------
    The exit code of the application.

    Notes
    -----
    The main function of the `Application` interface has to be implemented. All
    other members are optional.
    '''
    if arguments is None:
        arguments = sys.argv[1:]

    # pylint: disable=dangerous-default-value,protected-access,line-too-long
    c_application = _ffi.new('clingo_application_t*', (
        _lib.pyclingo_application_program_name if _overwritten(Application, application, "program_name") else _ffi.NULL,
        _lib.pyclingo_application_version if _overwritten(Application, application, "version") else _ffi.NULL,
        _lib.pyclingo_application_message_limit if _overwritten(Application, application, "message_limit") else _ffi.NULL,
        _lib.pyclingo_application_main if _overwritten(Application, application, "main") else _ffi.NULL,
        _lib.pyclingo_application_logger if _overwritten(Application, application, "logger") else _ffi.NULL,
        _lib.pyclingo_application_print_model if _overwritten(Application, application, "print_model") else _ffi.NULL,
        _lib.pyclingo_application_register_options if _overwritten(Application, application, "register_options") else _ffi.NULL,
        _lib.pyclingo_application_validate_options if _overwritten(Application, application, "validate_options") else _ffi.NULL))

    mem: List[Any] = []
    c_data = _ffi.new_handle((application, mem))

    return _lib.clingo_main(
        c_application,
        [ _ffi.new('char[]', arg.encode()) for arg in arguments ], len(arguments),
        c_data)

Classes

class Application

Interface that has to be implemented to customize clingo.

Expand source code
class Application(metaclass=ABCMeta):
    '''
    Interface that has to be implemented to customize clingo.
    '''
    program_name: str
    '''
    Program name defaulting to `'clingo'` used in the help output.
    '''
    version: str
    '''
    Version string defaulting to clingo's version.
    '''
    message_limit: int
    '''
    Maximum number of messages defaulting to `20` passed to the logger.
    '''

    @abstractmethod
    def main(self, control: Control, files: Sequence[str]) -> None:
        '''
        Function to replace clingo's default main function.

        This function must be implemented.

        Parameters
        ----------
        control
            The main control object.
        files
            The files passed to clingo_main.
        '''

    def register_options(self, options: ApplicationOptions) -> None:
        '''
        Function to register custom options.

        Parameters
        ----------
        options
            Object to register additional options
        '''

    def validate_options(self) -> bool:
        '''
        Function to validate custom options.

        Returns
        -------
        This function should return false if option validation fails.
        '''

    def print_model(self, model: Model, printer: Callable[[], None]) -> None:
        '''
        Function to print additional information when the text output is used.

        Parameters
        ----------
        model
            The current model
        printer
            The default printer as used in clingo.
        '''

    def logger(self, code: MessageCode, message: str) -> None:
        '''
        Function to intercept messages normally printed to standard error.

        By default, messages are printed to standard error.

        Parameters
        ----------
        code
            The message code.
        message
            The message string.

        Notes
        -----
        This function should not raise exceptions.
        '''

Class variables

var message_limit : int

Maximum number of messages defaulting to 20 passed to the logger.

var program_name : str

Program name defaulting to 'clingo' used in the help output.

var version : str

Version string defaulting to clingo's version.

Methods

def logger(self, code: MessageCode, message: str) ‑> None

Function to intercept messages normally printed to standard error.

By default, messages are printed to standard error.

Parameters

code
The message code.
message
The message string.

Notes

This function should not raise exceptions.

Expand source code
def logger(self, code: MessageCode, message: str) -> None:
    '''
    Function to intercept messages normally printed to standard error.

    By default, messages are printed to standard error.

    Parameters
    ----------
    code
        The message code.
    message
        The message string.

    Notes
    -----
    This function should not raise exceptions.
    '''
def main(self, control: Control, files: Sequence[str]) ‑> None

Function to replace clingo's default main function.

This function must be implemented.

Parameters

control
The main control object.
files
The files passed to clingo_main.
Expand source code
@abstractmethod
def main(self, control: Control, files: Sequence[str]) -> None:
    '''
    Function to replace clingo's default main function.

    This function must be implemented.

    Parameters
    ----------
    control
        The main control object.
    files
        The files passed to clingo_main.
    '''
def print_model(self, model: Model, printer: Callable[[], None]) ‑> None

Function to print additional information when the text output is used.

Parameters

model
The current model
printer
The default printer as used in clingo.
Expand source code
def print_model(self, model: Model, printer: Callable[[], None]) -> None:
    '''
    Function to print additional information when the text output is used.

    Parameters
    ----------
    model
        The current model
    printer
        The default printer as used in clingo.
    '''
def register_options(self, options: ApplicationOptions) ‑> None

Function to register custom options.

Parameters

options
Object to register additional options
Expand source code
def register_options(self, options: ApplicationOptions) -> None:
    '''
    Function to register custom options.

    Parameters
    ----------
    options
        Object to register additional options
    '''
def validate_options(self) ‑> bool

Function to validate custom options.

Returns

This function should return false if option validation fails.

Expand source code
def validate_options(self) -> bool:
    '''
    Function to validate custom options.

    Returns
    -------
    This function should return false if option validation fails.
    '''
class ApplicationOptions (rep, mem)

Object to add custom options to a clingo based application.

Expand source code
class ApplicationOptions(metaclass=ABCMeta):
    '''
    Object to add custom options to a clingo based application.
    '''
    def __init__(self, rep, mem):
        self._rep = rep
        self._mem = mem

    def add(self, group: str, option: str, description: str, parser: Callable[[str], bool],
            multi: bool=False, argument: Optional[str]=None) -> None:
        '''
        Add an option that is processed with a custom parser.

        Parameters
        ----------
        group
            Options are grouped into sections as given by this string.
        option
            Parameter option specifies the name(s) of the option. For example,
            `"ping,p"` adds the short option `-p` and its long form `--ping`. It is
            also possible to associate an option with a help level by adding `",@l"` to
            the option specification. Options with a level greater than zero are only
            shown if the argument to help is greater or equal to `l`.
        description
            The description of the option shown in the help output.
        parser
            An option parser is a function that takes a string as input and returns
            true or false depending on whether the option was parsed successively.
        multi
            Whether the option can appear multiple times on the command-line.
        argument
            Optional string to change the value name in the generated help.

        Notes
        -----
        The parser callback has to take care of storing the semantic value of
        the option somewhere.
        '''
        # pylint: disable=protected-access
        c_data = _ffi.new_handle(parser)
        self._mem.append(c_data)

        _handle_error(_lib.clingo_options_add(
            self._rep, group.encode(), option.encode(), description.encode(),
            _lib.pyclingo_application_options_parse, c_data,
            multi, argument.encode() if argument is not None else _ffi.NULL))

    def add_flag(self, group: str, option: str, description: str, target: Flag) -> None:
        '''
        Add an option that is a simple flag.

        This function is similar to `ApplicationOptions.add` but simpler because
        it only supports flags, which do not have values. Note that the target
        parameter must be of type Flag, which is set to true if the flag is passed on
        the command line.

        Parameters
        ----------
        group
            Options are grouped into sections as given by this string.
        option
            Same as for `ApplicationOptions.add`.
        description
            The description of the option shown in the help output.
        target
            The object that receives the value.
        '''
        # pylint: disable=protected-access
        self._mem.append(target)
        _handle_error(_lib.clingo_options_add_flag(
            self._rep, group.encode(), option.encode(), description.encode(),
            target._flag))

Methods

def add(self, group: str, option: str, description: str, parser: Callable[[str], bool], multi: bool = False, argument: Optional[str] = None) ‑> None

Add an option that is processed with a custom parser.

Parameters

group
Options are grouped into sections as given by this string.
option
Parameter option specifies the name(s) of the option. For example, "ping,p" adds the short option -p and its long form --ping. It is also possible to associate an option with a help level by adding ",@l" to the option specification. Options with a level greater than zero are only shown if the argument to help is greater or equal to l.
description
The description of the option shown in the help output.
parser
An option parser is a function that takes a string as input and returns true or false depending on whether the option was parsed successively.
multi
Whether the option can appear multiple times on the command-line.
argument
Optional string to change the value name in the generated help.

Notes

The parser callback has to take care of storing the semantic value of the option somewhere.

Expand source code
def add(self, group: str, option: str, description: str, parser: Callable[[str], bool],
        multi: bool=False, argument: Optional[str]=None) -> None:
    '''
    Add an option that is processed with a custom parser.

    Parameters
    ----------
    group
        Options are grouped into sections as given by this string.
    option
        Parameter option specifies the name(s) of the option. For example,
        `"ping,p"` adds the short option `-p` and its long form `--ping`. It is
        also possible to associate an option with a help level by adding `",@l"` to
        the option specification. Options with a level greater than zero are only
        shown if the argument to help is greater or equal to `l`.
    description
        The description of the option shown in the help output.
    parser
        An option parser is a function that takes a string as input and returns
        true or false depending on whether the option was parsed successively.
    multi
        Whether the option can appear multiple times on the command-line.
    argument
        Optional string to change the value name in the generated help.

    Notes
    -----
    The parser callback has to take care of storing the semantic value of
    the option somewhere.
    '''
    # pylint: disable=protected-access
    c_data = _ffi.new_handle(parser)
    self._mem.append(c_data)

    _handle_error(_lib.clingo_options_add(
        self._rep, group.encode(), option.encode(), description.encode(),
        _lib.pyclingo_application_options_parse, c_data,
        multi, argument.encode() if argument is not None else _ffi.NULL))
def add_flag(self, group: str, option: str, description: str, target: Flag) ‑> None

Add an option that is a simple flag.

This function is similar to ApplicationOptions.add() but simpler because it only supports flags, which do not have values. Note that the target parameter must be of type Flag, which is set to true if the flag is passed on the command line.

Parameters

group
Options are grouped into sections as given by this string.
option
Same as for ApplicationOptions.add().
description
The description of the option shown in the help output.
target
The object that receives the value.
Expand source code
def add_flag(self, group: str, option: str, description: str, target: Flag) -> None:
    '''
    Add an option that is a simple flag.

    This function is similar to `ApplicationOptions.add` but simpler because
    it only supports flags, which do not have values. Note that the target
    parameter must be of type Flag, which is set to true if the flag is passed on
    the command line.

    Parameters
    ----------
    group
        Options are grouped into sections as given by this string.
    option
        Same as for `ApplicationOptions.add`.
    description
        The description of the option shown in the help output.
    target
        The object that receives the value.
    '''
    # pylint: disable=protected-access
    self._mem.append(target)
    _handle_error(_lib.clingo_options_add_flag(
        self._rep, group.encode(), option.encode(), description.encode(),
        target._flag))
class Flag (value: bool = False)

Helper object to parse command-line flags.

Parameters

value
The initial value of the flag.
Expand source code
class Flag:
    '''
    Helper object to parse command-line flags.

    Parameters
    ----------
    value
        The initial value of the flag.
    '''
    def __init__(self, value: bool=False):
        self._flag = _ffi.new('bool*', value)

    def __bool__(self):
        return self.flag

    @property
    def flag(self) -> bool:
        '''
        The value of the flag.
        '''
        return self._flag[0]

    @flag.setter
    def flag(self, value: bool):
        self._flag[0] = value

Instance variables

var flag : bool

The value of the flag.

Expand source code
@property
def flag(self) -> bool:
    '''
    The value of the flag.
    '''
    return self._flag[0]