pax_global_header00006660000000000000000000000064127620066070014520gustar00rootroot0000000000000052 comment=1793f70e7239c7abe0e26ae3e2bb93bab09f88e4 ocaml-process-0.2.1/000077500000000000000000000000001276200660700142675ustar00rootroot00000000000000ocaml-process-0.2.1/.gitignore000066400000000000000000000000321276200660700162520ustar00rootroot00000000000000*~ _build _tests *.native ocaml-process-0.2.1/.merlin000066400000000000000000000000401276200660700155500ustar00rootroot00000000000000PKG alcotest S lib B _build/** ocaml-process-0.2.1/.travis.yml000066400000000000000000000005211276200660700163760ustar00rootroot00000000000000language: c sudo: required install: wget https://raw.githubusercontent.com/ocaml/ocaml-ci-scripts/master/.travis-opam.sh script: bash -ex .travis-opam.sh env: - OCAML_VERSION=4.01 PACKAGE=process - OCAML_VERSION=4.02 PACKAGE=process - OCAML_VERSION=4.03 PACKAGE=process - OCAML_VERSION=4.04 PACKAGE=process os: - linux - osx ocaml-process-0.2.1/CHANGES000066400000000000000000000004211276200660700152570ustar00rootroot000000000000000.2.1 (2016-09-01): * Added Windows support (#5 from msprotz) * Now installs .cmx files (#4 from aantron) 0.2.0 (2016-01-17): * Fixed assumptions regarding subprocess I/O (#2 from aantron) * Added Process.Exit.error_to_string 0.1.0 (2015-12-25): * Initial public release ocaml-process-0.2.1/META000066400000000000000000000003611276200660700147400ustar00rootroot00000000000000version = "0.2.1" description = "Easy process control" requires = "unix" archive(byte) = "process.cma" archive(byte, plugin) = "process.cma" archive(native) = "process.cmxa" archive(native, plugin) = "process.cmxs" exists_if = "process.cma" ocaml-process-0.2.1/Makefile000066400000000000000000000017021276200660700157270ustar00rootroot00000000000000.PHONY: build test install uninstall reinstall clean FINDLIB_NAME=process MOD_NAME=process OCAMLBUILD=ocamlbuild -use-ocamlfind -classic-display TARGETS=.cma .cmxa .cmx PRODUCTS=$(addprefix $(MOD_NAME),$(TARGETS)) TYPES=.mli .cmi .cmti INSTALL:=$(addprefix $(MOD_NAME), $(TYPES)) \ $(addprefix $(MOD_NAME), $(TARGETS)) INSTALL:=$(addprefix _build/lib/,$(INSTALL)) ARCHIVES:=_build/lib/$(MOD_NAME).a build: $(OCAMLBUILD) $(PRODUCTS) test_%.native: lib_test/%.ml $(OCAMLBUILD) lib_test/$*.native mv $*.native test_$*.native TEST_HELPERS=\ megabyte_writer kilobyte_writer\ empty_out nl_out trail_nl_out start_nl_out interleave_err_out test: build $(addprefix test_,$(addsuffix .native, $(TEST_HELPERS))) $(OCAMLBUILD) lib_test/test.native ./test.native install: ocamlfind install $(FINDLIB_NAME) META \ $(INSTALL) \ $(ARCHIVES) uninstall: ocamlfind remove $(FINDLIB_NAME) reinstall: uninstall install clean: ocamlbuild -clean ocaml-process-0.2.1/README.md000066400000000000000000000004141276200660700155450ustar00rootroot00000000000000## Easy process control in OCaml ```ocaml let stdout_lines = Process.read_stdout "ls" [||] in List.iter print_endline stdout_lines ``` It's as easy as that! Exit status, stdin, and stderr are also available. `process` makes it easy to use commands like functions. ocaml-process-0.2.1/_tags000066400000000000000000000001721276200660700153070ustar00rootroot00000000000000true: warn(@5@8@10@11@12@14@23@24@26@29) true: principal, bin_annot, safe_string, strict_sequence, debug "lib": include ocaml-process-0.2.1/lib/000077500000000000000000000000001276200660700150355ustar00rootroot00000000000000ocaml-process-0.2.1/lib/_tags000066400000000000000000000000261276200660700160530ustar00rootroot00000000000000<*.*>: package(bytes) ocaml-process-0.2.1/lib/process.ml000066400000000000000000000215021276200660700170450ustar00rootroot00000000000000(* * Copyright (c) 2015 David Sheets * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * *) module Signal = struct type t = | SIGABRT | SIGALRM | SIGFPE | SIGHUP | SIGILL | SIGINT | SIGKILL | SIGPIPE | SIGQUIT | SIGSEGV | SIGTERM | SIGUSR1 | SIGUSR2 | SIGCHLD | SIGCONT | SIGSTOP | SIGTSTP | SIGTTIN | SIGTTOU | SIGVTALRM | SIGPROF | Unknown of int let of_int = Sys.(function | x when x = sigabrt -> SIGABRT | x when x = sigalrm -> SIGALRM | x when x = sigfpe -> SIGFPE | x when x = sighup -> SIGHUP | x when x = sigill -> SIGILL | x when x = sigint -> SIGINT | x when x = sigkill -> SIGKILL | x when x = sigpipe -> SIGPIPE | x when x = sigquit -> SIGQUIT | x when x = sigsegv -> SIGSEGV | x when x = sigterm -> SIGTERM | x when x = sigusr1 -> SIGUSR1 | x when x = sigusr2 -> SIGUSR2 | x when x = sigchld -> SIGCHLD | x when x = sigcont -> SIGCONT | x when x = sigstop -> SIGSTOP | x when x = sigtstp -> SIGTSTP | x when x = sigttin -> SIGTTIN | x when x = sigttou -> SIGTTOU | x when x = sigvtalrm -> SIGVTALRM | x when x = sigprof -> SIGPROF | x -> Unknown x ) let to_string = function | SIGABRT -> "SIGABRT" | SIGALRM -> "SIGALRM" | SIGFPE -> "SIGFPE" | SIGHUP -> "SIGHUP" | SIGILL -> "SIGILL" | SIGINT -> "SIGINT" | SIGKILL -> "SIGKILL" | SIGPIPE -> "SIGPIPE" | SIGQUIT -> "SIGQUIT" | SIGSEGV -> "SIGSEGV" | SIGTERM -> "SIGTERM" | SIGUSR1 -> "SIGUSR1" | SIGUSR2 -> "SIGUSR2" | SIGCHLD -> "SIGCHLD" | SIGCONT -> "SIGCONT" | SIGSTOP -> "SIGSTOP" | SIGTSTP -> "SIGTSTP" | SIGTTIN -> "SIGTTIN" | SIGTTOU -> "SIGTTOU" | SIGVTALRM -> "SIGVTALRM" | SIGPROF -> "SIGPROF" | Unknown k -> "SIG"^(string_of_int k) end module Exit = struct type t = | Exit of int | Kill of Signal.t | Stop of Signal.t type error = { cwd : string; command : string; args : string array; status : t; } exception Error of error let of_unix = Unix.(function | WEXITED k -> Exit k | WSIGNALED k -> Kill (Signal.of_int k) | WSTOPPED k -> Stop (Signal.of_int k) ) let to_string = function | Exit k -> Printf.sprintf "exit %d" k | Kill k -> Printf.sprintf "kill %s" (Signal.to_string k) | Stop k -> Printf.sprintf "stop %s" (Signal.to_string k) let error_to_string { cwd; command; args; status } = let args = Array.map (Printf.sprintf "%S") args in let args_s = String.concat "; " (Array.to_list args) in Printf.sprintf "%s [|%s|] in %s: %s" command args_s cwd (to_string status) let check ?(exit_status=[0]) command args = function | Exit k when List.mem k exit_status -> () | status -> raise (Error { cwd = Unix.getcwd (); command; args; status }) end module Output = struct type t = { exit_status : Exit.t; stdout : string list; stderr : string list; } end module type S = sig type 'a io val run : ?stdin:Bytes.t -> ?exit_status:int list -> string -> string array -> Output.t io val read_stdout : ?stdin:Bytes.t -> ?exit_status:int list -> string -> string array -> string list io end module Blocking : S with type 'a io = 'a = struct type 'a io = 'a let quote = Printf.sprintf "\"%s\"" let string_of_prog_args prog args = prog ^ ( if Array.length args > 0 then " " ^ (String.concat " " Array.(to_list (map quote args))) else "" ) let rec waitpid_retry flags pid = try Unix.waitpid flags pid with Unix.Unix_error (Unix.EINTR,"waitpid","") -> waitpid_retry flags pid let io_from_fd fds fn fd = let closed = fn fd in if closed then List.filter ((<>) fd) fds else fds let select_io ~input_stdout ~stdout ~input_stderr ~stderr ~output_stdin ~stdin ~read_fds ~write_fds = let rec loop ~read_fds ~write_fds = if read_fds <> [] || write_fds <> [] then let ready_read, ready_write, _ready_exn = Unix.select read_fds write_fds [] ~-.1. in match ready_read with | fd::_ when fd = stdout -> let read_fds = io_from_fd read_fds input_stdout fd in loop ~read_fds ~write_fds | fd::_ when fd = stderr -> let read_fds = io_from_fd read_fds input_stderr fd in loop ~read_fds ~write_fds | _::_ -> failwith "unexpected read fd" (* TODO: ? *) | [] -> match ready_write with | fd::_ -> let write_fds = io_from_fd write_fds output_stdin fd in loop ~read_fds ~write_fds | [] -> failwith "select failed" (* TODO: ? *) in try let sigpipe = Sys.(signal sigpipe Signal_ignore) in loop ~read_fds ~write_fds; Sys.(set_signal sigpipe) sigpipe with Invalid_argument _ -> (* Can't ignore the pipe broken signal on Windows. *) loop ~read_fds ~write_fds let execute prog args ~output_stdin ~input_stdout ~input_stderr = let in_fd, stdin = Unix.pipe () in let stdout, out_fd = Unix.pipe () in let stderr, err_fd = Unix.pipe () in Unix.set_close_on_exec stdin; Unix.set_close_on_exec stdout; Unix.set_close_on_exec stderr; let args = Array.append [|prog|] args in let pid = Unix.create_process prog args in_fd out_fd err_fd in Unix.close in_fd; Unix.close out_fd; Unix.close err_fd; select_io ~input_stdout ~stdout ~input_stderr ~stderr ~output_stdin ~stdin ~read_fds:[ stdout; stderr ] ~write_fds:[ stdin ]; (* stdin is closed when we run out of input *) Unix.close stdout; Unix.close stderr; let status = snd (waitpid_retry [Unix.WUNTRACED] pid) in Exit.of_unix status let rindex_from buf i c = try Some (Bytes.rindex_from buf i c) with Not_found -> None let rec lines buf i acc = match rindex_from buf i '\n' with | Some 0 -> Bytes.empty :: (Bytes.sub buf 1 i) :: acc | Some j -> lines buf (j - 1) (Bytes.sub buf (j + 1) (i - j) :: acc) | None -> Bytes.sub buf 0 (i + 1) :: acc let read_lines buf len into fd = (* The EPIPE case covers an odd behavior on Windows. See . *) let n = Unix.(try read fd buf 0 len with Unix_error (EPIPE, _, _) -> 0) in if n = 0 then true (* closed *) else let ls = lines buf (n - 1) [] in begin match !into with | [] -> into := List.rev ls | partial_line::rest -> match ls with | [] -> () | first::more -> let first = Bytes.cat partial_line first in into := List.rev_append more (first :: rest) end; false (* not closed *) let run ?(stdin=Bytes.empty) ?exit_status prog args = let out_lines = ref [] in let err_lines = ref [] in let len = 4096 in let buf = Bytes.create len in let input_stdout = read_lines buf len out_lines in let input_stderr = read_lines buf len err_lines in let stdin_len = Bytes.length stdin in let stdin_off = ref 0 in let output_stdin i_fd = let off = !stdin_off in let len = stdin_len - off in if len = 0 then begin Unix.close i_fd; true (* closed, we have nothing more to write *) end else try let n = Unix.single_write i_fd stdin off len in stdin_off := off + n; false (* not closed *) with | Unix.Unix_error (Unix.EPIPE, "single_write", _) -> true (* closed *) in let exit_status' = execute prog args ~output_stdin ~input_stdout ~input_stderr in (match exit_status with | None -> () | Some exit_status -> Exit.check ~exit_status prog args exit_status' ); let exit_status = exit_status' in let stdout = List.rev_map Bytes.to_string !out_lines in let stderr = List.rev_map Bytes.to_string !err_lines in Output.({ exit_status; stdout; stderr; }) let read_stdout ?stdin ?exit_status prog args = let exit_status = match exit_status with None -> [0] | Some v -> v in (run ?stdin ~exit_status prog args).Output.stdout end include Blocking ocaml-process-0.2.1/lib/process.mli000066400000000000000000000036461276200660700172270ustar00rootroot00000000000000(* * Copyright (c) 2015 David Sheets * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * *) module Signal : sig type t = | SIGABRT | SIGALRM | SIGFPE | SIGHUP | SIGILL | SIGINT | SIGKILL | SIGPIPE | SIGQUIT | SIGSEGV | SIGTERM | SIGUSR1 | SIGUSR2 | SIGCHLD | SIGCONT | SIGSTOP | SIGTSTP | SIGTTIN | SIGTTOU | SIGVTALRM | SIGPROF | Unknown of int val of_int : int -> t val to_string : t -> string end module Exit : sig type t = | Exit of int | Kill of Signal.t | Stop of Signal.t type error = { cwd : string; command : string; args : string array; status : t; } exception Error of error val of_unix : Unix.process_status -> t val to_string : t -> string val error_to_string : error -> string end module Output : sig type t = { exit_status : Exit.t; stdout : string list; stderr : string list; } end module type S = sig type 'a io val run : ?stdin:Bytes.t -> ?exit_status:int list -> string -> string array -> Output.t io val read_stdout : ?stdin:Bytes.t -> ?exit_status:int list -> string -> string array -> string list io end include S with type 'a io = 'a ocaml-process-0.2.1/lib_test/000077500000000000000000000000001276200660700160745ustar00rootroot00000000000000ocaml-process-0.2.1/lib_test/_tags000066400000000000000000000000311276200660700171060ustar00rootroot00000000000000<*.*>: package(alcotest) ocaml-process-0.2.1/lib_test/empty_out.ml000066400000000000000000000000001276200660700204410ustar00rootroot00000000000000ocaml-process-0.2.1/lib_test/interleave_err_out.ml000066400000000000000000000002031276200660700223160ustar00rootroot00000000000000print_endline "longish"; prerr_endline "short"; print_endline "longerer"; prerr_endline "very long indeed"; print_endline "short"; ocaml-process-0.2.1/lib_test/kilobyte_writer.ml000066400000000000000000000000751276200660700216460ustar00rootroot00000000000000let kilobyte = String.make 1024 ' ' ;; print_string kilobyte ocaml-process-0.2.1/lib_test/megabyte_writer.ml000066400000000000000000000001061276200660700216140ustar00rootroot00000000000000let megabyte = String.make (1024 * 1024) ' ' ;; print_string megabyte ocaml-process-0.2.1/lib_test/nl_out.ml000066400000000000000000000000211276200660700177170ustar00rootroot00000000000000print_endline "" ocaml-process-0.2.1/lib_test/start_nl_out.ml000066400000000000000000000000371276200660700211430ustar00rootroot00000000000000Printf.printf "\nhello, world" ocaml-process-0.2.1/lib_test/test.ml000066400000000000000000000051771276200660700174170ustar00rootroot00000000000000(* * Copyright (c) 2016 David Sheets * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * *) let try_test f () = try f () with Process.Exit.Error e -> failwith (Process.Exit.error_to_string e) module Pipes = struct let full_pipe () = let megabyte = Bytes.make (1024 * 1024) ' ' in Process.read_stdout ~stdin:megabyte "./test_megabyte_writer.native" [||] |> ignore let broken_pipe () = let megabyte = Bytes.make (1024 * 1024) ' ' in Process.read_stdout ~stdin:megabyte "./test_kilobyte_writer.native" [||] |> ignore let tests = [ "full_pipe", `Quick, try_test full_pipe; "broken_pipe", `Quick, try_test broken_pipe; ] end module Reading = struct let empty () = let output = Process.read_stdout "./test_empty_out.native" [||] in Alcotest.(check (list string)) "empty output" [] output let nl () = let output = Process.read_stdout "./test_nl_out.native" [||] in Alcotest.(check (list string)) "newline output" ["";""] output let trail_nl () = let output = Process.read_stdout "./test_trail_nl_out.native" [||] in Alcotest.(check (list string)) "trailing newline output" ["hello, world";""] output let start_nl () = let output = Process.read_stdout "./test_start_nl_out.native" [||] in Alcotest.(check (list string)) "starting newline output" [""; "hello, world"] output let interleave () = (* TODO: test the stderr, too *) let output = Process.read_stdout "./test_interleave_err_out.native" [||] in Alcotest.(check (list string)) "interleaved err and out output" ["longish"; "longerer"; "short"; ""] output let tests = [ "empty", `Quick, try_test empty; "nl", `Quick, try_test nl; "trail_nl", `Quick, try_test trail_nl; "start_nl", `Quick, try_test start_nl; "interleave", `Quick, try_test interleave; ] end let tests = [ "pipes", Pipes.tests; "reading", Reading.tests; ] ;; Alcotest.run "process" tests ocaml-process-0.2.1/lib_test/trail_nl_out.ml000066400000000000000000000000351276200660700211170ustar00rootroot00000000000000print_endline "hello, world" ocaml-process-0.2.1/opam000066400000000000000000000011361276200660700151470ustar00rootroot00000000000000opam-version: "1.2" name: "process" version: "0.2.1" maintainer: "David Sheets " authors: [ "David Sheets" "Jonathan Protzenko" ] homepage: "https://github.com/dsheets/ocaml-process" bug-reports: "https://github.com/dsheets/ocaml-process/issues" license: "ISC" dev-repo: "https://github.com/dsheets/ocaml-process.git" tags: [ "process" "subprocess" "command" "system" ] build: [ [make] ] install: [make "install"] remove: ["ocamlfind" "remove" "process"] depends: [ "ocamlfind" {build} "ocamlbuild" {build} "base-unix" "base-bytes" ] available: [ ocaml-version >= "4.01.0" ]