Fixed 'make clean'
[ocaml-csv.git] / csvtool.ml
1 (* Handy tool for managing CSV files.
2  * $Id: csvtool.ml,v 1.1 2005-05-24 13:52:50 rich Exp $
3  *)
4
5 open Printf
6 open Csv
7
8 let cmd_cols ~separator ~csv ~chan cols =
9   let cols = List.map int_of_string cols in
10
11   let output = List.map (
12     fun row ->
13       let n = List.length row in
14       let row = List.map (
15         fun col_wanted ->
16           if 0 <= col_wanted && col_wanted < n then
17             List.nth row col_wanted
18           else
19             ""
20       ) cols in
21       row
22   ) csv in
23   save_out ~separator chan output
24
25 let cmd_namedcols ~separator ~csv ~chan names =
26   let header, data =
27     match csv with
28       | [] -> failwith "no rows in this CSV file"
29       | h :: t -> h, t in
30   let data = associate header data in
31   let data = List.map (
32     fun row -> List.map (fun name -> List.assoc name row) names
33   ) data in
34   save_out ~separator chan data
35
36 let cmd_width ~csv ~chan () =
37   fprintf chan "%d\n" (columns csv)
38
39 let cmd_height ~csv ~chan () =
40   fprintf chan "%d\n" (lines csv)
41
42 let cmd_readable ~csv ~chan () =
43   save_out_readable chan csv
44
45 (* Process the arguments. *)
46 let usage =
47   "csvtool - Copyright (C) 2005 Richard W.M. Jones, Merjis Ltd.
48
49 csvtool is a tool for performing manipulations on CSV files from shell scripts.
50
51 Summary:
52   csvtool [-options] command [command-args] < input.csv
53
54 Commands:
55   col [col1] [col2] ...
56     Return one or more columns from the CSV file.  Columns are numbered
57     starting from zero.
58
59   namedcol [name1] [name2] ...
60     Assuming the first row of the CSV file is a list of column headings,
61     this returned the column(s) with the named headings.
62
63   width
64     Return the maximum width of the CSV file (number of columns in the
65     widest row).
66
67   height
68     Return the number of rows in the CSV file.
69
70   readable
71     Print the input CSV in a readable format.
72
73 Input and output files:
74   csvtool normally processes its input from stdin and writes its output
75   to stdout.  Use the -i and -o options to override this behaviour.
76
77 Options:"
78
79 let () =
80   let input_sep = ref ',' in
81   let set_input_sep = function
82     | "TAB" -> input_sep := '\t'
83     | "COMMA" -> input_sep := ','
84     | s -> input_sep := s.[0]
85   in
86
87   let output_sep = ref ',' in
88   let set_output_sep = function
89     | "TAB" -> output_sep := '\t'
90     | "COMMA" -> output_sep := ','
91     | s -> output_sep := s.[0]
92   in
93
94   let input_file = ref "" in
95   let output_file = ref "" in
96
97   let argspec = [
98     "-t", Arg.String set_input_sep,
99     "Input separator char.  Use -t TAB for tab separated input.";
100     "-u", Arg.String set_output_sep,
101     "Output separator char.  Use -t TAB for tab separated output.";
102     "-i", Arg.Set_string input_file,
103     "Read CSV input from file (instead of stdin)";
104     "-o", Arg.Set_string output_file,
105     "Write output to file (instead of stdout)"
106   ] in
107
108   let rest = ref [] in
109   let set_rest str =
110     rest := str :: !rest
111   in
112
113   Arg.parse argspec set_rest usage;
114
115   let input_sep = !input_sep in
116   let output_sep = !output_sep in
117   let input_file = !input_file in
118   let output_file = !output_file in
119   let rest = List.rev !rest in
120
121   let cmd, args =
122     match rest with
123       | [] -> prerr_endline (Sys.executable_name ^ " --help for usage"); exit 1
124       | h :: t -> h, t in
125
126   (* Read the input file. *)
127   let input =
128     if input_file <> "" then load ~separator:input_sep input_file
129     else load_in ~separator:input_sep stdin in
130
131   (* Set up the output file. *)
132   let chan =
133     if output_file <> "" then open_out output_file
134     else stdout in
135
136   (match cmd with
137      | "col" | "cols" ->
138          cmd_cols ~separator:output_sep ~csv:input ~chan args
139      | "namedcol" | "namedcols" ->
140          cmd_namedcols ~separator:output_sep ~csv:input ~chan args
141      | "width" ->
142          cmd_width ~csv:input ~chan ()
143      | "height" ->
144          cmd_height ~csv:input ~chan ()
145      | "readable" ->
146          cmd_readable ~csv:input ~chan ()
147      | _ -> prerr_endline (Sys.executable_name ^ " --help for usage")
148   );
149
150   if output_file <> "" then close_out chan