Lots of documentation.
authorRichard W.M. Jones <rjones@redhat.com>
Fri, 20 Sep 2013 16:12:35 +0000 (17:12 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Fri, 20 Sep 2013 16:17:29 +0000 (17:17 +0100)
.gitignore
Makefile.am
configure.ac
goaljobs-reference.pod [new file with mode: 0644]
goaljobs.mli
goaljobs.pod [new file with mode: 0644]
goaljobs.spec.in

index 42a7a0b..a385798 100644 (file)
@@ -6,6 +6,7 @@
 *.cmxa
 *.o
 *.a
+*.1
 
 Makefile.in
 Makefile
index 11d4fbd..8a81cd3 100644 (file)
@@ -25,8 +25,11 @@ EXTRA_DIST = \
        goaljobs_config.ml.in \
        goaljobs.ml \
        goaljobs.mli \
+       goaljobs.pod \
+       goaljobs-reference.pod \
        goaljobs.spec \
        goaljobs.spec.in \
+       html/.gitignore \
        META.in \
        NOTES \
        pa_goal.ml \
@@ -85,6 +88,20 @@ depend: .depend
 rpm: dist
        rpmbuild -ta $(PACKAGE_NAME)-$(PACKAGE_VERSION).tar.gz
 
+if HAVE_POD2MAN
+
+man_MANS = goaljobs.1 goaljobs-reference.1
+
+goaljobs.1: goaljobs.pod
+       $(POD2MAN) -c "Goaljobs" --release $(PACKAGE)-$(VERSION) $< > $@-t
+       mv $@-t $@
+
+goaljobs-reference.1: goaljobs-reference.pod
+       $(POD2MAN) -c "Goaljobs" --release $(PACKAGE)-$(VERSION) $< > $@-t
+       mv $@-t $@
+
+endif
+
 if HAVE_OCAMLDOC
 
 # HTML library documentation.
index cdf9cfb..ebc5cfd 100644 (file)
@@ -99,10 +99,12 @@ AM_CONDITIONAL([HAVE_OCAMLDOC],
                [test "x$OCAMLDOC" != "xno"])
 
 dnl Check for POD (for manual pages).
-AC_CHECK_PROG(PERLDOC,perldoc,perldoc)
-if test "x$PERLDOC" = "x"; then
-    AC_MSG_ERROR([You must install the 'perldoc' program])
+AC_CHECK_PROG(POD2MAN,pod2man,pod2man,no)
+if test "x$POD2MAN" = "x"; then
+    AC_MSG_ERROR([You must install the 'pod2man' program])
 fi
+AM_CONDITIONAL([HAVE_POD2MAN],
+               [test "x$POD2MAN" != "xno"])
 
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_FILES([goaljobs_config.ml
diff --git a/goaljobs-reference.pod b/goaljobs-reference.pod
new file mode 100644 (file)
index 0000000..7912568
--- /dev/null
@@ -0,0 +1,362 @@
+=encoding utf8
+
+=head1 NAME
+
+goaljobs-reference - reference documentation for writing goaljobs scripts
+
+=head1 SUMMARY
+
+ open Unix
+ open Printf
+ open Goaljobs
+ let goal name args... =
+   target (condition);
+   require (goal1 ...);
+   require (goal2 ...);
+   (* code to implement the goal *)
+ let goal all () =
+   require (name args...)
+ every 30 minutes (
+   fun () ->
+     require (goal1 ...)
+ )
+
+=head1 DESCRIPTION
+
+Goaljobs is a flexible build system and business rules manager similar
+to make and cron, but much more powerful.  You can use it to automate
+many complex tasks that have multiple steps (even with manual steps)
+that have to be carried out in dependency order.
+
+For a tutorial-like introduction to goaljobs, see
+L<http://rwmj.wordpress.com/tag/goaljobs/>
+
+For examples, see the C<examples/> directory in the source and
+L<http://git.annexia.org/?p=goals.git;a=summary>
+
+For reference documentation on how to write scripts, see below.
+
+Note this man page does not cover the whole Goaljobs API.  To read
+about the Goaljobs API, look for the file C<goaljobs.mli> (in the
+source code or installed as part of the goaljobs package), or see the
+HTML documentation installed as part of the goaljobs package.
+
+=head1 THE SCRIPT FILE
+
+The script file should usually start with opening these modules (none
+of this are required, it's just useful to have them open):
+
+ open Unix
+ open Printf
+ open Goaljobs
+
+This is followed by goals and/or functions and/or top-level OCaml
+statements and/or C<every> statements (a.k.a periodic jobs).
+
+You can use multiple script files to make up a goaljobs program.  You
+have to list them in dependency order on the goaljobs command line
+(earlier files required by later files), the same way that the OCaml
+compiler works.  So usually you end up writing:
+
+ goaljobs utils.ml another_library.ml script.ml
+
+where C<script.ml> requires the functions from the utils/library.
+Note that circular dependencies are not possible.
+
+=head1 GOALS
+
+Each goal should have the following basic form:
+
+ let goal name args... =
+   target (condition);
+   require (goal1 ...);
+   require (goal2 ...);
+   (* code to implement the goal *)
+
+There is no hard-and-fast rule about this.  In particular you can put
+arbitrary OCaml statements anywhere inside a goal (since a goal is
+just a special form of OCaml function), but sticking to this overall
+plan is a good idea.
+
+There should be zero or one target.  Multiple target statements should
+not be used in a goal.  The target should come as early as possible,
+and the target condition should be as simple and fast to evaluate as
+is practical (see L</THE MEMORY> below).
+
+There should be zero or any number of C<require> statements.  Each
+require statement should name a single goal (with optional parameters
+for that goal).
+
+After that should come the code that implements the goal, which might
+be, for example, a series of shell commands, but could even be
+user-interactive.
+
+As with ordinary OCaml functions, you can define goals recursively
+or with mutual recursion using:
+
+ let rec goal1 args... =
+   ...
+ and goal2 args... =
+   ...
+ and goal3 args... =
+   ...
+
+A goal can also have no arguments:
+
+ let goal all () =
+   ...
+
+This defines the common goal called C<all>, which acts the same way as
+C<make all>, ie. if you run the program without any arguments, it will
+run the C<all> goal if one exists.
+
+=head2 PUBLISHING GOALS
+
+If a goal is "published" it means it is available to be run directly
+from the command line.  All no-arg goals are published by default.
+You do not need to do anything special for them.  For example:
+
+ let goal clean () = sh "rm *~"
+
+can be used on the command line:
+
+ ./myscript clean
+
+For goals which take any parameters, you have to define a small code
+snippet that converts command line arguments to goal parameters (the
+reason has to do with OCaml being strongly typed, and because goal
+parameters might not all be strings).
+
+ let goal compile program sources =
+   target (more_recent [program] sources);
+   ...
+ let () =
+   publish "compile" (
+     fun args ->
+       let program = List.hd args in
+       let sources = List.tl args in
+       require (compiled program sources)
+   )
+
+Then you can use:
+
+ ./myscript compile program main.c utils.c
+
+=head1 TARGET AND REQUIRE
+
+The target is promise or contract that you make that the given
+condition I<will> be true when the goal has finished running.
+
+In the first example, the target is that the C<o_file> (object) exists
+and is newer than the C<c_file> (source).  The goal meets that target
+by running the C compiler (C<cc>) which, if it succeeds, will ensure
+that the object file exists and is newer than the source file.
+
+ let goal compiled c_file =
+   let o_file = change_file_extension "o" c_file in
+   target (more_recent [o_file] [c_file]);
+   sh "
+     cd $builddir
+     cc -c %s -o %s
+   " c_file o_file
+
+In the second example, the goal requires that several files have been
+compiled (C<require (compiled ...)>) before it can link the final
+program:
+
+ let goal built program source =
+   target (more_recent [program] [source]);
+   require (compiled source);
+   let object = change_file_extension "o" source in
+   sh "
+     cd $builddir
+     cc %s -o %s
+   " object program
+
+=head1 PERIODIC JOBS
+
+If you want to have a goal that runs when some outside event happens
+you have three choices: Manually run the script (this is basically
+what C<make> forces you to do).  Have some sort of hook that runs the
+script (eg. a git hook).  Or use a periodic job to poll for an event
+or change.
+
+Periodic jobs run regularly to poll for an outside event or change.
+If a script has periodic jobs, then it runs continuously (or until you
+kill it).
+
+An example of a script that checks for new git commits and when it
+sees one it will ensure it passes the tests:
+
+ let repo = Sys.getenv "HOME" // "repo"
+ let goal git_commit_tested commit =
+   let key = sprintf "repo-tested-%s" commit in
+   target (memory_exists key);
+   sh "
+     git clone %s test
+     cd test
+     ./configure
+     make
+     make check
+   " repo_url;
+
+   (* Record that this commit was tested successfully. *)
+   memory_set key "1"
+ every 30 minutes (fun () ->
+   let commit = shout "cd %s && git rev-parse HEAD" repo in
+   (* Require that this commit has been tested. *)
+   require (git_commit_tested commit)
+ )
+
+Some notes about the above example: Firstly only the current HEAD
+commit is required to be tested.  This is because older commits are
+irrelevant and because if they failed the test before there is not
+point retesting them (commits are immutable).  Secondly we use the
+Memory to remember that we have successfully tested a commit.  This is
+what stops the program from repeatedly testing the same commit.
+
+=head1 SHELL
+
+You can call out to the Unix shell using one of the functions
+C<Goaljobs.sh>, C<Goaljobs.shout> or C<Goaljobs.shlines>.  (These
+functions are documented in the C<goaljobs.mli> file / API
+documentation.)
+
+C<sh> runs the command(s).  C<shout> collects the output of the
+command (to stdout only) and returns it as a single string.
+C<shlines> collects the output and returns it as a list of lines.
+
+C<sh>, C<shout>, C<shlines> work like printf.  ie. You can substitute
+variables using C<%s>, C<%d> and so on.  For example:
+
+ sh "rsync foo-%s.tar.gz example.com:/html/" version
+
+Each shell runs in a new temporary directory.  The temporary directory
+and all its contents is deleted after the shell exits.  If you want to
+save any data, C<cd> somewhere.  If you don't want the temporary
+directory creation, use C<~tmpdir:false>.
+
+The environment variable C<$builddir> is exported to the script.  This
+is the current directory when the goaljobs program was started.
+
+Each invocation of C<sh> (etc) is a single shell (this is slightly
+different from how C<make> works).  For example:
+
+ sh "
+   package=foo-%s
+   tarball=$package.tar.gz
+   cp $HOME/$tarball .
+   tar zxf $tarball
+   cd $package
+   ./configure
+   make
+ " version
+
+The shell error mode is set such that if any single command
+returns an error then the C<sh> function as a whole exits with
+an error.  Write:
+
+ command ||:
+
+to ignore the result of a command.
+
+C</bin/sh> is used unless you set C<Goaljobs.shell> to some other
+value.  Note that the environment variable C<SHELL> is I<never> used.
+
+=head1 THE MEMORY
+
+"The Memory" is key/value storage which persists across goaljobs
+sessions.  It is stored in the file C<$HOME/.goaljobs-memory> (which is
+a binary file, but you can delete it if you want).
+
+The Memory is locked during accesses, so it is safe to read or write
+it from multiple parallel goaljobs sessions.
+
+Keys and values are strings.  The keys should be globally unique, so
+it is suggested you use some application-specific prefix.  eg:
+C<myapp-key>
+
+A common pattern is:
+
+let goal tested version =
+  let key = "myapp-tested-" ^ version in
+  target (memory_exists key);
+  (* some code to test this version *)
+  memory_set key "1"
+
+Note in that example the value C<1> is arbitrary.  You just want to
+store I<any> value so that a later call to C<memory_exists> will
+succeed.
+
+For information about C<Goaljobs.memory_*> APIs see the
+C<goaljobs.mli> file / API documentation.
+
+=head1 FILES
+
+=over 4
+
+=item C</bin/sh>
+
+This is the default shell used by C<sh*> APIs.  You can change
+the shell by setting the C<Goaljobs.shell> reference.
+
+=item C<curl>
+
+The curl program (on the path) is used to check for and download
+URLs by APIs such as C<Goaljobs.url_exists>.
+
+=item C<~/.goaljobs-memory>
+
+Persistent key/value store used when you use the C<Goaljobs.memory_*>
+APIs.
+
+=back
+
+=head1 ENVIRONMENT VARIABLES
+
+=over 4
+
+=item B<builddir>
+
+This environment variable is set to the current directory when the
+goals program starts, and is available in goals, shell scripts, etc.
+
+=back
+
+=head1 SEE ALSO
+
+L<goaljobs(1)>
+
+=head1 AUTHORS
+
+Richard W.M. Jones <rjones @ redhat . com>
+
+=head1 COPYRIGHT
+
+(C) Copyright 2013 Red Hat Inc.,
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
index 3e1ca0c..c1dff52 100644 (file)
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *)
 
-(** {1 Goaljobs library of useful helper functions.} *)
+(** {1 Goaljobs library API} *)
 
-(** {2 Target and require}
-
-    These are used to write goals.
-
-    Normally you write a goal with zero or one [target] and
-    zero or more [require]s, as the examples below should make
-    clear.
-
-    In the first example, the target is that the [o_file] (object) exists
-    and is newer than the [c_file] (source).  The goal
-    meets that target by running the C compiler ([cc]) which, if it
-    succeeds, will ensure that the object file exists and is newer
-    than the source file.
-
-    {v
-    let goal compiled c_file =
-        let o_file = change_file_extension "o" c_file in
-        target (more_recent [o_file] [c_file]);
-
-        sh "cd $builddir && cc -c %s -o %s" c_file o_file
-    v}
-
-    In the second example, the goal requires that several files
-    have been compiled ([require (compiled ...)])
-    before it can link the final program:
-
-    {v
-    let goal built program source =
-        target (more_recent [program] [source]);
-
-        require (compiled source);
-
-        let object = change_file_extension "o" source in
-        sh "cd $builddir && cc %s -o %s" object program
-    v}
-
-*)
+(** {2 Target and require} *)
 
 val target : bool -> unit
   (** [target] {i condition} defines the target condition that {b will}
@@ -123,53 +87,7 @@ val require : (unit -> unit) -> unit
       placed anywhere within the goal, as long as you put them
       before they are needed. *)
 
-(** {2 Periodic jobs}
-
-    If you want to have a goal that runs when some outside event
-    happens you have three choices: Manually run the script (this is
-    basically what [make] forces you to do).  Have some sort of hook
-    that runs the script (eg. a git hook).  Or use a periodic job to
-    poll for an event or change.
-
-    Periodic jobs run regularly to poll for an outside event or
-    change.  If a script has periodic jobs, then it runs continuously
-    (or until you kill it).
-
-    An example of a script that checks for new git commits and when
-    it sees one it will ensure it passes the tests:
-
-    {v
-    let repo = Sys.getenv "HOME" // "repo"
-
-    let goal git_commit_tested commit =
-        let key = sprintf "repo-tested-%s" commit in
-        target (memory_exists key);
-
-        sh "
-            git clone %s test
-            cd test
-            ./configure
-            make
-            make check
-        ";
-
-        (* Record that this commit was tested successfully. *)
-        memory_set key "1"
-
-    every 30 minutes (fun () ->
-        let commit = shout "cd %s && git rev-parse HEAD" repo in
-        (* Require that this commit has been tested. *)
-        require (git_commit_tested commit)
-    )
-    v}
-
-    Some notes about the above example: Firstly only the current HEAD
-    commit is required to be tested.  This is because older commits
-    are irrelevant and because if they failed the test before there is
-    not point retesting them (commits are immutable).  Secondly we use
-    the Memory to remember that we have successfully tested a commit.
-    This is what stops the program from repeatedly testing the same
-    commit. *)
+(** {2 Periodic jobs} *)
 
 (* This is what lets you write '30 minutes' etc: *)
 type period_t = Seconds | Days | Months | Years
@@ -269,49 +187,7 @@ val (//) : string -> string -> string
 val quote : string -> string
   (** Quote the string to make it safe to pass directly to the shell. *)
 
-(** {2 Shell}
-
-    Call out to the Unix shell.  [/bin/sh] is used unless you set
-    {!shell} to some other value.  Note that the environment variable
-    [SHELL] is {i not} used.
-
-    {!sh}, {!shout}, {!shlines} work like [printf].  ie. You can
-    substitute variables using [%s], [%d] and so on.  For example:
-
-    {v
-      sh "rsync foo-%s.tar.gz example.com:/html/" version
-    v}
-
-    Each shell runs in a new temporary directory.  The temporary
-    directory and all its contents is deleted after the shell exits.
-    If you want to save any data, [cd] somewhere.  If you don't
-    want the temporary directory creation, use [~tmpdir:false].
-
-    The environment variable [$builddir] is exported to the script.
-    This is the current directory when the goaljobs program was started.
-
-    Each invocation of {!sh} (etc) is a single shell (this is slightly
-    different from how [make] works).  For example:
-
-    {v
-      sh "
-        package=foo-%s
-        tarball=$package.tar.gz
-        cp $HOME/$tarball .
-        tar zxf $tarball
-        cd $package
-        ./configure
-        make
-     " version
-    v}
-
-    The shell error mode is set such that if any single command
-    returns an error then the {!sh} function as a whole exits with
-    an error.  Write:
-    {v command ||: v}
-    to ignore the result of a command.
-
-*)
+(** {2 Shell} *)
 
 val sh : ?tmpdir:bool -> ('a, unit, string, unit) format4 -> 'a
   (** Run the command(s).
@@ -370,34 +246,7 @@ val filter_file_extension : string -> string list -> string
       would return [["bar.o"]] (a single element list). *)
 *)
 
-(** {2 Memory (persistent key/value storage)}
-
-    "The Memory" is key/value storage which persists across goaljobs
-    sessions.  It is stored in the file [$HOME/.goaljobs-memory]
-    (which is a binary file, but you can delete it if you want).
-
-    The Memory is locked during accesses, so it is safe to read
-    or write it from multiple parallel goaljobs sessions.
-
-    Keys and values are strings.  The keys should be globally
-    unique, so it is suggested you use some application-specific
-    prefix.  eg: "myapp-key"
-
-    A common pattern is:
-
-    {v
-    let goal tested version =
-        let key = "myapp-tested-" ^ version in
-        target (memory_exists key);
-
-        ... some work to test version ...
-
-        memory_set key "1"
-    v}
-
-    Note in that example the value ["1"] is arbitrary.  You just
-    want to store {i any} value so that a later call to {!memory_exists}
-    will succeed. *)
+(** {2 Memory (persistent key/value storage)} *)
 
 val memory_exists : string -> bool
   (** [memory_exists key] checks that the named [key] exists in
@@ -416,29 +265,7 @@ val memory_get : string -> string option
 val memory_delete : string -> unit
   (** Delete the [key].  If the key doesn't exist, has no effect. *)
 
-(** {2 Publishing goals}
-
-    To "publish" a goal means it's available on the command line
-    for users to use directly.
-
-    Goals that have zero arguments are {b automatically published}.
-    So for example:
-
-    {v
-    let goal clean () = sh "rm *~"
-    v}
-
-    can be used on the command line:
-
-    {v ./script clean v}
-
-    The special goal called [all] (if it exists) is run implicitly
-    unless the user specifies another goal.  Unlike [make], there is
-    nothing special about the first goal in the file.
-
-    You can also publish goals, especially ones which take a non-zero
-    number of parameters, by calling {!publish}.
-*)
+(** {2 Publishing goals} *)
 
 val publish : string -> (string list -> unit) -> unit
   (** Publish the named goal.
diff --git a/goaljobs.pod b/goaljobs.pod
new file mode 100644 (file)
index 0000000..3aef222
--- /dev/null
@@ -0,0 +1,118 @@
+=encoding utf8
+
+=head1 NAME
+
+goaljobs - make and cron replacement and business rules manager
+
+=head1 SUMMARY
+
+ goaljobs [-o output] [lib.ml...] goals.ml
+
+ ./goals -l
+
+ ./goals all
+ ./goals
+
+=head1 DESCRIPTION
+
+Goaljobs is a flexible build system and business rules manager similar
+to make and cron, but much more powerful.  You can use it to automate
+many complex tasks that have multiple steps (even with manual steps)
+that have to be carried out in dependency order.
+
+=head2 GETTING STARTED
+
+For a tutorial-like introduction to goaljobs, see
+L<http://rwmj.wordpress.com/tag/goaljobs/>
+
+For examples, see the C<examples/> directory in the source and
+L<http://git.annexia.org/?p=goals.git;a=summary>
+
+For reference documentation on how to write scripts, see the
+L<goaljobs-reference(1)> man page.
+
+To read about the Goaljobs API, look for the file C<goaljobs.mli> (in
+the source code or installed as part of the goaljobs package), or see
+the HTML documentation installed as part of the goaljobs package.
+
+=head2 GOALJOBS WRAPPER SCRIPT
+
+C<goaljobs> is a wrapper script that compiles your goals into a binary
+(program) which can then be run.  It takes a set of input files
+(C<*.ml>) and writes a single output program.  The output program has
+the same name as the last input file minus C<.ml>, but you can
+override this using the I<-o> option.
+
+Note that the C<goaljobs> script is just a simple wrapper around the
+OCaml compiler (L<ocamlopt(1)>).
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display short help and exit.
+
+=item B<-g>
+
+Enable debugging symbols in the output binary.
+
+=item B<-I> directory
+
+=item B<-I> +package
+
+Pass these options directly to the OCaml compiler.  You can use this
+to include OCaml modules from another directory.  The C<+package> form
+includes a package relative to the OCaml library directory
+(C<ocamlc -where>).
+
+=item B<-o> output
+
+Set the name of the output binary.
+
+The default is to use the name of the final source file, after
+removing the C<.ml> extension.
+
+=item B<--package> package(s)
+
+This passes I<-package package(s)> to L<ocamlfind(1)>.  You can use it
+to enable OCaml packages, eg:
+
+ goaljobs --package bigarray,libvirt source.ml
+
+=item B<--pkgdir> dir
+
+If goaljobs is installed in a non-standard directory, pass the name of
+the directory using this option.  You can also use this if you want to
+run goaljobs without installing it (pass the goaljobs source
+directory).
+
+=back
+
+=head1 SEE ALSO
+
+L<goaljobs-reference(1)>,
+L<ocamlopt(1)>
+
+=head1 AUTHORS
+
+Richard W.M. Jones <rjones @ redhat . com>
+
+=head1 COPYRIGHT
+
+(C) Copyright 2013 Red Hat Inc.,
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
index 7532cb8..f84975d 100644 (file)
@@ -15,7 +15,7 @@ BuildRequires:   ocaml-findlib-devel
 BuildRequires:   ocaml-camlp4-devel
 
 # For building manual pages.
-BuildRequires:   /usr/bin/perldoc
+BuildRequires:   perl-podlators
 
 # Requires camlp4 and ocamlfind at runtime.
 Requires:        /usr/bin/ocamlc
@@ -69,7 +69,7 @@ rm $RPM_BUILD_ROOT%{_libdir}/ocaml/goaljobs/goaljobs.cma
 %else
 %{_libdir}/ocaml/goaljobs/goaljobs.cma
 %endif
-#%{_mandir}/man1/*.1*
+%{_mandir}/man1/*.1*
 
 
 %changelog