eZ component: ConsoleTools, Design, 1.3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:Author: Tobias Schlitt
:Revision: $Rev$
:Date: $Date$
:Status: Draft

.. contents::

=====
Scope
=====

The scope of this document is to describe the enhancements of the ConsoleTools
component for version 1.3 of the component. The following features are part of
this design document:

- Basic dialog system to interact with a user through STDIN.
- Enhanced handling of arguments (including help output).

This document describes a new range of classes, which will be added to
ConsoleTools, to fulfill this needs.

=============
Dialog system
=============

Design overview
===============

The following section gives a brief introduction of the concept of a simple
dialog system that enables the developer of a console based application to
interact with the user through STDIN.

Clarification of terms
----------------------

User
^^^^

The "user" is the person using a program and interacting with it during
runtime. The user has no knowledge about the internals and must be guided
through the dialog system he is interacting with. The term "user" in case of
this document is _not_ the developer using the described component, but the
user who interacts with the final program.

Developer
^^^^^^^^^

In contrast to the "user", the term "developer" in this document refers to the
person who creates a program, using the described component.

Dialog
^^^^^^

The basic idea of a new mechanism to interact with the user through STDIN in
this document is called a "dialog".  A dialog is presented to the user by a
certain amount of output it generates and a certain amount of input it
requests afterwards.

Basic design
------------

The core of the new feature described in this document is a dialog. The dialog
is an object, must have the following capabilities:

- Presents itself to the user on the command line
- Requests data  from the user through STDIN.
- Validate the result for its purpose.
- Return the result to the developer.

A dialog is usually used in a loop, which displays the dialog over and over again
until it received a valid result.

Class design
============

The following classes will realize the idea of dialogs in ConsoleTools.

ezcConsoleDialog
----------------

This interface describes the external API, which is commonly used by developers
and other classes to interact with a dialog object. Each dialog class must
implement this interface.

__construct( ezcConsoleOutput $output, ezcConsoleDialogOptions $options )
  The constructor of a dialog receives an instance of ezcConsoleOutput, which
  it must use for presenting its output, when requested. In addition, it can
  receive an option object, which must be an instance or subclass instance of
  ezcConsoleDialogOptions.
hasValidResult()
  This method must return a boolean value, which indicates, if the dialog
  already received a result from the user, which is valid in its context.
getResult()
  After the hasValidResult() method returned true, this method will return the
  value received from the dialog.
display()
  Using this method, the developer can instruct the dialog object to display
  itself to the user. The dialog must use the instance of eczConsoleOutput it
  received in the constructor for displaying.
reset()
  This method resets the dialog internally to the state it had directly after
  construction, so that it can be reused by the developer.

In addition to this interface, an implementation of ezcConsoleDialog can
contain a set of static factory methods, which return a dialog in a
pre-configured state (for example a standard yes/no question, where only the
text needs to be set).

ezcConsoleDialogOptions
-----------------------

This is the base option class, which must be accepted by a dialog. It contains
options, which must be valid for each dialog class. A dialog class may request,
that the options it receives are instances of a subclass, if it expects
additional options. Each of the options defined in ezcConsoleDialogOptions must
also be available in this subclass. Every option defined must have a sensible
default value, so that there is no need for the developer to change it.

The base option class provides the following options:

format
  The name of the format this dialog uses to be displayed. The format
  identifier must be defined in the ezcConsoleOutput instance submitted to the
  constructor of the dialog.
validator
  An instance of ezcConsoleDialogValidator. This validator is responsible for
  validation and manipulation of the result a dialog received from the user. An
  implementation of ezcConsoleDialog may require a specific sub-class of
  ezcConsoleDialogValidator here (e.g. for a menu dialog, which requires a
  special validator object to work).

ezcConsoleDialogViewer
----------------------

The ezcConsoleDialogViewer class provides a set of static convenience methods
that can be used by a developer that works with dialogs and by a developer that
creates new dialogs. These methods are:

displayDialog( ezcConsoleDialog $dialog ) [static]
  This method is recommended to be used for displaying dialogs. It performs the
  typical loop: Display the dialog over and over again until it received a
  valid result. When this state is reached, it returns the dialogs value.
readLine( $trim = true ) [static]
  The readLine() method is commonly used in a dialog implementation to read a
  line from standard in. It returns the input provided by a user, optionally
  trimmed of leading and trailing white spaces.

ezcConsoleDialogValidator
-------------------------

The ezcConsoleValidator interface provides signatures for methods that can be used
by a dialog to validate the result it retrieved. The validator is configured by
the user and submitted to the dialog via an option (if a dialog supports this
mechanism).

__construct( ... )
  The signature of the constructor is left to the validator implementation to
  retrieve settings (e.g. the valid results).
validate( mixed $result )
  This method is responsible for validating a given result.  The dialog will
  submit the result it received to this method which will indicate if the
  result was valid by a boolean value. To manipulate / fix a retrieved result,
  the dialog implementation can call the fixup() method.
fixup( mixed $result )
  A validator can try to fix a given result to be valid. This manipulation can
  be omitted by simply returning the result again. An example for a fixup would
  be to convert a date information into a Unix timestamp using strtotime(). The
  validate method would then expect an integer value > 0. The dialog
  implementation is responsible for calling fixup() as it thinks is appropriate
  (e.g. always before calling validate(), only if validate() fails, never).

Dialog implementations
======================

The new feature set comes with a collection of basic dialog implementations,
which will be described in this section.

ezcConsoleQuestionDialog
------------------------

The ezcConsoleQuestionDialog is the most basic imaginable dialog. It asks the
user a simply question and expects a certain answer. A typical output from an
ezcConsoleQuestionDialog object could look like this: ::

    Do you want to continue? (y/n) 

This dialog implementation provides a set of rudimentary options, which can be
used to customize its appearance and enhance its capabilities. For this
purpose, it comes with a custom options class ezcConsoleQuestionDialogOptions,
that accepts the following options in addition to those provided by
ezcConsoleDialogOptions:

text
  This option defines the main "question" text, displayed to the user.
validator
  The validator is an instance of ezcConsoleQuestionDialogValidator. For
  implementation details, see further below.
showResults
  If this boolean option is set to true, the dialog will display the possible
  result values behind the question text itself (retrieved from the validator).
  If a default value is provided, it will also indicate, which one this is. For
  example: ::
    
    Do you want to continue? (y/n) [y]

  While here "y" and "n" are valid results and "y" is the default result, which
  is selected when simply skipping this question by hitting just <RETURN>.

ezcConsoleQuestionDialogValidator
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The interface if ezcConsoleQuestionValidator inherits from
ezcConsoleDialogValidator and adds the following methods:

getResultString()
  This method returns the result string to be displayed if the option
  "showResults" is true in the ezcConsoleQuestionDialogOptions object.

Concrete implementations of these interface are:

ezcConsoleQuestionDialogTypeValidator
  This validator checks the result to be of a given type (e.g. int, float). It
  optionally checks if the result is in a given range (e.g. [0-100]).
ezcConsoleQuestionDialogCollectionValidator
  This validator checks the result against a given collection of pre-defined
  values (e.g. "y" and "n"). Beside that, it can perform basic operations on
  the result before checking it (like strtolower()).
ezcConsoleQuestionDialogRegexValidator
  This validator checks the result against a given regex (e.g.
  "/([0-2]?[0-9])[:.]([0-6]?[0-9])/" for validation of a time value). In addition
  it can perform a manipulation using this regex with a given (e.g. "\1:\2" to
  unify the time value given).

ezcConsoleMenuDialog
--------------------

The second dialog implementation shipped with ConsoleTools is the menu dialog,
which is an enhanced version of the question dialog. The menu dialog will
display an ordered set of options and let the user select one of these. A
typical menu can look like this: ::

    You can choose one of the following actions.

    1)  Create something new
    2)  Edit something
    3)  Delete something
    4)  Do something else

    Please choose an action: 

The menu dialog also comes with its own set of options, the
ezcConsoleMenuDialogOptions:

text
  The text displayed before the menu.
selectionText
  The text displayed after the menu to indicate to the user that he should make
  a selection.
markerChar
  The marker character used to divide the marker of a menu entry (e.g. the
  number) from the menu value (the text of a menu entry).
validator
  The validator must be an instance of ezcConsoleMenuDialogValidator.

ezcConsoleMenuDialogValidator
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

In contrast to ezcConsoleQuestionDialogValidator, this is not an interface, but
a concrete implementation of ezcConsoleDialogValidator. The menu validator
offers 2 properties to determine the menu entries:

$entries
  An array of entries shown as the menu. The keys of this array represent the
  markers of the menu entries, the values represent the text shown.

In addition, the validator can be configured to perform a manipulation on the
result like the ezcConsoleQuestionDialogCollectionValidator (e.g.
strtolower()).

==============================
Enhanced handling of arguments
==============================

Design overview
===============

The current functionality for handling options and arguments for a console
based application in eZ Components (mainly the class ezcConsoleInput) has only
rudimentary support for dealing with arguments. The current possibility is to
either switch arguments on or off and does not allow to specify the following
things:

- How many arguments are possible
- Which arguments are mandatory/optional
- Names and types for the arguments.

The goal of this design section is to provide these 3 features for argument
handling.

Clarification of terms
----------------------

Parameter
^^^^^^^^^

The term parameter is a generic term, which covers options and arguments
together.

Option
^^^^^^

An option for a console based application is specified using a short or a long
name and can (optionally) carry a value of a given type. Short names are built
using "-<name>" syntax, long names use "--<name>". Options are already handled
by the ezcConsoleInput class, using objects of ezcConsoleOption.

Example: ::

    foo.php --save "bar.txt" -a

"--save" is a long name and the parameter carries a string value "bar.txt".
"-a" is a short name of an option, while this option does not carry any value.

Argument
^^^^^^^^

In contrast to an option, an argument is not specified using a specific name, but
by by the order of submission to the program. Arguments can only be given to a
program, after all options have been specified.

Example: ::

    foo.php --save "bar.txt" -a -- "baz" 23
    
"baz" is the value of the first argument, 23 the value of the second one.

Arguments separator
^^^^^^^^^^^^^^^^^^^

In some cases the semantic for specifying parameters on the console is not
deterministic. Since the algorithm used to parse parameters cannot handle
nondeterministic semantics, a separator must be used to determine the border
between options and arguments. The separator used is "-- ", which indicates
that anything following it is an argument and does not belong to the option
before it. The seperator is not mandatory and must only be used in cases where
the parsing would not be deterministic.

Example: ::

    foo.php --save "bar.txt" -a "baz" 23
    foo.php --save "bar.txt" -a -- "baz" 23

In the first line, it is not possible to determine if "baz" is the value for
the parameter "-a" or if it is an argument. The second line clarifies this.

Basic design
------------

The core of the new functionality is built upon 2 classes:

- ezcConsoleArguments
- ezcConsoleArgument

The first one is a collection class, which takes care of holding all arguments.
The second one is a struct class, which handles 1 specific argument.

Class design
============

ezcConsoleArgument
------------------

This struct class represents a single argument and defines the following
properties:

name
  A descriptive name for the argument. This name is used for 2 purposes:
  - Displaying it in the help text generated by ezcConsoleInput.
  - Identifying and retrieving the object from the argument collection.
  This property is mandatory and may not be left out.
type
  The data type of the argument. The type is used for validation purposes when
  the parameter string of the console application is parsed and is indicated to
  the user in the help text generated by ezcConsoleInput. The default for this
  property will be ezcConsoleInput::TYPE_STRING. ezcConsoleInput::TYPE_NONE
  will not be allowed.
shorthelp
  A short help text, which is used by ezcConsoleInput when generating a short
  help output. A sensible default will be set for this property.
longhelp
  A long help text, which is used by ezcConsoleInput when generating a long
  help output. A sensible default will be set for this property.
mandatory
  This boolean flag specifies if an argument is mandatory or optional. Since
  ezcConsoleArguments is an ordered collection, all arguments following an
  optional argument will be handled as optional, too. The default value for
  this property is true.
default
  This property carries the default value for optional arguments. If these are
  not submitted, the default value will be used as the value property. If no
  default value was explicitly defined, it is null and therefore the value
  property will be null, if the argument was not submitted.
multiple
  This booloan property determines, if an argument can carry multiple values.
  The default here is false. If this is set to true, no more arguments may
  follow the current argument, since multiple defines the number of values to
  be undefined. If arguments would follow, the parameter string would be
  non-deterministic. Therefore all following arguments are silently ignored.
  For arguments with this property true, the returned value is an array. The
  default value may be an array, too.
value
  This property is not meant to be set by the developer directly, but by
  ezcConsoleInput while parsing the parameter string of the application. It
  will contain the value submitted for the argument after parsing. If this
  argument was not submitted, the value of the default property will be set
  here.

ezcConsoleArguments
-------------------

This collection class carries the arguments for an application. It keeps track
of the order of the ezcConsoleArgument objects and offers 2 access methods for
them:

- By their order (using numeric identification)
- By their name

For these purpose, the ArrayAccess and Iterator interfaces provided by PHP will
be implemented in this class. ArrayAccess will be used for both variants of
access, while Iterator will iterate of the numeric order of the arguments.
Argument names must be unique. The registration of new arguments is only
allowed by number. The retrival is also allowed by name.

The class does not offer any additional methods to deal with arguments and
purely relies on the given interfaces.

Integration aspects
===================

The current design of ezcConsoleInput is not designed to handle arguments in
the planned way. Therefore its API must be changed slightly. Naturally this
will be done maintaining BC. The following changes are planned:

Property $argumentDefinition
----------------------------

Since ezcConsoleInput already has a property $arguments, which carries the
values of submitted arguments in an array, a new property will be introduced to
carry the instance of ezcConsoleArguments. By default, this property will be
null, which indicates, that the old manor of handling arguments is used.

If ezcConsoleArguments is used, the preferred way of retrieving argument values
is, to use the ezcConsoleArgument struct of the specific argument. The
$arguments property of ezcConsoleInput will still be set and contain the values
of the provided arguments.

Switching arguments on/off
--------------------------

The ezcConsoleOption class allows to switch arguments on or off for a console
call to the application using its property $arguments. This behaviour will not
be changed. If an option defines that arguments are not allowed, if it is
submitted, all defined arguments will not be allowed.

It is possible, that this behaviour will be enhanced in the future, so that
options can define a filter rule for arguments instead of a boolean on/off
switch. But this feature is not in scope of the design given here.

Usage example
=============

The following example shows the basic of the newly created API.

Using arguments
---------------

::

    $input = new ezcConsoleInput();
    $input->argumentDefinition = new ezcConsoleArguments();

    // First argument
    $input->argumentDefinition[] = new ezcConsoleArgument(
        "infile",
        ezcConsoleInput::TYPE_STRING,
        "The file to read from.",
        "The input file, from which the content to process is read.",
    );

    // Second argument
    $input->argumentDefinition[1] = new ezcConsoleArgument(
        "outfile"
    );
    $input->argumentDefinition[1]->shorthelp = "Output file.";
    $input->argumentDefinition[1]->longhelp  = "File to output generated
        content to. Output will be printed to STDOUT if left out.";
    $input->argumentDefinition[1]->mandatory = false;
    $input->argumentDefinition[1]->default   = "php://output;

    try
    {
        $input->process();
    }
    catch ( ezcConsoleMissingArgumentException $e )
    {
        // Only the first argument will possibly trigger this
        die( "Argument {$e->argument->name} missing!" );
    }

    // The first argument is always present if no exception occured
    $inFile  = fopen( $input->argumentDefinition["infile"]->value, "r" );
    // The second one is optional, but has a default
    $outFile = fopen( $input->argumentDefinition["infile"]->value, "w" );

Generating help
---------------
    
::
    
    $input = new ezcConsoleInput();
    $input->argumentDefinition = new ezcConsoleArguments();

    // First argument
    $input->argumentDefinition[] = new ezcConsoleArgument(
        "file",
        ezcConsoleInput::TYPE_STRING,
        "Output file.",
        "File to output generated
    );

    // Second argument
    $input->argumentDefinition[] = new ezcConsoleArgument(
        "bytes",
        ezcConsoleInput::TYPE_INT,
        "Bytes to write.",
        "The number of bytes written to the output file. If left out,
             everything will be written.",
        false,
        -1
    );

    echo $input->getHelpiText( "Some example program." );

Generates: ::

    Usage: test.php [--] <string:file> [<int:bytes>]
    Some example program

    Arguments:
    <string:file>       Output file.
    <int:bytes> = -1    Bytes to write.

Open issues
===========

- The access of arguments by number and name is comfortable, but (as described
  above) not reliable, if 2 parameters have the same name. It also makes
  implementation more complex (if a parameter is removed, namesake has to come
  in place. So, do we want this or not?
- The property name $argumentsDefinition is OK for the definition part, but
  does actually not make sense, if you access it later to retrieve the value.
  Any better naming solution?
- It is sometimes sensible to allow that for 1 argument multiple values are
  submitted (e.g. if you expect an undefined number of file names). To resolve
  this, we need to invent a property "multiple" for the ezcConsoleArgument
  struct. If multiple is defined for 1 argument, no more arguments must follow,
  since the algorithm could not determine easily where the multiple values for
  the argument end. Do we need this functionality (now)?


..
   Local Variables:
   mode: rst
   fill-column: 79
   End: 
   vim: et syn=rst tw=79
