whenjobs --daemon-status
whenjobs --daemon-restart
+Examine running jobs:
+
+ whenjobs --jobs
+ whenjobs --cancel serial
+ whenjobs --start "name"
+
=head1 DESCRIPTION
Whenjobs is a powerful but simple replacement for cron. It lets you
=over 4
+=item B<--cancel> serial
+
+Cancel the job with the given serial number.
+
+Use I<--jobs> to list running jobs along with their serial numbers.
+The serial number is also available in the job script (as
+C<$JOBSERIAL>) and in the log file.
+
=item B<--daemon-start>
=item B<--daemon-stop>
Print the value of a variable.
+=item B<--jobs>
+
+List all running jobs.
+
+Note that it is possible for the same job to be running more than once
+(for example, a periodic job that takes longer than the period to run).
+
=item B<-l>
=item B<--list>
whenjobs --lib $builddir/lib -e
+=item B<--start> "job name"
+
+Start the job immediately and unconditionally.
+
+This runs the job even if its normal preconditions are not met. This
+may cause unexpected results, so use with caution.
+
=item B<--set> variable value
=item B<--type> bool|int|float|string|unit
This is the same as writing C<prev variable E<gt> variable>.
-B<Note:> There is a subtle and difficult problem with using the
-I<decreases> operator: The first time the expression is evaluated, the
-job has (by definition) not yet run. Therefore C<prev variable>
-evaluates to C<""> (see definition of I<prev> above). Since
-C<"" E<lt> everything>, the I<decreases> operator evaluates to
-false, and since this usually means the job does not run, the
-operator always evaluates to false. A future version of whenjobs
-will address this problem.
+B<Note:> There is a subtle gotcha with the I<decreases> operator: The
+first time the expression is evaluated, the job has (by definition)
+not yet run. Therefore C<prev variable> evaluates to C<""> (see
+definition of I<prev> above). Since it is always true that
+
+ "" < anything
+
+the I<decreases> operator evaluates to false, and since this usually
+means the job does not run, the operator always evaluates to false.
+
+To fix this, ensure that the variable is initialized (see
+L</SETTING THE INITIAL VALUE OF VARIABLES> below).
=item B<reloaded ()>
after the jobs file has been reloaded or the daemon restarted.
Thereafter it evaluates to false.
-You can use this to initialize variables, but note that this does not
-solve the I<decreases> operator problem described above, because
-variables are initialized too late to affect that.
+Don't use this to initialize variables: it won't do what you mean.
=item B<false>
The shell script runs as the ordinary user. It has no special
privileges.
+=head2 JOB NAMES
+
+Jobs are given implicit names (C<job$1>, C<job$2> etc.). You can also
+name jobs explicitly by preceeding the "every" or "when" statement
+with C<job "name">:
+
+ job "poll source"
+ every 10 seconds :
+ <<
+ # ...
+ >>
+
+The job name is passed to the shell script in the C<$JOBNAME>
+environment variable.
+
+=head2 OCAML EXPRESSIONS
+
+As well as simple "every" and "when" expressions, advanced users may
+want to use arbitrary OCaml expressions, functions, etc in the jobs
+script. These are useful for factoring common code or strings, for
+setting the initial values of variables, or for defining pre and post
+functions.
+
+A simple example of an OCaml expression is:
+
+ let prefix = "daily_"
+
+ job (prefix ^ "virus_scan")
+ every day :
+ <<
+ # ...
+ >>
+
+ job (prefix ^ "disk_check")
+ every day :
+ <<
+ # ...
+ >>
+
+which creates two jobs called C<"daily_virus_scan"> and
+C<"daily_disk_check"> (C<^> is the OCaml string concatenation
+operator).
+
+OCaml expressions have access to a library of functions called
+B<Whentools> which is described below. It lets you set variables,
+create jobs algorithmically, etc.
+
+The OCaml expressions run once, when the jobs file is being loaded or
+reloaded.
+
+=head3 SETTING THE INITIAL VALUE OF VARIABLES
+
+Variables are created when they are referenced, and until set they
+have the value empty string (just like the shell). Across file
+reloads, the previous values of variables are preserved.
+
+To initialize a variable to a known value when the jobs file is
+loaded, call one of the C<Whentools.set_variable*> functions as in
+this example:
+
+ let () =
+ Whentools.set_variable "name" "Richard";
+ Whentools.set_variable_int "counter" 0
+
+=head3 PRE FUNCTIONS
+
+Before a job runs, you can arrange that a C<pre> function is called.
+This function may decide not to run the job (by returning C<false>).
+
+One use for this is to prevent a particular job from running if there
+is already an instance of the same job running:
+
+ job "only one"
+ pre (Whentools.one ())
+ every 10 seconds :
+ <<
+ # Takes longer than 10 seconds to run, but 'Whentools.one ()'
+ # will ensure only one is ever running.
+ sleep 11
+ >>
+
+When using pre functions, jobs must be given an explicit name, ie.
+you must use the C<job> statement.
+
+A number of pre functions are available in the library; see below.
+
+You can also write your own post functions (in OCaml). The function
+is passed one argument which is a C<Whentools.preinfo> struct, defined
+below. It should return a boolean: C<true> if the job should run, and
+C<false> if the job should not run.
+
+Note that a fresh serial number (see L</JOBSERIAL>) is assigned to
+each run, whether or not the job actually runs because of
+preconditions.
+=head3 POST FUNCTIONS
+After a job runs, you can control what happens to its output by
+writing a C<post> function. To write a post function you have to
+name the job (ie. have an explicit C<job> statement). Put C<post ...>
+after the job name like this:
+ job "poll source"
+ post (Whentools.mailto "you@example.com")
+ every 10 seconds :
+ <<
+ # ...
+ >>
+
+A number of post functions are available in the library; see below.
+
+You can also write your own post functions (in OCaml). The
+function is passed one argument which is a C<Whentools.result> struct,
+defined below.
+
+=head3 WHENTOOLS LIBRARY
+
+=head4 Functions
+
+=over 4
+
+=item B<Whentools.mailto> [I<~only_on_failure:true>]
+[I<~from:from_address>] I<email_address> I<result>
+
+This built-in post function sends the result of the script by email to
+the given email address.
+
+If the optional C<~only_on_failure:true> flag is set, then it is only
+sent out if the script failed.
+
+If the optional C<~from> flag is set, then the from address is set
+accordingly. This is sometimes needed when sending mail.
+
+Note the C<result> parameter is passed implicitly by the daemon. You
+do not need to add it.
+
+Here are some examples of using the mailto function:
+
+ job "ex.1"
+ post (Whentools.mailto "you@example.com")
+ every 10 seconds :
+ <<
+ # do something
+ >>
+
+ job "ex.2"
+ post (Whentools.mailto ~only_on_failure:true
+ "you@example.com")
+ every 10 seconds :
+ <<
+ # do something
+ >>
+
+ let from = "me@example.com"
+ let to_addr = "you@example.com"
+
+ job "ex.3"
+ post (Whentools.mailto ~from to_addr)
+ every 10 seconds :
+ <<
+ # do something
+ >>
+
+=item B<Whentools.max> I<n>
+
+This built-in pre function ensures that a maximum of I<n> instances of
+the job are running.
+
+It checks the list of running jobs, and if I<n> or more instances are
+already running, then it returns C<false>, which ensures that the new
+job is not started.
+
+=item B<Whentools.one> I<()>
+
+This built-in pre function ensures that only one instance of the job
+is running. It is the same as calling:
+
+ Whentools.max 1
+
+=item B<Whentools.set_variable> I<name> I<string>
+
+Set variable I<name> to the string.
+
+=item B<Whentools.set_variable_bool> I<name> I<b>
+
+Set variable I<name> to the boolean value I<b>.
+=item B<Whentools.set_variable_int> I<name> I<i>
+Set variable I<name> to the integer value I<i>.
+=item B<Whentools.set_variable_string> I<name> I<s>
+
+Set variable I<name> to the string value <s>. This is
+the same as I<Whentools.set_variable>.
+
+=item B<Whentools.set_variable_float> I<name> I<f>
+
+Set variable I<name> to the floating point value I<f>.
+
+=back
+
+=head4 Structures
+
+=over 4
+
+=item B<Whentools.preinfo>
+
+This structure is passed to pre functions. It has the following
+fields:
+
+ type preinfo = {
+ pi_job_name : string; # Job name.
+ pi_serial : Big_int.big_int; # Job serial number.
+ pi_variables : (string * variable) list; # Variables set in job.
+ pi_running : preinfo_running_job list; # List of running jobs.
+ }
+ and preinfo_running_job = {
+ pirun_job_name : string; # Running job name.
+ pirun_serial : Big_int.big_int; # Running job serial number.
+ pirun_start_time : float; # Running job start time.
+ pirun_pid : int; # Running job process ID.
+ }
+
+=item B<Whentools.result>
+
+This structure is passed to post functions. It has the following
+fields:
+
+ type result = {
+ res_job_name : string; # job name
+ res_serial : big_int; # job serial (same as $JOBSERIAL)
+ res_code : int; # return code from the shell script
+ res_tmpdir : string; # temporary directory script ran in
+ res_output : string; # filename of stdout/stderr output
+ res_start_time : float; # when the job started
+ }
+
+=back
=head1 FILES