I'm looking for a part-time remote job.

Hire me


I'm the author of:

Mastering Redmine is a comprehensive guide with tips, tricks and best practices, and an easy-to-learn structure.

Check the book's project or

Buy the book

Social pages of the book:

By buying this book you also donate to Redmine (see this page).


Follow me:

Context API

A context is an instance of Orangutan::Context class. Each context should be stored in a separate file with extension .context. This file should “return” Orangutan::Context reference that is context creation should be the last operation in the file.

An “empty” context file looks like:

new Orangutan::Context(
);

This statement returns Orangutan::Context object.

Of course, such “empty” context is useless. To provide some functionality a context should declare “members”. Members can be callback functions, string or numeric parameters, array or hash references etc (depends on the member). Members are “declared” this way:

new Orangutan::Context(
  <name> => <reference>
);

Supported members are described in subsections.

Depending on the members declared a context can be an “output” context, an “input” context or “input/output” context. Input contexts can be interpreted as questions (asked by Orangutan). Output contexts can be interpreted as answers on these questions or user’s requests. Input/output contexts usually are little “dialogs” - Orangutan asks a question and user asnwers it. But do not take these “types” literally. If a context is input this does not mean that it does not output anything and if a context is output this does not mean that it does not “act” on user’s input. These types define how a context is inserted - a user has two contexts queues - input and output. Input contexts are inserted into input queue, output contexts - into output queue and input/output contexts - into both queues.

A context object like a user object can have custom fields. Besides custom fields some contexts store state data (e.g. timeout). That’s why the same context object cannot be used for different users. Such contexts are “copied” and a user uses the copy of the “original” context. The “original” context is called “generic”.

Members

init

Some contexts may need to fetch some information before being added to users’ queues to action properly. This member is currently used for example in Help context. It loops through available contexts and extracts help information from them (see “help” member).

This member should be declared this way:

init => sub {
  my ($context) = @_;
},

The only argument passed to the function is the reference to the context (itself).

request

A context can have a “question” or “request”. This is what is shown to a user when a context is “inserted” into user’s output contexts queue. And this is what makes a context an output one.

The “request” member can contain a string, an array reference or function reference. If a string is specified it is sent to a user. If an array is specified Orangutan randomly selects an array item and sends it to a user. If a function is specified - Orangutan calls it, fetches returned string and sends it to a user.

request => 'Hello!',
...
request => [ 'Hi!', 'Hello!' ],
...
request => sub {
  my ($context, $user) = @_;
  return 'Hello!';
},

If a function is specified it receives two arguments - a reference to the context (itself) and a reference to a user. The function must return a string. If the function returns ''undef’' Orangutan will not send anything to a user and will remove the context from input and output queues (this way you may “cancel” the context).

Instead of request name you can use the question name (question is an alias for request):

question => 'How are you?',

delay

Sometimes it is needed to delay a request, for example, if you need to show some message to users in 30 minutes after they came online. This can be done by setting delay:

delay => 1800,

The value of the delay should be number of seconds.

response

To handle user input Orangutan first “parses” the input text and fetches “parameters” passed by users in their requests and then calls handling functions. Orangutan uses regular expressions to “recognize” (or “parse”) user requests. These regular expressions are stored in “response” member. Actionally this is what makes a context an input one.

Like “request” member the “response” member can contain a string (regular expression), a reference to an array of strings or a reference to a function. If a string is specified Orangutan uses it as a regular expression to recognize user input. If an array is specified Orangutan uses all elements of this array as regular expressions (tries one-by-one). If a function is given Orangutan calls it passing user input text as an argument.

response => '^H(?:i|ello)[^a-z]*!*\.*$',
...
response => [ '^Hi!*\.*$', '^Hello!*\.*$' ],
...
response => sub {
  my ($context, $text) = @_;
  my @result = ( );
  return @result;
}

Orangutan passes a reference to the context (itself) and a user input text as arguments to the function. This function should “parse” the input and determine if the given context should handle it. If input should not be handled by this context the function should return an empty array. Otherwise it should return an array containing an item number and parameters (if any).

The item number is a number of regular expression which has matched. That is if a string is given - this number will always be 1. If an array is given this number will be an index of array element which has matched + 1. For functions the developer decides which number to return. Usually the “response” function returns 1 but developer may decide to return a different number (any) to use it as an indicator of something.

Instead of response you can use answer (an alias).

See the “handler” member for more information on handling.

handler

The “handler” member is the place in Orangutan were all main operations are performed. This handler is called when a context receives a request from a user. Before going into handler the input text is parsed using the “response” member. After successfull parsing Orangutan has an item number and parameters. These data is passed to the handler function.

The value of the “handler” member must be a reference to a function:

handler => sub {
  my ($context, $user, $item, @parameters) = @_;
  $main::config->Context('Ok')->Tell($user);
  return 0;
},

This function receives a reference to the context (itself), a reference to a user object, an item number and custom parameters extracted from the user’s request.

Return value depends on the “timeout” member. If timeout is not set the return value is ignored so you may return anything. But if the timeout is set the return value has a special meaning - if it evaluates to true the context is not removed from the user’s queue and if it evaluates to false the context is removed (meaning that its operation was successfully completed).

To explain an item number better let’s review an example:

response => [
  '^John$',  # 1
  '^Chris$', # 2
  '^Amanda$' # 3
],
handler => sub {
  my ($context, $user, $item) = @_;
},

Saying the input text is “John” - $item will be 1, if the input is “Amanda” - $item will be 3.

To explain parameters better let’s review:

response => '^My name is ([^ ]+) ([^ ]+)!*\.*$',
handler => sub {
  my ($context, $user, $item, $firstname, $lastname) = @_;
}

Saying the input is “My name is John Smith!” - $item will be 1, $firstname will be “John” and $lastname will be “Smith”.

The “handler” member has an alias onmatch.

reply

The “reply” member was created as an alternative to the “handler” member. In the “handler” member you need to write a function - strings and arrays are not supported. And the “reply” member supports only strings or arrays of strings - functions are not supported1.

As you know Orangutan supports input and output contexts. As it was mentioned before these types should not be taken literally. Usually an input/output context define a question, is able to receive a response but also gives a “result” - sends a message indicating the successfulness of an operation. So it looks more like an output/input/output context... This “result” is usually sent in the “handler” member. But if the result message is always the same (e.g. “Ok”) it can be put into the “reply” member.

The “reply” member is also used to create questions/answers input contexts. Such contexts contain the “response” member recognizing user’s questions and the “reply” member giving answers on these questions.

So the “reply” member has the following syntax:

reply => 'Ok',
reply => [ 'Ok', 'Okay' ],

If a string is specified Orangutan sends it to a user as soon as a user input matches the context (but after calling its “handler” member). If an array is specified Orangutan randomly selects of of the items and sends it to a user.

Let’s see this very simple but fully functional context:

new Orangutan::Context(
  request => 'Are you a human?..',
  response => '^Yes$',
  reply => 'I knew that!..'
);

timeout

Saying Orangutan asks something. You could ask: “How long will he wait for an answer?”. The answer is... forever! It is not good, of course. This is where the “timeout” member helps. This member defines a time in seconds Orangutan will wait for an answer.

The value of this member should be a number of seconds:

timeout => 1800,

In addition to “timeout” you can use “time” (an alias).

ontimeout

Some questions can be very important and if a user does not answer them Orangutan should “guess” the answer - that is use some default value or do some other actions. But Orangutan should also give a chance to a user to asnwer such questions. That’s where the “ontimeout” member helps. This member is called when a timeout occurs.

The “ontimeout” handler must contain a function:

ontimeout => sub {
  my ($context, $user) = @_;
  return 0;
},

Orangutan passes a context object reference and a user reference to this member.

Usually when a context timeouts it is removed from user queues. But sometimes it is needed to keep it. To keep a context after timeout the “ontimeout” member must return a value evaluating to true.

nomatch

A user can send a request which does not match any context. So such request is going to be ignored. In some cases it can be useful to “recheck” such request (it did not match but maybe we can determine what is wrong?). For this the “nomatch” member was created.

The value of the “nomatch” member is a function:

nomatch => sub {
  my ($context, $user) = @_;
},

The function receives a reference to the context (self) and a reference to a user.

Right now Orangutan calls all “nomatch” members of all available contexts in the input queue. It is not a problem currently because the “nomatch” member is defined only in the “Task” context. The developer who will decide to use it will need to “improve” the member (I believe $input should be added to the function arguments and I believe the function should return some value indicating whether the member has handled the input).

undo

Orangutan tries to emulate the natural language interface what means that users can use free form statements as they were speaking to a human not a bot. On practice this leads to misunderstandings. That is Orangutan sometimes does not what was expected by a user. For such cases the “undo” member was created.

The “undo” member contains a reference to a function. This function should “cancel” the operation done by the context. If this is not possible the function should explain why.

undo => sub {
  my ($context, $user) = @_;
},

The function receives two arguments - a reference to the context (itself) and a reference to the user object.

The “undo” handler is initiated by the “Cancel” context. A user can cancel only just done operation (the operation done by the previous context).

event

Orangutan allows a developer to set handlers on different events. These handlers are defined in the “event” member.

The “event” member should contain a reference to a hash:

event => {
  status => sub {},
  task   => sub {}
},

The hash must contain an event ID as a key and a function reference as a value.

For a detailed description of the event handlers check Events section.

schedule

Some operations should be performed on the schedule basis. Currently in Orangutan they are: a) every 8 hours Orangutan searches for tasks lasting for more than 24 hours, in a hour after the end of the work time Orangutan checks if the user is still available (asks “Are you there?”), on work day start and work day end Orangutan “fires” work day start and end events etc.

For this Orangutan uses the internal scheduler. Contexts can add “tasks” to this schedule. This is done using the “schedule” member:

schedule => sub {
  my ($context, $scheduler, $arg) = @_;
},

Arguments of the function are: $context is a reference to a generic context (itself), $scheduler is a reference to the scheduler (which is actually Users collection) and $arg is a special argument (see futhure).

The “schedule” member is executed at least once - on Orangutan start. To initialize the scheduler Orangutan calls this member with the $arg equal to init. The next run is set up by the “schedule” member. The “schedule” member also defines a value of the $arg for the next run. So a developer can actually use any value for the $arg.

Let’s review an example:

schedule => sub {
  my ($context, $scheduler, $arg) = @_;

  # Execute code for a generic context
  ...
  foreach my $username ($main::users->GetUsers) {
    my $user = $main::users->GetUser($username);
    # Execute code for each user
    ...
  }
  # Set up the next run with $arg equal to 'run'
  $scheduler->Schedule(time + 3600, $context, 'run');
},

Please note that the scheduler executes a task using the generic context.

help

Orangutan provides help information using the “Help” context. This context gives a short tutorial and the “Frequently Asked Questions”. The FAQ is built using the information provided by contexts.

If a context wants to add some help information to FAQ it must defile the “help” member. The value of the “help” member must be a hash:

help => {
  title    => 'A question',
  question => 'Regular expression(s)',
  answer   => 'An answer',
  weight   => 500
},

Here the title will be shown on the FAQ page (usually it is a question), the question is a regular expression or a reference to an array of regular expressions which should at least match the title, the answer is an answer to the title and the weight defines a position of the question on the FAQ page.

As it was said above the question must contain a regular expression which matches the title. But it can, of course, contain other regular expressions as well. The “Help” context will try to check if a user input matches any of these regular expressions.

weight

Each input context defines regular expressions to recognize user input. Sometimes user input can match several regular expressions in different contexts that’s why these regular expressions are “ordered”. The same about output contexts - some requests can be more important (and should be sent first) and others - less. A context defines the “weight” member to specify its position in user’s input and output queues.

The value of the “weight” member can be undef, a reference to a hash, a reference to an array and a function reference:

weight => undef,
...
weight => { out => 10, in => 10 },
...
weight => { request => 10, response => 10 },
...
weight => [ 10, 10 ],
...
weight => sub {
  my ($context, $user) = @_;
  return ( 10, 10 );
},

Weight is a positive or negative number used to sort contexts in user’s queue. The value undef means “do not insert this context in user’s queue”. The value 0 is default.

If a hash is used it may contain request or out and response or in keys with the weight values. If an array is used it must contain two elements - output weight and input weight. If a function is used it must return an array of two elements - output and input weights.

copy

As it was mentioned above some contexts store state data - e.g. when timeout will occur, was the request sent etc. Orangutan is able to recognize such contexts and copy them. But he does not know if a context is going to set a custom field... So such contexts won’t be copied and several users will use the same custom field value. To prevent this Orangutan declares the “copy” member. This member indicates whether the context should be copied.

copy => 1

The value of the “copy” member must be 1 indicating “yes, the context should be copied” or 0 (default) meaning “copy only if you need to”.

Events

config

Some important decisions are made by Orangutan based on user’s configuration. Therefore sometimes it is very important for contexts to know when some configuration options are changed. Depending on the change made contexts might need to recalculate something, remove themselves from user’s queue etc.

The “config” event is a very new event which is currently used only for notifying contexts when a user asks Orangutan to stop reminding him to enter tasks (the “AskTask” context). Of course, this event should be extended in future.

The handler for this event should look like:

event => {
  config => sub {
    my ($context, $user, $type, $config, $value) = @_;
  }
},

Here the $context is a reference to the context (itself), the $user is a user object, the $type is always equal to config, the $config is also always equal to “flag2” and the $value is a new value of the configuration option.

For the flag configuration option the $value indicates a flag changed. So a developer must check was it set or was it unset by checking the current value of the flag.

status

A context should always take into account user’s status. Luckily Orangutan handles status transparently to contexts - he won’t send a request to a user when a user is offline, he calculates timeout using the time during which a user was online etc. Orangutan lets contexts handle status changes by catching the “status” event.

The “status” event handler should be defined this way:

event => {
  status => sub {
    my ($context, $user, $type, $status) = @_;
  }
},

Here the $context is a reference to the context (itself), the $user is a reference to the user, the $type is event name which is always status and the $status is a new status of the user.

The $status can be equal to the following:

  • online - User goes online
  • away - User goes away
  • undef - User goes offline

When using the “status” event a developer should take into account that this event is raised before changing the status. This lets a developer fetch the previous status which is still current at the moment of calling the handler.

task

The very main feature of the Orangutan is task handling. Most of the available contexts work with tasks or prepare some data for tasks. Many of the contexts need to know when a new task is created, when a task is closed, added, canceled etc. To provide such information to the contexts Orangutan raises the “task” event.

The “task” event handler should be defined as follows:

event => {
  task => sub {
    my ($context, $user, $type, $status, @arguments) = @_;
  }
},

The $context is a reference to the context (itself), the $user is a reference to the user object, the $type is always equal to task. And possible values for $status are shown in the table:

$status Description @arguments
start Raised when a new task has been started The task started
cancel Raised when a current task has been removed The task canceled
break Raised when a current task has been broken Two tasks created by a break
end Raised when a current task has been closed The task finished
added Raised when a previous task has been added The task added

The value of @arguments as shown in the table depends on the $status value. Usually it’s an array with just a reference to the task object but for break this array contains references of two tasks created by the break.

More advanced example of the “task” handler:

event => {
  task => sub {
    my $context = shift;
    my $user = shift;
    my $type = shift;
    my $status = shift;
    my ($curtask, $prevtask);
    if ($status eq 'break') {
      $prevtask = shift;
    }
    $curtask = shift;
  }
},

workday

Users can specify their work day length (hours) and work day start time. This information is needed to determine if currently is the work time, if enough hours were spent etc. Orangutan provides API to access this information. In addition it provides work day start and work day end events (currently these events are used in the “WhatTask” context).

The “workday” event is raised twice a working day - when the work day starts and when the work day ends.

event => {
  workday => sub {
    my ($context, $user, $type, $status) = @_;
  }
},

The $context is a reference to the context (itself), $user is a reference to a user, $type is always equal to workday, $status is equal to start if the work day starts and to end if the work day ends.

1 of course, these two members could be merged but for now they are separate

2 currently only flag changes are supported

Terms of use | Privacy policy