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 . 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 "-" syntax, long names use "--". 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 [--] [] Some example program Arguments: Output file. = -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