diff options
author | Stephane Glondu <steph@glondu.net> | 2015-10-28 15:11:39 +0100 |
---|---|---|
committer | Stephane Glondu <steph@glondu.net> | 2015-10-28 15:11:39 +0100 |
commit | 96085d3868a7416d93475f218ce763ab24fa40f4 (patch) | |
tree | a67ed1fcbda2aeac5afdc7706126e18698f05986 | |
parent | acd60db886f534655215369bf0f3b13bf56c7ee6 (diff) |
Imported Upstream version 1.0.1
-rw-r--r-- | .ocamlinit | 2 | ||||
-rw-r--r-- | CHANGES.md | 6 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | TODO.md | 28 | ||||
-rw-r--r-- | _tags | 2 | ||||
-rw-r--r-- | doc/React.S.html | 2 | ||||
-rw-r--r-- | doc/React.html | 2 | ||||
-rw-r--r-- | doc/index_values.html | 2 | ||||
-rw-r--r-- | opam | 9 | ||||
-rw-r--r-- | pkg/META | 2 | ||||
-rwxr-xr-x | pkg/build | 28 | ||||
-rwxr-xr-x | pkg/build.ml | 15 | ||||
-rw-r--r-- | pkg/config | 3 | ||||
-rw-r--r-- | pkg/config.ml | 11 | ||||
-rwxr-xr-x | pkg/git.ml | 13 | ||||
-rwxr-xr-x | pkg/pkg-builder | 108 | ||||
-rwxr-xr-x | pkg/pkg-git | 16 | ||||
-rwxr-xr-x | pkg/pkg-varsubsts | 24 | ||||
-rw-r--r-- | pkg/topkg-ext.ml | 272 | ||||
-rw-r--r-- | pkg/topkg.ml | 303 | ||||
-rw-r--r-- | src/react.ml | 4 | ||||
-rw-r--r-- | src/react.mli | 6 | ||||
-rw-r--r-- | test/test.ml | 17 | ||||
-rw-r--r-- | test/tests.itarget | 2 |
24 files changed, 683 insertions, 196 deletions
@@ -1,2 +1,2 @@ #directory "_build/src" -#load "react.cmo" +#load "react.cmo"
\ No newline at end of file @@ -1,3 +1,9 @@ +v1.0.1 2014-04-21 La Forclaz (VS) +--------------------------------- + +- Fix `S.bind`. +- Use package builder topkg for distribution. + v1.0.0 2014-04-02 La Forclaz (VS) --------------------------------- @@ -1,6 +1,6 @@ React — Declarative events and signals for OCaml ------------------------------------------------------------------------------- -Release 1.0.0 +Release 1.0.1 React is an OCaml module for functional reactive programming (FRP). It provides support to program with time varying values : declarative @@ -0,0 +1,28 @@ + +# New event combinators + +```ocaml +E.Option.some : 'a option event -> 'a event +(** [some e] is [E.fmap (fun v -> v) e] *) + +S.Option.some : 'a -> 'a option signal -> 'a signal +``` + + + +# New signal combinators. + +To avoid uses of S.value we need better ways to access a +signal's current value and inject it in an efficient +way in the graph. + +```ocaml +S.freeze : 'a signal -> 'a signal +(** [freeze s]_{t} = [s]_{t'} where t' is freeze's creation time. *) +``` + +See if we can return a const and if what happens when used with +bind and/or provide an alternative S.bind for bootstraping. + + + @@ -5,4 +5,4 @@ <test/breakout.{native,byte}> : use_unix <test/js_test.{ml,native,byte}> : package(js_of_ocaml), \ package(js_of_ocaml.syntax), \ - syntax(camlp4o) + syntax(camlp4o)
\ No newline at end of file diff --git a/doc/React.S.html b/doc/React.S.html index 3a10534..cc829e7 100644 --- a/doc/React.S.html +++ b/doc/React.S.html @@ -244,7 +244,7 @@ The type for signals of type <code class="code"><span class="keywordsign">'</spa </div> <pre><span id="VALbind"><span class="keyword">val</span> bind</span> : <code class="type">?eq:('b -> 'b -> bool) -><br> 'a <a href="React.html#TYPEsignal">React.signal</a> -> ('a -> 'b <a href="React.html#TYPEsignal">React.signal</a>) -> 'b <a href="React.html#TYPEsignal">React.signal</a></code></pre><div class="info "> -<code class="code">bind s sf</code> is <code class="code">switch (map sf s)</code>.<br> +<code class="code">bind s sf</code> is <code class="code">switch (map ~eq:( == ) sf s)</code>.<br> </div> <pre><span id="VALfix"><span class="keyword">val</span> fix</span> : <code class="type">?eq:('a -> 'a -> bool) -><br> 'a -> ('a <a href="React.html#TYPEsignal">React.signal</a> -> 'a <a href="React.html#TYPEsignal">React.signal</a> * 'b) -> 'b</code></pre><div class="info "> diff --git a/doc/React.html b/doc/React.html index 9380b29..48784d0 100644 --- a/doc/React.html +++ b/doc/React.html @@ -47,7 +47,7 @@ Declarative events and signals. types and modules in your scope. <p> - <em>Release 1.0.0 - Daniel Bünzli <daniel.buenzl i@erratique.ch> </em><br> + <em>Release 1.0.1 - Daniel Bünzli <daniel.buenzl i@erratique.ch> </em><br> </div> <hr width="100%"> <br> diff --git a/doc/index_values.html b/doc/index_values.html index a7addd0..f5e3791 100644 --- a/doc/index_values.html +++ b/doc/index_values.html @@ -115,7 +115,7 @@ <td></td></tr> <tr><td><a href="React.S.html#VALbind">bind</a> [<a href="React.S.html">React.S</a>]</td> <td><div class="info"> -<code class="code">bind s sf</code> is <code class="code">switch (map sf s)</code>. +<code class="code">bind s sf</code> is <code class="code">switch (map ~eq:( == ) sf s)</code>. </div> </td></tr> <tr><td align="left"><br>C</td></tr> @@ -1,7 +1,7 @@ opam-version: "1" maintainer: "Daniel Bünzli <daniel.buenzl i@erratique.ch>" homepage: "http://erratique.ch/software/react" -authors: ["Daniel Bünzli <daniel.buenzli i@erratique.ch>"] +authors: ["Daniel Bünzli <daniel.buenzl i@erratique.ch>"] doc: "http://erratique.ch/software/react/doc/React" tags: [ "reactive" "declarative" "signal" "event" "frp" ] license: "BSD3" @@ -9,6 +9,7 @@ depends: ["ocamlfind"] ocaml-version: [>= "3.11.0"] build: [ - ["./pkg/pkg-git" ] - ["./pkg/build" "true" ] -] + [ "ocaml" "pkg/git.ml" ] + [ "ocaml" "pkg/build.ml" "native=%{ocaml-native}%" + "native-dynlink=%{ocaml-native}%" ] # TODO FIXME +]
\ No newline at end of file @@ -1,4 +1,4 @@ -version = "1.0.0" +version = "1.0.1" description = "Declarative events and signals for OCaml" archive(byte) = "react.cma" archive(byte, plugin) = "react.cma" diff --git a/pkg/build b/pkg/build deleted file mode 100755 index 5c32123..0000000 --- a/pkg/build +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -# Usage: build has_native - -set -e -LOC=`dirname $0` - -. $LOC/pkg-builder - -NATIVE=$1 - -add lib pkg/META -add lib src/react.mli -add lib src/react.cmti -add lib src/react.cmi -add lib src/react.cmx -add lib src/react.cma -add lib src/react.a -add lib src/react.cmxa -add lib src/react.cmxs - -add doc README.md -add doc CHANGES.md -add doc test/breakout.ml -add doc test/clock.ml - -build -install react diff --git a/pkg/build.ml b/pkg/build.ml new file mode 100755 index 0000000..1ab82dd --- /dev/null +++ b/pkg/build.ml @@ -0,0 +1,15 @@ +#!/usr/bin/env ocaml +#directory "pkg";; +#use "topkg.ml";; + +let () = + Pkg.describe "react" ~builder:`OCamlbuild [ + Pkg.lib "pkg/META"; + Pkg.lib ~exts:Exts.module_library "src/react"; + Pkg.doc "README.md"; + Pkg.doc "CHANGES.md"; + Pkg.doc "test/breakout.ml"; + Pkg.doc "test/clock.ml"; ] + + + diff --git a/pkg/config b/pkg/config deleted file mode 100644 index a19c529..0000000 --- a/pkg/config +++ /dev/null @@ -1,3 +0,0 @@ -NAME=react -VERSION=`git describe master | sed "s/^.//"` -MAINTAINER="Daniel Bünzli <daniel.buenzl i\\\@erratique.ch>" diff --git a/pkg/config.ml b/pkg/config.ml new file mode 100644 index 0000000..19ecc23 --- /dev/null +++ b/pkg/config.ml @@ -0,0 +1,11 @@ +#!/usr/bin/env ocaml +#directory "pkg" +#use "topkg-ext.ml" + +module Config = struct + include Config_default + let vars = + [ "NAME", "react"; + "VERSION", Git.describe ~chop_v:true "master"; + "MAINTAINER", "Daniel Bünzli <daniel.buenzl i\\@erratique.ch>" ] +end diff --git a/pkg/git.ml b/pkg/git.ml new file mode 100755 index 0000000..4169980 --- /dev/null +++ b/pkg/git.ml @@ -0,0 +1,13 @@ +#!/usr/bin/env ocaml +#directory "pkg" +#use "config.ml" + +(* This is only for git checkout builds, it can be ignored + for distribution builds. *) + +let () = + if Dir.exists ".git" then begin + Vars.subst ~skip:Config.subst_skip ~vars:Config.vars ~dir:"." &>>= fun () -> + Cmd.exec_hook Config.git_hook &>>= fun () -> () + end + diff --git a/pkg/pkg-builder b/pkg/pkg-builder deleted file mode 100755 index 872644e..0000000 --- a/pkg/pkg-builder +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/sh - -# Should be included by a script. The includer should set the variable -# $NATIVE to "false" if it doesn't want native code compilation. - -OCAMLBUILD=${OCAMLBUILD:="ocamlbuild -use-ocamlfind -classic-display"} -B="_build" -NL=" -" - -LIBS=""; ILIBS="" -BINS=""; IBINS="" -SBIS=""; ISBIS="" -TOPS=""; ITOPS="" -SHRS=""; ISHRS="" -ETCS=""; IETCS="" -DOCS=""; IDOCS="" -MISC=""; IMISC="" -STBS=""; ISTBS="" -MANS=""; IMANS="" - -add_lib () { LIBS="$LIBS $1"; ILIBS="$ILIBS$NL \"$B/$1\" {\"$2\"}"; } -add_lib_nobuild () { ILIBS="$ILIBS$NL \"?$B/$1\" {\"$2\"}"; } -add_bin () { BINS="$BINS $1"; IBINS="$IBINS$NL \"$B/$1\" {\"$2\"}"; } -add_sbin () { SBIS="$SBIS $1"; ISBIS="$ISBIS$NL \"$B/$1\" {\"$2\"}"; } -add_toplevel () { TOPS="$TOPS $1"; ITOPS="$ITOPS$NL \"$B/$1\" {\"$2\"}"; } -add_share () { SHRS="$SHRS $1"; ISHRS="$ISHRS$NL \"$B/$1\" {\"$2\"}"; } -add_etc () { ETCS="$ETCS $1"; IETCS="$IETCS$NL \"$B/$1\" {\"$2\"}"; } -add_doc () { DOCS="$DOCS $1"; IDOCS="$IDOCS$NL \"$B/$1\" {\"$2\"}"; } -add_misc () { MISC="$MISC $1"; IMISC="$IMISC$NL \"$B/$1\" {\"$2\"}"; } -add_stublibs () { STBS="$STBS $1"; ISTBS="$ISTBS$NL \"$B/$1\" {\"$2\"}"; } -add_man () { MANS="$MANS $1"; IMANS="$IMANS$NL \"$B/$1\" {\"$2\"}"; } -add () -{ - SRC=$2 - DST=$3 - if [ "$DST" = "" ]; then - DST=`basename $SRC` - fi - case $1 in - lib) - if [ "$NATIVE" != "false" ]; then - case $2 in - *.cmti | *.cmt) add_lib_nobuild $SRC $DST ;; - *) add_lib $SRC $DST ;; - esac - else - case $2 in - *.a | *.cmx | *.cmxa | *.cmxs) ;; - *.cmti | *.cmt) add_lib_nobuild $SRC $DST ;; - *) add_lib $SRC $DST ;; - esac - fi - ;; - bin) - if [ "$NATIVE" != "false" ]; then - add_bin $SRC.native $DST - else - add_bin $SRC.byte $DST - fi - ;; - sbin) - if [ "$NATIVE" != "false" ]; then - add_sbin $SRC.native $DST - else - add_sbin $SRC.byte $DST - fi - ;; - share) add_share $SRC $DST ;; - etc) add_etc $SRC $DST ;; - toplevel) add_top $SRC.top $DST ;; - doc) add_doc $SRC $DST ;; - misc) add_misc $SRC $DST ;; - stublibs) add_stublibs $SRC $DST ;; - man) add_man $SRC $DST ;; - esac -} - -build () -{ - $OCAMLBUILD $LIBS $BINS $SBIS $DOCS $TOPS $SHRS $ETCS $MISC $STBS $MANS; -} - -install () -{ - cat > $1.install <<EOF -lib: [$ILIBS -] -bin: [$IBINS -] -sbin: [$ISBIS -] -toplevel: [$ITOPS -] -share: [$ISHRS -] -etc: [$IETCS -] -doc: [$IDOCS -] -misc: [$IMISC -] -stublibs: [$ISTBS -] -man: [$IMANS -] -EOF -} diff --git a/pkg/pkg-git b/pkg/pkg-git deleted file mode 100755 index 44e0e2b..0000000 --- a/pkg/pkg-git +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -## Usage: pkg-git -## Prepare package for an opam git/pinned package. - -set -e -LOC=`dirname $0` - -if [ -d ".git" ]; then - . $LOC/config - . $LOC/pkg-varsubsts - - if [ -f $LOC/hook-pkg-git ]; then - . $LOC/hook-pkg-git - fi -fi diff --git a/pkg/pkg-varsubsts b/pkg/pkg-varsubsts deleted file mode 100755 index 40c2d54..0000000 --- a/pkg/pkg-varsubsts +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -# To add other variable substitutions already predefine $VARSUBST in -# config. - -set -e -LOC=`dirname $0` - -SED=${SED:="sed"} - -VARSUBSTS="$VARSUBST s|%%NAME%%|$NAME|g; \ - s|%%VERSION%%|$VERSION|g; \ - s|%%MAINTAINER%%|$MAINTAINER|g;" - -for file in `git ls-files`; do - if [ ! -x "$file" ]; then # skip scripts - $SED "$VARSUBSTS" $file > $file.tmp - mv -f $file.tmp $file - fi -done - -if [ -f $LOC/hook-pkg-varsubsts ]; then - . $LOC/hook-pkg-varsubsts -fi diff --git a/pkg/topkg-ext.ml b/pkg/topkg-ext.ml new file mode 100644 index 0000000..4aa5e0b --- /dev/null +++ b/pkg/topkg-ext.ml @@ -0,0 +1,272 @@ +(*--------------------------------------------------------------------------- + Copyright (c) 2014 Daniel C. Bünzli. All rights reserved. + Distributed under the BSD3 license, see license at the end of the file. + react release 1.0.1 + ---------------------------------------------------------------------------*) + +let ( >>= ) v f = match v with `Ok v -> f v | `Error _ as e -> e +let ( &>>= ) v f = match v with +| `Ok v -> f v | `Error e -> Printf.eprintf "%s: %s\n%!" Sys.argv.(0) e; exit 1 + +type 'a result = [ `Ok of 'a | `Error of string ] + +(** Working with files *) +module File : sig + val exists : string -> bool + (** [exists file] is [true] if [file] exists. *) + + val read : string -> string result + (** [read file] is [file]'s contents. *) + + val write : string -> string -> unit result + (** [write file content] writes [contents] to [file]. *) + + val write_subst : string -> (string * string) list -> string -> unit result + (** [write_subst file vars content] writes [contents] to [file] + substituting variables of the form [%%ID%%] by their definition. + The [ID]'s are [List.map fst vars] and their definition content + is found with [List.assoc]. *) + + val delete : ?maybe:bool -> string -> unit result + (** [delete maybe file] deletes file [file]. If [maybe] is [true] (defaults + to false) no error is reported if the file doesn't exist. *) + + val temp : unit -> string result + (** [temp ()] creates a temporary file and returns its name. The file + is destroyed at the end of program execution. *) +end = struct + let exists = Sys.file_exists + let read file = try + let ic = open_in file in + let len = in_channel_length ic in + let s = String.create len in + really_input ic s 0 len; close_in ic; `Ok s + with Sys_error e -> `Error e + + let write f s = try + let oc = open_out f in + output_string oc s; close_out oc; `Ok () + with Sys_error e -> `Error e + + let write_subst f vars s = try + let oc = open_out f in + let start = ref 0 in + let last = ref 0 in + let len = String.length s in + while (!last < len - 2) do + if not (s.[!last] = '%' && s.[!last + 1] = '%') then incr last else + begin + let start_subst = !last in + let last_id = ref (!last + 2) in + let stop = ref false in + while (!last_id < len - 1 && not !stop) do + if not (s.[!last_id] = '%' && s.[!last_id + 1] = '%') then begin + if s.[!last_id] <> ' ' then (incr last_id) else + (stop := true; last := !last_id) + end else begin + let id_start = start_subst + 2 in + let id = String.sub s (id_start) (!last_id - id_start) in + try + let subst = List.assoc id vars in + output oc s !start (start_subst - !start); + output_string oc subst; + stop := true; + start := !last_id + 2; + last := !last_id + 2; + with Not_found -> + stop := true; + last := !last_id + end + done + end + done; + output oc s !start (len - !start); close_out oc; `Ok () + with Sys_error e -> `Error e + + let delete ?(maybe = false) file = try + if maybe && not (exists file) then `Ok () else + `Ok (Sys.remove file) + with Sys_error e -> `Error e + + let temp () = try + let f = Filename.temp_file (Filename.basename Sys.argv.(0)) "topkg" in + at_exit (fun () -> ignore (delete f)); `Ok f + with Sys_error e -> `Error e +end + +(** Working with directories. *) +module Dir : sig + val exists : string -> bool + (** [exists dir] is [true] if directory [dir] exists. *) + + val change_cwd : string -> unit result + (** [change_cwd dir] changes the current working directory to [dir]. *) + + val fold_files_rec : ?skip:string list -> (string -> 'a -> 'a result) -> + 'a -> string list -> 'a result + (** [fold_files_rec skip f acc paths] folds [f] over the files + found in [paths]. Files and directories whose suffix matches an + element of [skip] are skipped. *) +end = struct + let exists dir = Sys.file_exists dir && Sys.is_directory dir + let change_cwd dir = try `Ok (Sys.chdir dir) with Sys_error e -> `Error e + let fold_files_rec ?(skip = []) f acc paths = + let is_dir d = try Sys.is_directory d with Sys_error _ -> false in + let readdir d = try Array.to_list (Sys.readdir d) with Sys_error _ -> [] in + let keep p = not (List.exists (fun s -> Filename.check_suffix p s) skip) in + let process acc file = match acc with + | `Error _ as e -> e + | `Ok acc -> f file acc + in + let rec aux f acc = function + | (d :: ds) :: up -> + let paths = List.rev_map (Filename.concat d) (readdir d) in + let paths = List.find_all keep paths in + let dirs, files = List.partition is_dir paths in + begin match List.fold_left process acc files with + | `Error _ as e -> e + | `Ok _ as acc -> aux f acc (dirs :: ds :: up) + end + | [] :: [] -> acc + | [] :: up -> aux f acc up + | _ -> assert false + in + let paths = List.find_all keep paths in + let dirs, files = List.partition is_dir paths in + let acc = List.fold_left process (`Ok acc) files in + aux f acc (dirs :: []) +end + +(** Command invocation. *) +module Cmd : sig + val exec : string -> unit result + (** [exec cmd] executes [cmd]. *) + + val exec_hook : string option -> unit result + (** [exec_hook args] is [exec ("ocaml " ^ "args")] if [args] is some. *) + + val read : string -> string result + (** [read cmd] executes [cmd] and returns the contents of its stdout. *) +end = struct + let exec cmd = + let code = Sys.command cmd in + if code = 0 then `Ok () else + `Error (Printf.sprintf "invocation `%s' exited with %d" cmd code) + + let exec_hook args = match args with + | None -> `Ok () + | Some args -> exec (Printf.sprintf "ocaml %s" args) + + let read cmd = + File.temp () >>= fun file -> + exec (Printf.sprintf "%s > %s" cmd file) >>= fun () -> + File.read file >>= fun v -> + `Ok v +end + +(** Variable substitution. *) +module Vars : sig + val subst : skip:string list -> vars:(string * string) list -> + dir:string -> unit result + (** [subst skip vars dir] substitutes [vars] in all files + in [dir] except those that are [skip]ped (see {!Dir.fold_files_rec}). *) + + val get : string -> (string * string) list -> string result + (** [get v] lookup variable [v] in [vars]. Returns an error if [v] is + absent or if it is the empty string. *) + +end = struct + let subst ~skip ~vars ~dir = + let subst f () = + File.read f >>= fun contents -> + File.write_subst f vars contents >>= fun () -> `Ok () + in + Dir.fold_files_rec ~skip subst () [dir] + + let get v vars = + let v = try List.assoc v vars with Not_found -> "" in + if v <> "" then `Ok v else + `Error (Printf.sprintf "empty or undefined variable %s in Config.vars" v) +end + +(** Git invocations. *) +module Git : sig + val describe : ?chop_v:bool -> string -> string + (** [describe chop_v branch] invokes [git describe branch]. If [chop_v] + is [true] (defaults to [false]) an initial ['v'] in the result + is chopped. *) +end = struct + let describe ?(chop_v = false) branch = + if not (Dir.exists ".git") then "not-a-git-checkout" else + Cmd.read (Printf.sprintf "git describe %s" branch) &>>= fun d -> + let len = String.length d in + if chop_v && len > 0 && d.[0] = 'v' then String.sub d 1 (len - 2) else + String.sub d 0 (len - 1) (* remove \n *) +end + +(** Default configuration. *) +module Config_default : sig + val subst_skip : string list + (** [subst_skip] is a list of suffixes that are automatically + skipped during variable substitution. *) + + val vars : (string * string) list + (** [vars] is the list of variables to substitute, empty. *) + + val git_hook : string option + (** [git_start_hook] is an ocaml script to invoke before a git package + build, after variable substitution occured. *) + + val distrib_remove : string list + (** [distrib_remove] is a list of files to remove before making + the distributino tarball. *) + + val distrib_hook : string option + (** [distrib_hook] is an ocaml script to invoke before trying + to build the distribution. *) + + val www_demos : string list + (** [www_demos] is a list of build targets that represent single page + js_of_ocaml demo. *) +end = struct + let subst_skip = [".git"; ".png"; ".jpeg"; ".otf"; ".ttf"; ".pdf" ] + let vars = [] + let git_hook = None + let distrib_remove = [".git"; ".gitignore"; "build"] + let distrib_hook = None + let www_demos = [] +end + + +(*--------------------------------------------------------------------------- + Copyright (c) 2014 Daniel C. Bünzli. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. Neither the name of Daniel C. Bünzli nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 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. + ---------------------------------------------------------------------------*) diff --git a/pkg/topkg.ml b/pkg/topkg.ml new file mode 100644 index 0000000..e539992 --- /dev/null +++ b/pkg/topkg.ml @@ -0,0 +1,303 @@ +(*--------------------------------------------------------------------------- + Copyright (c) 2014 Daniel C. Bünzli. All rights reserved. + Distributed under the BSD3 license, see license at the end of the file. + react release 1.0.1 + ---------------------------------------------------------------------------*) + +(* Public api *) + +(** Build environment access *) +module type Env = sig + val bool : string -> bool + (** [bool key] declares [key] as being a boolean key in the environment. + Specifing key=(true|false) on the command line becomes mandatory. *) + + val native : bool + (** [native] is [bool "native"]. *) + + val native_dynlink : bool + (** [native_dylink] is [bool "native-dynlink"] *) +end + +(** Exts defines sets of file extensions. *) +module type Exts = sig + val interface : string list + (** [interface] is [[".mli"; ".cmi"; ".cmti"]] *) + + val interface_opt : string list + (** [interface_opt] is [".cmx" :: interface] *) + + val library : string list + (** [library] is [[".cma"; ".cmxa"; ".cmxs"; ".a"]] *) + + val module_library : string list + (** [module_library] is [(interface_opt @ library)]. *) +end + +(** Package description. *) +module type Pkg = sig + type builder = [ `OCamlbuild | `Other of string * string ] + (** The type for build tools. Either [`OCamlbuild] or an + [`Other (tool, bdir)] tool [tool] that generates its build artefacts + in [bdir]. *) + + type moves + (** The type for install moves. *) + + type field = ?cond:bool -> ?exts:string list -> ?dst:string -> string -> moves + (** The type for field install functions. A call + [field cond exts dst path] generates install moves as follows: + {ul + {- If [cond] is [false] (defaults to [true]), no move is generated.} + {- If [exts] is present, generates a move for each path in + the list [List.map (fun e -> path ^ e) exts].} + {- If [dst] is present this path is used as the move destination + (allows to install in subdirectories). If absent [dst] is + [Filename.basename path].} *) + + val lib : field + val bin : ?auto:bool -> field + (** If [auto] is true (defaults to false) generates + [path ^ ".native"] if {!Env.native} is [true] and + [path ^ ".byte"] if {!Env.native} is [false]. *) + val sbin : ?auto:bool -> field (** See {!bin}. *) + val toplevel : field + val share : field + val share_root : field + val etc : field + val doc : field + val misc : field + val stublibs : field + val man : field + val describe : string -> builder:builder -> moves list -> unit + (** [describe name builder moves] describes a package named [name] with + builder [builder] and install moves [moves]. *) +end + +(* Implementation *) + +module Topkg : sig + val cmd : [`Build | `Explain | `Help ] + val env : (string * bool) list + val err_parse : string -> 'a + val err_mdef : string -> 'a + val err_miss : string -> 'a + val err_file : string -> string -> 'a + val warn_unused : string -> unit +end = struct + + (* Parses the command line. The actual cmd execution occurs in the call + to Pkg.describe. *) + + let err fmt = + let k _ = exit 1 in + Format.kfprintf k Format.err_formatter ("%s: " ^^ fmt ^^ "@.") Sys.argv.(0) + + let err_parse a = err "argument `%s' is not of the form key=(true|false)" a + let err_mdef a = err "bool `%s' is defined more than once" a + let err_miss a = err "argument `%s=(true|false)' is missing" a + let err_file f e = err "%s: %s" f e + let warn_unused k = + Format.eprintf "%s: warning: environment key `%s` unused.@." Sys.argv.(0) k + + let cmd, env = + let rec parse_env acc = function (* not t.r. *) + | arg :: args -> + begin try + (* String.cut ... *) + let len = String.length arg in + let eq = String.index arg '=' in + let bool = bool_of_string (String.sub arg (eq + 1) (len - eq - 1)) in + let key = String.sub arg 0 eq in + if key = "" then raise Exit else + try ignore (List.assoc key acc); err_mdef key with + | Not_found -> parse_env ((key, bool) :: acc) args + with + | Invalid_argument _ | Not_found | Exit -> err_parse arg + end + | [] -> acc + in + match List.tl (Array.to_list Sys.argv) with + | "explain" :: args -> `Explain, parse_env [] args + | ("help" | "-h" | "--help" | "-help") :: args -> `Help, parse_env [] args + | args -> `Build, parse_env [] args +end + +module Env : sig + include Env + val get : unit -> (string * bool) list +end = struct + let env = ref [] + let get () = !env + let add_bool key b = env := (key, b) :: !env + let bool key = + let b = try List.assoc key Topkg.env with + | Not_found -> if Topkg.cmd = `Build then Topkg.err_miss key else true + in + add_bool key b; b + + let native = bool "native" + let native_dynlink = bool "native-dynlink" +end + +module Exts : Exts = struct + let interface = [".mli"; ".cmi"; ".cmti"] + let interface_opt = ".cmx" :: interface + let library = [".cma"; ".cmxa"; ".cmxs"; ".a"] + let module_library = (interface_opt @ library) +end + +module Pkg : Pkg = struct + type builder = [ `OCamlbuild | `Other of string * string ] + type moves = (string * (string * string)) list + type field = ?cond:bool -> ?exts:string list -> ?dst:string -> string -> moves + + let str = Printf.sprintf + let warn_unused () = + let keys = List.map fst Topkg.env in + let keys_used = List.map fst (Env.get ()) in + let unused = List.find_all (fun k -> not (List.mem k keys_used)) keys in + List.iter Topkg.warn_unused unused + + let has_suffix = Filename.check_suffix + let build_strings ?(exec_sep = " ") btool bdir mvs = + let no_build = [ ".cmti"; ".cmt" ] in + let install = Buffer.create 1871 in + let exec = Buffer.create 1871 in + let rec add_mvs current = function + | (field, (src, dst)) :: mvs when field = current -> + if List.exists (has_suffix src) no_build then + Buffer.add_string install (str "\n \"?%s/%s\" {\"%s\"}" bdir src dst) + else begin + Buffer.add_string exec (str "%s%s" exec_sep src); + Buffer.add_string install (str "\n \"%s/%s\" {\"%s\"}" bdir src dst); + end; + add_mvs current mvs + | (((field, _) :: _) as mvs) -> + if current <> "" (* first *) then Buffer.add_string install " ]\n"; + Buffer.add_string install (str "%s: [" field); + add_mvs field mvs + | [] -> () + in + Buffer.add_string exec btool; + add_mvs "" mvs; + Buffer.add_string install " ]\n"; + Buffer.contents install, Buffer.contents exec + + let pr = Format.printf + let pr_explanation btool bdir pkg mvs = + let env = Env.get () in + let install, exec = build_strings ~exec_sep:" \\\n " btool bdir mvs in + pr "@[<v>"; + pr "Package name: %s@," pkg; + pr "Build tool: %s@," btool; + pr "Build directory: %s@," bdir; + pr "Environment:@, "; + List.iter (fun (k,v) -> pr "%s=%b@, " k v) (List.sort compare env); + pr "@,Build invocation:@,"; + pr " %s@,@," exec; + pr "Install file:@,"; + pr "%s@," install; + pr "@]"; + () + + let pr_help () = + pr "Usage example:@\n %s" Sys.argv.(0); + List.iter (fun (k,v) -> pr " %s=%b" k v) (List.sort compare (Env.get ())); + pr "@." + + let build btool bdir pkg mvs = + let install, exec = build_strings btool bdir mvs in + let e = Sys.command exec in + if e <> 0 then exit e else + let install_file = pkg ^ ".install" in + try + let oc = open_out install_file in + output_string oc install; flush oc; close_out oc + with Sys_error e -> Topkg.err_file install_file e + + let mvs ?(drop_exts = []) field ?(cond = true) ?(exts = []) ?dst src = + if not cond then [] else + let mv src dst = (field, (src, dst)) in + let expand exts s d = List.map (fun e -> mv (s ^ e) (d ^ e)) exts in + let dst = match dst with None -> Filename.basename src | Some dst -> dst in + let files = if exts = [] then [mv src dst] else expand exts src dst in + let keep (_, (src, _)) = not (List.exists (has_suffix src) drop_exts) in + List.find_all keep files + + let lib = + let drop_exts = + if Env.native && not Env.native_dynlink then [ ".cmxs" ] else + if not Env.native then [ ".a"; ".cmx"; ".cmxa"; ".cmxs" ] else [] + in + mvs ~drop_exts "lib" + + let share = mvs "share" + let share_root = mvs "share_root" + let etc = mvs "etc" + let toplevel = mvs "toplevel" + let doc = mvs "doc" + let misc = mvs "misc" + let stublibs = mvs "stublib" + let man = mvs "man" + + let bin_drops = if not Env.native then [ ".native" ] else [] + let bin_mvs field ?(auto = false) ?cond ?exts ?dst src = + let src, dst = + if not auto then src, dst else + let dst = match dst with + | None -> Some (Filename.basename src) + | Some _ as dst -> dst + in + let src = if Env.native then src ^ ".native" else src ^ ".byte" in + src, dst + in + mvs ~drop_exts:bin_drops field ?cond ?dst src + + let bin = bin_mvs "bin" + let sbin = bin_mvs "sbin" + + let describe pkg ~builder mvs = + let mvs = List.sort compare (List.flatten mvs) in + let btool, bdir = match builder with + | `OCamlbuild -> "ocamlbuild -use-ocamlfind -classic-display", "_build" + | `Other (btool, bdir) -> btool, bdir + in + match Topkg.cmd with + | `Explain -> pr_explanation btool bdir pkg mvs + | `Help -> pr_help () + | `Build -> warn_unused (); build btool bdir pkg mvs +end + +(*--------------------------------------------------------------------------- + Copyright (c) 2014 Daniel C. Bünzli. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. Neither the name of Daniel C. Bünzli nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 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. + ---------------------------------------------------------------------------*) diff --git a/src/react.ml b/src/react.ml index aab46d0..f69a682 100644 --- a/src/react.ml +++ b/src/react.ml @@ -1,7 +1,7 @@ (*--------------------------------------------------------------------------- Copyright (c) 2009 Daniel C. Bünzli. All rights reserved. Distributed under a BSD3 license, see license at the end of the file. - react release 1.0.0 + react release 1.0.1 ---------------------------------------------------------------------------*) let err_max_rank = "maximal rank exceeded" @@ -1107,7 +1107,7 @@ module S = struct | Const i -> signal ~i m' p u | Smut m -> Node.add_dep m.snode m'.snode; signal m' p u - let bind ?eq s sf = switch ?eq (map sf s) + let bind ?eq s sf = switch ?eq (map ~eq:( == ) sf s) let fix ?(eq = ( = )) i f = let update_delayed n p u nl = diff --git a/src/react.mli b/src/react.mli index 50837ce..ff6045a 100644 --- a/src/react.mli +++ b/src/react.mli @@ -1,7 +1,7 @@ (*--------------------------------------------------------------------------- Copyright (c) 2009 Daniel C. Bünzli. All rights reserved. Distributed under a BSD3 license, see license at the end of the file. - react release 1.0.0 + react release 1.0.1 ---------------------------------------------------------------------------*) (** Declarative events and signals. @@ -16,7 +16,7 @@ {{!ex}examples}. Open the module to use it, this defines only two types and modules in your scope. - {e Release 1.0.0 - Daniel Bünzli <daniel.buenzl i\@erratique.ch> } *) + {e Release 1.0.1 - Daniel Bünzli <daniel.buenzl i\@erratique.ch> } *) (** {1 Interface} *) @@ -419,7 +419,7 @@ module S : sig val bind : ?eq:('b -> 'b -> bool) -> 'a signal -> ('a -> 'b signal) -> 'b signal - (** [bind s sf] is [switch (map sf s)]. *) + (** [bind s sf] is [switch (map ~eq:( == ) sf s)]. *) val fix : ?eq:('a -> 'a -> bool) -> 'a -> ('a signal -> 'a signal * 'b) -> 'b (** [fix i sf] allow to refer to the value a signal had an diff --git a/test/test.ml b/test/test.ml index 7e6a37e..8643213 100644 --- a/test/test.ml +++ b/test/test.ml @@ -933,6 +933,22 @@ let test_esswitch4 () = (* test_esswitch3 + high rank. *) List.iter set_y [1; 1; 2; 2]; List.iter set_x [7; 7; 8; 8; 9; 9]; List.iter empty [assert_s; !assert_d1; !assert_d2; !assert_d3] +let test_bind () = + let e, set_e = E.create () in + let a = S.hold 0 e in + let b = S.hold 1 e in + let s, set_s = S.create true in + let next = function + | true -> b + | false -> a + in + let f = S.bind s next in + let assert_bind = vals f [1; 0; 3;] in + set_s false; + set_e 3; + set_s true; + List.iter empty [assert_bind] + let test_fix () = let s, set_s = S.create 0 in let history s = @@ -1085,6 +1101,7 @@ let test_signals () = test_esswitch3 (); test_switch4 (); test_esswitch4 (); + test_bind (); test_fix (); test_fix' (); test_lifters (); diff --git a/test/tests.itarget b/test/tests.itarget index 1771929..443ae9c 100644 --- a/test/tests.itarget +++ b/test/tests.itarget @@ -1,3 +1,3 @@ test.native clock.native -breakout.native +breakout.native
\ No newline at end of file |