io.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/ (DIR) Log (DIR) Files (DIR) Refs (DIR) Tags (DIR) README (DIR) LICENSE --- io.lisp (8280B) --- 1 ;; Copyright (c) 2002-2006, Edward Marco Baringer 2 ;; All rights reserved. 3 4 (in-package :alexandria) 5 6 (defmacro with-open-file* ((stream filespec &key direction element-type 7 if-exists if-does-not-exist external-format) 8 &body body) 9 "Just like WITH-OPEN-FILE, but NIL values in the keyword arguments mean to use 10 the default value specified for OPEN." 11 (once-only (direction element-type if-exists if-does-not-exist external-format) 12 `(with-open-stream 13 (,stream (apply #'open ,filespec 14 (append 15 (when ,direction 16 (list :direction ,direction)) 17 (when ,element-type 18 (list :element-type ,element-type)) 19 (when ,if-exists 20 (list :if-exists ,if-exists)) 21 (when ,if-does-not-exist 22 (list :if-does-not-exist ,if-does-not-exist)) 23 (when ,external-format 24 (list :external-format ,external-format))))) 25 ,@body))) 26 27 (defmacro with-input-from-file ((stream-name file-name &rest args 28 &key (direction nil direction-p) 29 &allow-other-keys) 30 &body body) 31 "Evaluate BODY with STREAM-NAME to an input stream on the file 32 FILE-NAME. ARGS is sent as is to the call to OPEN except EXTERNAL-FORMAT, 33 which is only sent to WITH-OPEN-FILE when it's not NIL." 34 (declare (ignore direction)) 35 (when direction-p 36 (error "Can't specify :DIRECTION for WITH-INPUT-FROM-FILE.")) 37 `(with-open-file* (,stream-name ,file-name :direction :input ,@args) 38 ,@body)) 39 40 (defmacro with-output-to-file ((stream-name file-name &rest args 41 &key (direction nil direction-p) 42 &allow-other-keys) 43 &body body) 44 "Evaluate BODY with STREAM-NAME to an output stream on the file 45 FILE-NAME. ARGS is sent as is to the call to OPEN except EXTERNAL-FORMAT, 46 which is only sent to WITH-OPEN-FILE when it's not NIL." 47 (declare (ignore direction)) 48 (when direction-p 49 (error "Can't specify :DIRECTION for WITH-OUTPUT-TO-FILE.")) 50 `(with-open-file* (,stream-name ,file-name :direction :output ,@args) 51 ,@body)) 52 53 (defun read-stream-content-into-string (stream &key (buffer-size 4096)) 54 "Return the \"content\" of STREAM as a fresh string." 55 (check-type buffer-size positive-integer) 56 (let ((*print-pretty* nil)) 57 (with-output-to-string (datum) 58 (let ((buffer (make-array buffer-size :element-type 'character))) 59 (loop 60 :for bytes-read = (read-sequence buffer stream) 61 :do (write-sequence buffer datum :start 0 :end bytes-read) 62 :while (= bytes-read buffer-size)))))) 63 64 (defun read-file-into-string (pathname &key (buffer-size 4096) external-format) 65 "Return the contents of the file denoted by PATHNAME as a fresh string. 66 67 The EXTERNAL-FORMAT parameter will be passed directly to WITH-OPEN-FILE 68 unless it's NIL, which means the system default." 69 (with-input-from-file 70 (file-stream pathname :external-format external-format) 71 (read-stream-content-into-string file-stream :buffer-size buffer-size))) 72 73 (defun write-string-into-file (string pathname &key (if-exists :error) 74 if-does-not-exist 75 external-format) 76 "Write STRING to PATHNAME. 77 78 The EXTERNAL-FORMAT parameter will be passed directly to WITH-OPEN-FILE 79 unless it's NIL, which means the system default." 80 (with-output-to-file (file-stream pathname :if-exists if-exists 81 :if-does-not-exist if-does-not-exist 82 :external-format external-format) 83 (write-sequence string file-stream))) 84 85 (defun read-stream-content-into-byte-vector (stream &key ((%length length)) 86 (initial-size 4096)) 87 "Return \"content\" of STREAM as freshly allocated (unsigned-byte 8) vector." 88 (check-type length (or null non-negative-integer)) 89 (check-type initial-size positive-integer) 90 (do ((buffer (make-array (or length initial-size) 91 :element-type '(unsigned-byte 8))) 92 (offset 0) 93 (offset-wanted 0)) 94 ((or (/= offset-wanted offset) 95 (and length (>= offset length))) 96 (if (= offset (length buffer)) 97 buffer 98 (subseq buffer 0 offset))) 99 (unless (zerop offset) 100 (let ((new-buffer (make-array (* 2 (length buffer)) 101 :element-type '(unsigned-byte 8)))) 102 (replace new-buffer buffer) 103 (setf buffer new-buffer))) 104 (setf offset-wanted (length buffer) 105 offset (read-sequence buffer stream :start offset)))) 106 107 (defun read-file-into-byte-vector (pathname) 108 "Read PATHNAME into a freshly allocated (unsigned-byte 8) vector." 109 (with-input-from-file (stream pathname :element-type '(unsigned-byte 8)) 110 (read-stream-content-into-byte-vector stream '%length (file-length stream)))) 111 112 (defun write-byte-vector-into-file (bytes pathname &key (if-exists :error) 113 if-does-not-exist) 114 "Write BYTES to PATHNAME." 115 (check-type bytes (vector (unsigned-byte 8))) 116 (with-output-to-file (stream pathname :if-exists if-exists 117 :if-does-not-exist if-does-not-exist 118 :element-type '(unsigned-byte 8)) 119 (write-sequence bytes stream))) 120 121 (defun copy-file (from to &key (if-to-exists :supersede) 122 (element-type '(unsigned-byte 8)) finish-output) 123 (with-input-from-file (input from :element-type element-type) 124 (with-output-to-file (output to :element-type element-type 125 :if-exists if-to-exists) 126 (copy-stream input output 127 :element-type element-type 128 :finish-output finish-output)))) 129 130 (defun copy-stream (input output &key (element-type (stream-element-type input)) 131 (buffer-size 4096) 132 (buffer (make-array buffer-size :element-type element-type)) 133 (start 0) end 134 finish-output) 135 "Reads data from INPUT and writes it to OUTPUT. Both INPUT and OUTPUT must 136 be streams, they will be passed to READ-SEQUENCE and WRITE-SEQUENCE and must have 137 compatible element-types." 138 (check-type start non-negative-integer) 139 (check-type end (or null non-negative-integer)) 140 (check-type buffer-size positive-integer) 141 (when (and end 142 (< end start)) 143 (error "END is smaller than START in ~S" 'copy-stream)) 144 (let ((output-position 0) 145 (input-position 0)) 146 (unless (zerop start) 147 ;; FIXME add platform specific optimization to skip seekable streams 148 (loop while (< input-position start) 149 do (let ((n (read-sequence buffer input 150 :end (min (length buffer) 151 (- start input-position))))) 152 (when (zerop n) 153 (error "~@<Could not read enough bytes from the input to fulfill ~ 154 the :START ~S requirement in ~S.~:@>" 'copy-stream start)) 155 (incf input-position n)))) 156 (assert (= input-position start)) 157 (loop while (or (null end) (< input-position end)) 158 do (let ((n (read-sequence buffer input 159 :end (when end 160 (min (length buffer) 161 (- end input-position)))))) 162 (when (zerop n) 163 (if end 164 (error "~@<Could not read enough bytes from the input to fulfill ~ 165 the :END ~S requirement in ~S.~:@>" 'copy-stream end) 166 (return))) 167 (incf input-position n) 168 (write-sequence buffer output :end n) 169 (incf output-position n))) 170 (when finish-output 171 (finish-output output)) 172 output-position))