This is an automated email from the git hooks/post-receive script. glondu pushed a commit to branch master in repository ocaml-csv.
commit 825a995a48f15042cc84cf7cfff8b234cf87c021 Author: Stephane Glondu <st...@glondu.net> Date: Tue Jan 26 15:04:40 2016 +0100 Imported Upstream version 1.4.2 --- _oasis | 2 +- examples/csvtool.ml | 157 ++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 111 insertions(+), 48 deletions(-) diff --git a/_oasis b/_oasis index 7ca476f..86f74b8 100644 --- a/_oasis +++ b/_oasis @@ -1,7 +1,7 @@ # -*-conf-*- OASISFormat: 0.4 Name: csv -Version: 1.4.1 +Version: 1.4.2 Synopsis: A pure OCaml library to read and write CSV files. Description: This is a pure OCaml library to read and write CSV files, including all extensions used by Excel — e.g. quotes, newlines, diff --git a/examples/csvtool.ml b/examples/csvtool.ml index 83fcc66..d2ece8a 100644 --- a/examples/csvtool.ml +++ b/examples/csvtool.ml @@ -190,21 +190,27 @@ let cmd_readable ~input_sep ~chan files = let csv = List.concat (List.map (Csv.load ~separator:input_sep) files) in Csv.save_out_readable chan csv +let iter_csv_rows ~input_sep ~f files = + List.iter (fun filename -> + let in_chan, close = + match filename with + | "-" -> stdin, false + | filename -> open_in filename, true in + try + Csv.iter ~f (Csv.of_channel ~separator:input_sep in_chan); + if close then close_in in_chan + with Exit -> + if close then close_in in_chan + ) files + + let cmd_cat ~input_sep ~output_sep ~chan files = (* Avoid loading the whole file into memory. *) let chan = Csv.to_channel ~separator:output_sep chan in let f row = Csv.output_record chan row in - List.iter ( - fun filename -> - let in_chan, close = - match filename with - | "-" -> stdin, false - | filename -> open_in filename, true in - Csv.iter ~f (Csv.of_channel ~separator:input_sep in_chan); - if close then close_in in_chan - ) files + iter_csv_rows ~input_sep ~f files let cmd_paste ~input_sep ~output_sep ~chan files = (* Return the 1st row, concatenation of all 1st rows; whether all @@ -305,15 +311,7 @@ let cmd_set_columns ~input_sep ~output_sep ~chan cols files = let csv = Csv.set_columns ~cols csv in Csv.output_all (Csv.to_channel ~separator:output_sep chan) csv in - List.iter ( - fun filename -> - let in_chan, close = - match filename with - | "-" -> stdin, false - | filename -> open_in filename, true in - Csv.iter ~f (Csv.of_channel ~separator:input_sep in_chan); - if close then close_in in_chan - ) files + iter_csv_rows ~input_sep ~f files let cmd_set_rows ~input_sep ~output_sep ~chan rows files = let csv = List.concat (List.map (Csv.load ~separator:input_sep) files) in @@ -331,18 +329,9 @@ let cmd_head ~input_sep ~output_sep ~chan rows files = decr nr_rows; Csv.output_record chan row ) + else raise Exit in - List.iter ( - fun filename -> - if !nr_rows > 0 then ( - let in_chan, close = - match filename with - | "-" -> stdin, false - | filename -> open_in filename, true in - Csv.iter ~f (Csv.of_channel ~separator:input_sep in_chan); - if close then close_in in_chan - ) - ) files + iter_csv_rows ~input_sep ~f files let cmd_drop ~input_sep ~output_sep ~chan rows files = (* Avoid loading the whole file into memory. *) @@ -354,15 +343,7 @@ let cmd_drop ~input_sep ~output_sep ~chan rows files = else decr nr_rows in - List.iter ( - fun filename -> - let in_chan, close = - match filename with - | "-" -> stdin, false - | filename -> open_in filename, true in - Csv.iter ~f (Csv.of_channel ~separator:input_sep in_chan); - if close then close_in in_chan - ) files + iter_csv_rows ~input_sep ~f files let cmd_square ~input_sep ~output_sep ~chan files = let csv = List.concat (List.map (Csv.load ~separator:input_sep) files) in @@ -404,6 +385,82 @@ let cmd_transpose ~input_sep ~output_sep ~chan files = Csv.output_all (Csv.to_channel ~separator:output_sep chan) tr ) files + +type format_el = String of string | Col of int + +let is_digit c = '0' <= c && c <= '9' + +(* Return the non-negative number starting at [i0 < i1 < len_s = + String.length s] and the index of the first character after that + number. It is expected that [s.[i0]] be a digit, otherwise + [Failure] is be raised. *) +let rec get_digit s len_s i0 i1 = + if i1 < len_s then + if is_digit s.[i1] then get_digit s len_s i0 (i1 + 1) + else (int_of_string(String.sub s i0 (i1 - i0)), i1) + else (* i0 < i1 (>)= len_s *) + (int_of_string(String.sub s i0 (len_s - i0)), len_s) + +(* Prepend to the format [fmt] the substring s.[i0 .. i1 - 1] unless + it is empty. *) +let prepend_substring s i0 i1 fmt = + if i0 < i1 then String(String.sub s i0 (i1 - i0)) :: fmt + else (* i0 ≥ i1, empty substring *) fmt + +(* [i0 ≤ i1 ≤ len_s] *) +let rec split_format s len_s i0 i1 = + if i1 >= len_s then + if i0 < len_s then [String(String.sub s i0 (len_s - i0))] + else [] + else if s.[i1] = '%' then + let i2 = i1 + 1 in + if i2 >= len_s then + split_format s len_s i0 i2 (* consider a final '%' as a normal char *) + else if is_digit s.[i2] then + let col, i3 = get_digit s len_s i2 (i2 + 1) in + prepend_substring s i0 i1 (Col col :: split_format s len_s i3 i3) + else if s.[i2] = '(' then + if i2 + 1 < len_s && is_digit s.[i2 + 1] then ( + let col, i3 = get_digit s len_s (i2 + 1) (i2 + 2) in + if i3 >= len_s || s.[i3] <> ')' then ( + let r = String.sub s i1 (i3 - i1) in + failwith(sprintf "Column format %S not terminated by ')'" r) + ); + prepend_substring + s i0 i1 (Col col :: split_format s len_s (i3 + 1) (i3 + 1)) + ) + else failwith "Column format %( not followed by a number" + else if s.[i2] = '%' then + let i3 = i2 + 1 in + String(String.sub s i0 (i1 - i0 + 1)) :: split_format s len_s i3 i3 + else (* % + non-digit, consider it a literal '%' *) + split_format s len_s i0 i2 + else if s.[i1] = '\\' then + (* Handle usual escapes. *) + let i2 = i1 + 1 in + if i2 >= len_s then split_format s len_s i0 i2 + else if s.[i2] = 'n' then + let i3 = i2 + 1 in + prepend_substring s i0 i1 (String "\n" :: split_format s len_s i3 i3) + else if s.[i2] = 'r' then + let i3 = i2 + 1 in + prepend_substring s i0 i1 (String "\r" :: split_format s len_s i3 i3) + else if s.[i2] = 't' then + let i3 = i2 + 1 in + prepend_substring s i0 i1 (String "\t" :: split_format s len_s i3 i3) + else split_format s len_s i0 i2 + else + split_format s len_s i0 (i1 + 1) + +let print_format row = function + | String s -> print_string s + | Col c -> try print_string (List.nth row (c - 1)) with _ -> () + +let cmd_format ~input_sep fmt files = + let fmt = split_format fmt (String.length fmt) 0 0 in + iter_csv_rows ~input_sep files + ~f:(fun row -> List.iter (print_format row) fmt) + let cmd_call ~input_sep command files = (* Avoid loading the whole file into memory. *) (* Use bash if it exists to enable the [command] to be an exported @@ -419,15 +476,7 @@ let cmd_call ~input_sep command files = exit code ) in - List.iter ( - fun filename -> - let in_chan, close = - match filename with - | "-" -> stdin, false - | filename -> open_in filename, true in - Csv.iter ~f (Csv.of_channel ~separator:input_sep in_chan); - if close then close_in in_chan - ) files + iter_csv_rows ~input_sep ~f files let rec uniq = function | [] -> [] @@ -508,6 +557,7 @@ and trim_flags flags = (* Process the arguments. *) let usage = "csvtool - Copyright (C) 2005-2006 Richard W.M. Jones, Merjis Ltd. + - Copyright (C) 2007- Richard W.M. Jones & Christophe Troestler csvtool is a tool for performing manipulations on CSV files from shell scripts. @@ -634,6 +684,17 @@ Commands: transpose input.csv Transpose the lines and columns of the CSV file. + format fmt + Print each row of the files according to the format 'fmt'. + Each occurrence of \"%i\" or \"%(i)\" (where 'i' is a number) in + 'fmt' is replaced by the content of column number 'i' (remember + that the leftmost column is numbered 1 in the traditional + spreadsheet fashion). A literal percent is obtained by doubling it. + The usual escape sequences \\n, \\r, and \\t are recognized. + + Example: + csvtool format '%(1) -> %8%%\\n' input.csv + call command This calls the external command (or shell function) 'command' followed by a parameter for each column in the CSV file. The @@ -802,6 +863,8 @@ let () = cmd_drop ~input_sep ~output_sep ~chan rows files | "transpose" :: files -> cmd_transpose ~input_sep ~output_sep ~chan files + | "format" :: fmt :: files -> + cmd_format ~input_sep fmt files | "call" :: command :: files -> cmd_call ~input_sep command files | "trim" :: flags :: files -> -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ocaml-maint/packages/ocaml-csv.git _______________________________________________ Pkg-ocaml-maint-commits mailing list Pkg-ocaml-maint-commits@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-ocaml-maint-commits