;; ;; util-io.scm ;; ;; Special IO extensions to deal with weird stuff. ;; ;; ISC License ;; ;; Copyright 2023 Brmlab, z.s. ;; Dominik Pantůček ;; ;; Permission to use, copy, modify, and/or 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. ;; (declare (unit util-io)) (import duck) (module* util-io #:doc ("Module implementing advanced I/O.") ( read-lines/no-bom get-process-output-lines get-process-exit+output-lines process-send/recv ) (import scheme (chicken base) (chicken io) (chicken process) (chicken format)) ;; If given string begins with UTF-8 BOM, it is removed. (define (remove-optional-bom str) (if (< (string-length str) 3) str (let ((maybe-bom (substring str 0 3))) (if (string=? maybe-bom "\xEF\xBB\xBF") (substring str 3) str)))) (define/doc (read-lines/no-bom ip) (" * ```port``` - an input port Reads lines using ```read-lines``` and if the first line contains UTF-8 BOM, removes it. ") (let ((lines (read-lines ip))) (if (null? lines) lines (cons (remove-optional-bom (car lines)) (cdr lines))))) (define/doc (get-process-output-lines cmd . args) (" * ```cmd``` - a string with the command * ```args``` - list of arguments to pass to process Returns a list of strings representing all the lines produced by running the command given. ") (let-values (((stdout stdin pid stderr) (process* cmd (map (lambda (x) (format "~A" x)) args)))) (close-output-port stdin) (let ((result (read-lines stdout))) (let-values (((pid exit-ok? exit/signal) (process-wait pid))) result)))) (define/doc (get-process-exit+output-lines cmd . args) (" * ```cmd``` - a string with the command * ```args``` - list of arguments to pass to process Returns two values - an exit code and a list of strings representing all the lines produced by running the command given. ") (let-values (((stdout stdin pid stderr) (process* cmd (map (lambda (x) (format "~A" x)) args)))) (close-output-port stdin) (let ((result (read-lines stdout))) (let-values (((pid exit-ok? exit/signal) (process-wait pid))) (values exit/signal result))))) (define/doc (process-send/recv cmd args . lines) (" * ```cmd``` - a string with command * ```args``` - list of arguments * ```lines``` - lines to feed to stdin of the process Executes given command ```cmd``` with given argument list ```args``` writing all ```lines``` to its standard input and then reads all the process output. ") (let-values (((stdout stdin pid stderr) (process* cmd args))) (let loop ((lines lines)) (when (not (null? lines)) (display (car lines) stdin) (newline stdin) (loop (cdr lines)))) (close-output-port stdin) (let ((result (read-lines stdout))) (let-values (((pid exit-ok? exit/signal) (process-wait pid))) (close-input-port stdout) (close-input-port stderr) result)))) )