Add .gitignore file for git.
[ocaml-csv.git] / csvtool.ml
index d944160..4e067b7 100644 (file)
@@ -1,5 +1,5 @@
 (* Handy tool for managing CSV files.
- * $Id: csvtool.ml,v 1.8 2006-11-24 15:49:24 rich Exp $
+ * $Id: csvtool.ml,v 1.11 2008-10-27 21:57:48 rich Exp $
  *)
 
 open Printf
@@ -303,6 +303,27 @@ let cmd_replace ~input_sep ~output_sep ~chan colspec update files =
   let csv = csv @ update in
   save_out ~separator:output_sep chan csv
 
+let cmd_call ~input_sep ~output_sep ~chan command files =
+  (* Avoid loading the whole file into memory. *)
+  let f row =
+    let cmd =
+      command ^ " " ^ String.concat " " (List.map Filename.quote row) in
+    let code = Sys.command cmd in
+    if code <> 0 then (
+      eprintf "%s: terminated with exit code %d\n" command code;
+      exit code
+    )
+  in
+  List.iter (
+    fun filename ->
+      let in_chan, close =
+       match filename with
+       | "-" -> stdin, false
+       | filename -> open_in filename, true in
+      load_rows ~separator:input_sep f in_chan;
+      if close then close_in in_chan
+  ) files
+
 let rec uniq = function
   | [] -> []
   | [x] -> [x]
@@ -364,6 +385,21 @@ let cmd_join ~input_sep ~output_sep ~chan colspec1 colspec2 files =
   ) csv in
   save_out ~separator:output_sep chan csv
 
+let rec cmd_trim ~input_sep ~output_sep ~chan (top, left, right, bottom) files =
+  let csv = List.concat (List.map (load ~separator:input_sep) files) in
+  let csv = trim ~top ~left ~right ~bottom csv in
+  save_out ~separator:output_sep chan csv
+
+and trim_flags flags =
+  let set c =
+    try ignore (String.index flags c); true with Not_found -> false
+  in
+  let top = set 't' in
+  let left = set 'l' in
+  let right = set 'r' in
+  let bottom = set 'b' in
+  (top, left, right, bottom)
+
 (* Process the arguments. *)
 let usage =
   "csvtool - Copyright (C) 2005-2006 Richard W.M. Jones, Merjis Ltd.
@@ -439,7 +475,8 @@ Commands:
 
       Example:
         csvtool join 1 2 coll1.csv coll2.csv > output.csv
-        If coll1.csv contains:
+
+        In the above example, if coll1.csv contains:
           Computers,$40
           Software,$100
         and coll2.csv contains:
@@ -453,6 +490,15 @@ Commands:
 
       Example: csvtool square input.csv > input-square.csv
 
+  trim [tlrb]+
+    Trim empty cells at the top/left/right/bottom of the CSV file.
+
+      Example:
+        csvtool trim t input.csv    # trims empty rows at the top only
+        csvtool trim tb input.csv   # trims empty rows at the top & bottom
+        csvtool trim lr input.csv   # trims empty columns at left & right
+        csvtool trim tlrb input.csv # trims empty rows/columns all around
+
   sub r c rows cols
     Take a square subset of the CSV, top left at row r, column c, which
     is rows deep and cols wide.  'r' and 'c' count from 1, or
@@ -467,6 +513,36 @@ Commands:
         csvtool replace 3 updates.csv original.csv > new.csv
         mv new.csv original.csv
 
+  call command
+    This calls the external command (or shell function) 'command'
+    followed by a parameter for each column in the CSV file.  The
+    external command is called once for each row in the CSV file.
+    If any command returns a non-zero exit code then the whole
+    program terminates.
+
+      Tip:
+        Use the shell command 'export -f funcname' to export
+        a shell function for use as a command.  Within the
+        function, use the positional parameters $1, $2, ...
+        to refer to the columns.
+
+      Example (with a shell function):
+        function test {
+          echo Column 1: $1
+          echo Column 2: $2
+        }
+        export -f test
+        csvtool call test my.csv
+
+        In the above example, if my.csv contains:
+          how,now
+          brown,cow
+        then the output is:
+          Column 1: how
+          Column 2: now
+          Column 1: brown
+          Column 2: cow
+
   readable
     Print the input CSV in a readable format.
 
@@ -597,6 +673,11 @@ let () =
      | "drop" :: rows :: files ->
         let rows = int_of_string rows in
         cmd_drop ~input_sep ~output_sep ~chan rows files
+     | "call" :: command :: files ->
+        cmd_call ~input_sep ~output_sep ~chan command files
+     | "trim" :: flags :: files ->
+        let flags = trim_flags flags in
+        cmd_trim ~input_sep ~output_sep ~chan flags files
      | _ ->
         prerr_endline (Sys.executable_name ^ " --help for usage");
         exit 2