Version française
Home     About     Download     Resources     Contact us    
Browse thread
[ANN] camlish: a simple module for shell scripting in OCaml
[ Home ] [ Index: by date | by threads ]
[ Search: ]

[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
Date: -- (:)
From: Zheng Li <zheng_li@u...>
Subject: [ANN] camlish: a simple module for shell scripting in OCaml
Hi,

I'd like to announce the availability of a small module for shell 
scripting: camlish.

The project URL is: http://zheng.li/projects/ocaml/camlish, where you 
can get all the code and docs. A source repo is going to provide later.

It's rather experimental, bug reports are always welcome.

Have fun!


FYI, the rest parts are directly copy&pasted from the README file:
-----------------------------------------------------------------------


===== Motivation =====

I was thinking about using OCaml for shell scripting. I once looked at
Cash, but quickly went away: I didn't really want to read 100+ pages of
manual just for my little scripting purposes. Recently there came
Caml-Shcaml, it also successfully stopped me from exploring any further
with its ~700 APIs (Sure I believe learning a small portion of them can
be enough to get me a quick start, but still...)

I believe they are both excellent and full-fledged shell
implementations, but they are really overkilled for me. Vanilla OCaml is
already a full-functional language which I can do anything with, include
scripting. What I want is just a little more convenience on this aspect,
better with minimal learning cost, not to learn another "language".
Otherwise I could just resort to plain ocaml because I'm lazy. Moreover,
I don't want to patch my compiler source or use Camlp4 magic to have a
more shell-like syntax: it's probably nice-looking but alien.


==== Basics ====

So I wrote camlish, a plain OCaml module to ease shell scripting in
OCaml, especially for those thinking the same.

Camlish follows simple principles:

   * It deals with simple coordination and interaction with outside
     commands and shells. It provides a very thin layer of glues to
     ease that. That's all it does.

   * OCaml itself has far better language features than any shell
     script languages out there, so when camlish is used for scripting,
     it encourages you to stay on the OCaml side as much as possible,
     call external and push/pull the inputs/outputs back/forth as OCaml
     values, and sometime resort to /bin/sh if necessary.

==== Features ====

Here are some features of camlish:

   * Code base is small. The core is just 500+ loc, 30+ definitions
     (except operator sugar).

   * Interface is simple. It deals and only deals with the follows:

     - command construction: wrap a string or a OCaml function to a
       typed command

       <code ocaml>
       let c1 = cmd "seq 1 20"                    (* (unit, unit) cmd *)

       let c2 = func (List.map float_of_int)
                                        (* (int list, float list) cmd *)
       </code>

     - command redirection: redirect input/output/error to any files,
       file descriptors and OCaml values, via plain record syntax. We
       call all these direction adapters as "ports". Besides a number
       of primitive ports, we can define and compose our own "ports"
       for any data type.

       <code ocaml>
       let c3 = { c1 with pout = pout_lines }
       	       	    	      		  (* (unit, string list) cmd *)
       let c4 = { c2 with pin = int_of_string =| c2.pin }
                                     (* (string list, float list) cmd *)
       </code>

     - command composition: pipeline(>>>), sequence (&&&) and
       subshell(~$) and more.

       <code ocaml>
       let c5 = c3 >>> c4                    (* (unit, float list) cmd *)
       let c6 = {(cmd "pwd" &&& cmd "date") with pout = pout_file "log"}
                                                   (* (unit, unit) cmd *)
       </code>
       						
     - command execution: input/return values according to command's
       redirection. There are different execution schemes depending on
       various factors (e.g. having input value or not, running in
       background or not etc.)

       <code ocaml>
       !! c5                          (* get float list [1.; ...; 20.] *)
       </code>

       The example is made up for illustration. In practice you'd
       instead write

       <code ocaml>
       !! { (cmd "seq 1 20") with pout = pout_lines |= float_of_string }
       </code>

       and commands without redirection look normal:

       <code ocaml>
       !! (cmd "ls -l" >>> cmd "wc -l")
       !$ "ls -l | wc -l"                    (* resort to /bin/sh pipe *)
       </code>

       Here is an example collaborating commands and functions to count
       the numbers x between 1 .. n where x mod 3 or 5 is 0.

       <code ocaml>
       let count n=
         { (cmd "seq 1 %d" n) with pout = pout_lines }
         >>= int_of_string
         >>- List.filter (fun x -> x mod 3 = 0 || x mod 5 = 0)
         >>> {( cmd "wc -l") with
                pin = string_of_int =| pin_lines;
                pout = pout_string |- int_of_string }
                                          (* int -> (unit, int) cmd *)

       !! (count 1000)                     (* return OCaml int: 466 *)
       </code>

       Please read the manual for more details.

   * The interaction between external commands and OCaml values are
     implemented asynchronously inside (but with a synchronous
     interface). So it won't get stuck unnecessarily when dealing with
     large chunk of input/output.

     Moreover, a pair of stream ports are provided, so that you can
     read/write "not fully available" or infinite string/data on
     the borders of OCaml and commands. E.g.

     <code ocaml>
     let s = !!. { (cmd "yes") with pout = pout_stream "\n" }
                                  (* the endless output of "yes" is
				    redirected to a stream value. *)

     let _ = Stream.iter print_endline s     (* print infinite "y" *)
     </code>

   * It's tempting to use Camlp4 or to modify the compiler in order to
     achieving terse shell-like syntax, but we choose not. Camlish is a
     plain OCaml module.

   * It's also tempting to implement simple versions of common unix
     tools such as "ls" and "grep" as OCaml functions to achieve better
     composability. We opt not for the core module and not for now.

   * Camlish supports RC file written in OCaml, i.e. when you load
     camlish module from toplevel or run customized toplevel with
     Camlish embedded, it will try to read a file named ".camlishrc"
     from the current working directory firstly and the home directory
     of current user secondly. The RC file can be any plain ocaml file,
     e.g.

     <code ocaml>
     open Camlish
     open List
     open Prelude
     module A = Array
     module S = String
     module U = Unix
     </code>

   * When used interactively, camlish can additionally execute shell
     commands directly. So we can now use the toplevel as a shell,
     feeding it ocaml phrases and shell commands at the same time.

     Here is a trace of record:

     <code ocaml>
     $ rlwrap camlish   (* Or rlwrap ocaml unix.cma camlish.cma *)

            Objective Caml version 3.10.2
	
     [1] ls -l | wc -l;;
     53
     - : unit = ()
     [2] date;;
     Sat Oct 25 12:06:51 CEST 2008
     - : unit = ()
     [3] let f x = x;;
     val f : 'a -> 'a = <fun>
     [4] f 100;;
     - : int = 100
     [5] for i in `seq 1 3`; do echo "Now: "$i; done;;;
     Now: 1
     Now: 2
     Now: 3
     - : unit = ()
     [6] emacs -nw;;
     </code>

     In case there is some ambiguity, e.g. a OCaml value shadows a
     command name, a ";;;" ending can be used to enforce it is a
     command instead of OCaml toplevel phrase.


Please refer to the manual for more details.