diff --git a/CHANGELOG.md b/CHANGELOG.md index 08c0e5a..1e311bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.5.1 + +- `Sequence.IO` module, a very very simple way to read/write files +- options: `to_opt/of_opt/head/head_exn` + ## 0.5 - conversion with `klist` diff --git a/META b/META index f05073a..5fc1bff 100644 --- a/META +++ b/META @@ -1,6 +1,6 @@ # OASIS_START -# DO NOT EDIT (digest: 97bacb5c6907fa4ab239010b6f052d2a) -version = "0.5" +# DO NOT EDIT (digest: 3b9ebef180f5e4bdb720d2103ba95667) +version = "0.5.1" description = "Simple sequence (iterator) datatype and combinators" archive(byte) = "sequence.cma" archive(byte, plugin) = "sequence.cma" @@ -8,7 +8,7 @@ archive(native) = "sequence.cmxa" archive(native, plugin) = "sequence.cmxs" exists_if = "sequence.cma" package "invert" ( - version = "0.5" + version = "0.5.1" description = "Simple sequence (iterator) datatype and combinators" requires = "sequence delimcc" archive(byte) = "invert.cma" diff --git a/Makefile b/Makefile index ce1e9de..cdc0e22 100644 --- a/Makefile +++ b/Makefile @@ -57,10 +57,11 @@ push_stable: all git push origin git checkout master -VERSION=$(shell awk '/Version:/ {print $$2}' _oasis) +VERSION=$(shell awk '/^Version:/ {print $$2}' _oasis) update_next_tag: @echo "update version to $(VERSION)..." sed -i "s/NEXT_VERSION/$(VERSION)/g" *.ml *.mli + sed -i "s/NEXT_RELEASE/$(VERSION)/g" *.ml *.mli .PHONY: benchs tests examples update_next_tag push_doc push_stable diff --git a/README.md b/README.md index 17febb3..0ca3219 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,16 @@ way of iterating on a finite number of values, only allocating (most of the time one intermediate closure to do so. For instance, iterating on keys, or values, of a `Hashtbl.t`, without creating a list. +Documentation +============= + +See [the online API](http://cedeela.fr/~simon/software/sequence/Sequence.html). + Build ===== -You need OCaml, say OCaml 3.12 or OCaml 4.0. - - $ make +1. via opam `opam install sequence` +2. manually (need OCaml >= 3.12): `make all install` If you have `OUnit` installed, you can build and run tests with @@ -40,11 +44,6 @@ The module `examples/sexpr.mli` exposes the interface of the S-expression example library. It requires OCaml>=4.0 to compile, because of the GADT structure used in the monadic parser combinators part of `examples/sexpr.ml`. -Documentation -============= - -See [the online API](http://cedeela.fr/~simon/software/sequence/Sequence.html). - License ======= diff --git a/_oasis b/_oasis index 3e48dbe..d1452f2 100644 --- a/_oasis +++ b/_oasis @@ -1,6 +1,6 @@ OASISFormat: 0.4 Name: sequence -Version: 0.5 +Version: 0.5.1 Homepage: https://github.com/c-cube/sequence Authors: Simon Cruanes License: BSD-2-clause diff --git a/sequence.ml b/sequence.ml index cc14f74..9e5fb84 100644 --- a/sequence.ml +++ b/sequence.ml @@ -321,6 +321,17 @@ let min ?(lt=fun x y -> x < y) seq = exception ExitSequence +let head seq = + let r = ref None in + try + seq (fun x -> r := Some x; raise ExitSequence); None + with ExitSequence -> !r + +let head_exn seq = + match head seq with + | None -> invalid_arg "Sequence.head_exn" + | Some x -> x + let take n seq k = let count = ref 0 in try @@ -425,6 +436,12 @@ let to_list seq = List.rev (fold (fun y x -> x::y) [] seq) let to_rev_list seq = fold (fun y x -> x :: y) [] seq +let to_opt = head + +let of_opt o k = match o with + | None -> () + | Some x -> k x + let of_list l k = List.iter k l let to_array seq = @@ -699,3 +716,56 @@ let to_string ?sep pp_elt seq = let buf = Buffer.create 25 in pp_buf ?sep (fun buf x -> Buffer.add_string buf (pp_elt x)) buf seq; Buffer.contents buf + +(** {2 Basic IO} *) + +module IO = struct + let lines_of ?(mode=0o644) ?(flags=[Open_rdonly]) filename = + fun k -> + let ic = open_in_gen flags mode filename in + try + while true do + let line = input_line ic in + k line + done + with + | End_of_file -> close_in ic + | e -> close_in_noerr ic; raise e + + let chunks_of ?(mode=0o644) ?(flags=[]) ?(size=1024) filename = + fun k -> + let ic = open_in_gen flags mode filename in + try + let buf = String.create size in + let n = ref 0 in + let stop = ref false in + while not !stop do + n := 0; + (* try to read [size] chars. If [input] returns [0] it means + the end of file, so we stop, but first we yield the current chunk *) + while !n < size && not !stop do + let n' = input ic buf !n (size - !n) in + if n' = 0 then stop := true else n := !n + n'; + done; + if !n > 0 + then k (String.sub buf 0 !n) + done; + close_in ic + with e -> + close_in_noerr ic; + raise e + + let write_to ?(mode=0o644) ?(flags=[Open_creat;Open_wronly]) filename seq = + let oc = open_out_gen flags mode filename in + try + seq (fun s -> output oc s 0 (String.length s)); + close_out oc + with e -> + close_out oc; + raise e + + let write_lines ?mode ?flags filename seq = + write_to ?mode ?flags filename (snoc (intersperse "\n" seq) "\n") +end + + diff --git a/sequence.mli b/sequence.mli index e2691a9..04a6310 100644 --- a/sequence.mli +++ b/sequence.mli @@ -23,10 +23,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *) -(** {1 Transient iterators, that abstract on a finite sequence of elements.} *) +(** {1 Simple and Efficient Iterators} *) (** The iterators are designed to allow easy transfer (mappings) between data - structures, without defining n^2 conversions between the n types. The + structures, without defining [n^2] conversions between the [n] types. The implementation relies on the assumption that a sequence can be iterated on as many times as needed; this choice allows for high performance of many combinators. However, for transient iterators, the {!persistent} @@ -53,8 +53,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. of this memory structure, cheaply and repeatably. *) type +'a t = ('a -> unit) -> unit - (** Sequence abstract iterator type, representing a finite sequence of - values of type ['a]. *) + (** A sequence of values of type ['a]. If you give it a function ['a -> unit] + it will be applied to every element of the sequence successively. *) type +'a sequence = 'a t @@ -97,7 +97,7 @@ val repeat : 'a -> 'a t at {!take} and the likes if you iterate on it. *) val iterate : ('a -> 'a) -> 'a -> 'a t - (** [iterate f x] is the infinite sequence (x, f(x), f(f(x)), ...) *) + (** [iterate f x] is the infinite sequence [x, f(x), f(f(x)), ...] *) val forever : (unit -> 'b) -> 'b t (** Sequence that calls the given function to produce elements. @@ -113,7 +113,8 @@ val cycle : 'a t -> 'a t (** {2 Consume a sequence} *) val iter : ('a -> unit) -> 'a t -> unit - (** Consume the sequence, passing all its arguments to the function *) + (** Consume the sequence, passing all its arguments to the function. + Basically [iter f seq] is just [seq f]. *) val iteri : (int -> 'a -> unit) -> 'a t -> unit (** Iterate on elements and their index in the sequence *) @@ -253,12 +254,23 @@ val min : ?lt:('a -> 'a -> bool) -> 'a t -> 'a option (** Min element of the sequence, using the given comparison function. see {!max} for more details. *) +val head : 'a t -> 'a option + (** First element, if any, otherwise [None] + @since 0.5.1 *) + +val head_exn : 'a t -> 'a + (** First element, if any, fails + @raise Invalid_argument if the sequence is empty + @since 0.5.1 *) + val take : int -> 'a t -> 'a t (** Take at most [n] elements from the sequence. Works on infinite sequences. *) val take_while : ('a -> bool) -> 'a t -> 'a t - (** Take elements while they satisfy the predicate, then stops iterating *) + (** Take elements while they satisfy the predicate, then stops iterating. + Will work on an infinite sequence [s] if the predicate is false for at + least one element of [s]. *) val drop : int -> 'a t -> 'a t (** Drop the [n] first elements of the sequence. Lazy. *) @@ -307,9 +319,13 @@ val to_rev_list : 'a t -> 'a list val of_list : 'a list -> 'a t +val to_opt : 'a t -> 'a option + (** Alias to {!head} + @since 0.5.1 *) + val to_array : 'a t -> 'a array (** Convert to an array. Currently not very efficient because - and intermediate list is used. *) + an intermediate list is used. *) val of_array : 'a array -> 'a t @@ -322,6 +338,10 @@ val array_slice : 'a array -> int -> int -> 'a t (** [array_slice a i j] Sequence of elements whose indexes range from [i] to [j] *) +val of_opt : 'a option -> 'a t + (** Iterate on 0 or 1 values. + @since 0.5.1 *) + val of_stream : 'a Stream.t -> 'a t (** Sequence of elements of a stream (usable only once) *) @@ -482,16 +502,20 @@ module Infix : sig It will therefore be empty if [a < b]. *) val (>>=) : 'a t -> ('a -> 'b t) -> 'b t - (** Monadic bind (infix version of {!flat_map} *) + (** Monadic bind (infix version of {!flat_map} + @since 0.5 *) val (>|=) : 'a t -> ('a -> 'b) -> 'b t - (** Infix version of {!map} *) + (** Infix version of {!map} + @since 0.5 *) val (<*>) : ('a -> 'b) t -> 'a t -> 'b t - (** Applicative operator (product+application) *) + (** Applicative operator (product+application) + @since 0.5 *) val (<+>) : 'a t -> 'a t -> 'a t - (** Concatenation of sequences *) + (** Concatenation of sequences + @since 0.5 *) end include module type of Infix @@ -510,3 +534,54 @@ val pp_buf : ?sep:string -> (Buffer.t -> 'a -> unit) -> val to_string : ?sep:string -> ('a -> string) -> 'a t -> string (** Print into a string *) + +(** {2 Basic IO} + +Very basic interface to manipulate files as sequence of chunks/lines. The +sequences take care of opening and closing files properly; every time +one iterates over a sequence, the file is opened/closed again. + +Example: copy a file ["a"] into file ["b"], removing blank lines: + +{[ + Sequence.(IO.lines_of "a" |> filter (fun l-> l<> "") |> IO.write_lines "b");; +]} + +By chunks of [4096] bytes: + +{[ + Sequence.IO.(chunks_of ~size:4096 "a" |> write_to "b");; +]} + +@since 0.5.1 *) + +module IO : sig + val lines_of : ?mode:int -> ?flags:open_flag list -> + string -> string t + (** [lines_of filename] reads all lines of the given file. It raises the + same exception as would opening the file and read from it, except + from [End_of_file] (which is caught). The file is {b always} properly + closed. + Every time the sequence is iterated on, the file is opened again, so + different iterations might return different results + @param mode default [0o644] + @param flags default: [[Open_rdonly]] *) + + val chunks_of : ?mode:int -> ?flags:open_flag list -> ?size:int -> + string -> string t + (** Read chunks of the given [size] from the file. The last chunk might be + smaller. Behaves like {!lines_of} regarding errors and options. + Every time the sequence is iterated on, the file is opened again, so + different iterations might return different results *) + + val write_to : ?mode:int -> ?flags:open_flag list -> + string -> string t -> unit + (** [write_to filename seq] writes all strings from [seq] into the given + file. It takes care of opening and closing the file. + @param mode default [0o644] + @param flags used by [open_out_gen]. Default: [[Open_creat;Open_wronly]]. *) + + val write_lines : ?mode:int -> ?flags:open_flag list -> + string -> string t -> unit + (** Same as {!write_to}, but intercales ['\n'] between each string *) +end diff --git a/setup.ml b/setup.ml index b1ea76d..e293257 100644 --- a/setup.ml +++ b/setup.ml @@ -1,7 +1,7 @@ (* setup.ml generated for the first time by OASIS v0.4.4 *) (* OASIS_START *) -(* DO NOT EDIT (digest: 623687f8c58c30bc6ff9a8e5ebe8f593) *) +(* DO NOT EDIT (digest: d7a207daf3186cce7792651a50aaba59) *) (* Regenerated by OASIS v0.4.4 Visit http://oasis.forge.ocamlcore.org for more information and @@ -6826,7 +6826,7 @@ let setup_t = alpha_features = []; beta_features = []; name = "sequence"; - version = "0.5"; + version = "0.5.1"; license = OASISLicense.DEP5License (OASISLicense.DEP5Unit @@ -7192,7 +7192,7 @@ let setup_t = }; oasis_fn = Some "_oasis"; oasis_version = "0.4.4"; - oasis_digest = Some "6\2028\169\031Z \246[\195\132\t\168\226\152("; + oasis_digest = Some "8\252\157\1340^<0\133GR\029nmc6"; oasis_exec = None; oasis_setup_args = []; setup_update = false