From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.1.3 (2006-06-01) on yquem.inria.fr X-Spam-Level: * X-Spam-Status: No, score=1.8 required=5.0 tests=AWL,DNS_FROM_SECURITYSAGE, SPF_FAIL autolearn=disabled version=3.1.3 X-Original-To: caml-list@yquem.inria.fr Delivered-To: caml-list@yquem.inria.fr Received: from mail4-relais-sop.national.inria.fr (mail4-relais-sop.national.inria.fr [192.134.164.105]) by yquem.inria.fr (Postfix) with ESMTP id 585FFBB84 for ; Mon, 27 Oct 2008 04:30:06 +0100 (CET) X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: Av0DAB/SBEnAXQImgWdsb2JhbACTfQEBFiKrP4NP X-IronPort-AV: E=Sophos;i="4.33,490,1220220000"; d="scan'208";a="30777247" Received: from discorde.inria.fr ([192.93.2.38]) by mail4-smtp-sop.national.inria.fr with ESMTP; 27 Oct 2008 04:30:05 +0100 Received: from mail3-relais-sop.national.inria.fr (mail3-relais-sop.national.inria.fr [192.134.164.104]) by discorde.inria.fr (8.13.6/8.13.6) with ESMTP id m9R3U4Ew029547 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=OK) for ; Mon, 27 Oct 2008 04:30:05 +0100 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: Av4AAJfSBElQW+UCe2dsb2JhbACTfQEBFiIEqzmDTw X-IronPort-AV: E=Sophos;i="4.33,490,1220220000"; d="scan'208";a="18538106" Received: from main.gmane.org (HELO ciao.gmane.org) ([80.91.229.2]) by mail3-smtp-sop.national.inria.fr with ESMTP/TLS/AES256-SHA; 27 Oct 2008 04:30:04 +0100 Received: from list by ciao.gmane.org with local (Exim 4.43) id 1KuInk-0001hJ-Lc for caml-list@inria.fr; Mon, 27 Oct 2008 03:29:56 +0000 Received: from ivr94-8-88-162-26-239.fbx.proxad.net ([88.162.26.239]) by main.gmane.org with esmtp (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Mon, 27 Oct 2008 03:29:56 +0000 Received: from zheng_li by ivr94-8-88-162-26-239.fbx.proxad.net with local (Gmexim 0.1 (Debian)) id 1AlnuQ-0007hv-00 for ; Mon, 27 Oct 2008 03:29:56 +0000 X-Injected-Via-Gmane: http://gmane.org/ To: caml-list@inria.fr From: Zheng Li Subject: [ANN] camlish: a simple module for shell scripting in OCaml Date: Mon, 27 Oct 2008 04:30:32 +0100 Message-ID: <490535D8.8020500@users.sourceforge.net> Mime-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit X-Complaints-To: usenet@ger.gmane.org X-Gmane-NNTP-Posting-Host: ivr94-8-88-162-26-239.fbx.proxad.net User-Agent: Thunderbird 2.0.0.17 (Windows/20080914) Sender: news X-Miltered: at discorde with ID 490535BC.000 by Joe's j-chkmail (http://j-chkmail . ensmp . fr)! X-Spam: no; 0.00; ocaml:01 ocaml:01 bug:01 full-fledged:01 compiler:01 camlp:01 syntax:01 glues:01 inputs:01 seq:01 descriptors:01 syntax:01 seq:01 synchronous:01 redirected:01 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 let c1 = cmd "seq 1 20" (* (unit, unit) cmd *) let c2 = func (List.map float_of_int) (* (int list, float list) cmd *) - 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. 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 *) - command composition: pipeline(>>>), sequence (&&&) and subshell(~$) and more. let c5 = c3 >>> c4 (* (unit, float list) cmd *) let c6 = {(cmd "pwd" &&& cmd "date") with pout = pout_file "log"} (* (unit, unit) cmd *) - 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.) !! c5 (* get float list [1.; ...; 20.] *) The example is made up for illustration. In practice you'd instead write !! { (cmd "seq 1 20") with pout = pout_lines |= float_of_string } and commands without redirection look normal: !! (cmd "ls -l" >>> cmd "wc -l") !$ "ls -l | wc -l" (* resort to /bin/sh pipe *) Here is an example collaborating commands and functions to count the numbers x between 1 .. n where x mod 3 or 5 is 0. 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 *) 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. 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" *) * 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. open Camlish open List open Prelude module A = Array module S = String module U = Unix * 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: $ 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 = [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;; 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.