Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

with-output-to-destination #155

Open
mmontone opened this issue Jul 23, 2023 · 6 comments
Open

with-output-to-destination #155

mmontone opened this issue Jul 23, 2023 · 6 comments

Comments

@mmontone
Copy link

Hi.

I use this macro in some of my systems, to write to an output stream created from a "destination" spec:

  • If pathname or string, opens a file.
  • If nil, writes to a string.
  • If T, writes to *standard-output*.

Similar to CL:FORMAT destination argument, but also writes to files.

I've opened this issue in case you are interested and would like to include in Serapeum.

(defun call-with-output-to-destination (destination function &rest args)
  "Evaluate FUNCTION with a stream created from DESTINATION as argument.
If DESTINATION is a pathname, then it is opened for writing.
If it is a string, then it is interpreted as a PATHNAME.
If it is a stream, then it is used as it is.
If it is NIL, then WITH-OUTPUT-TO-STRING is used to create the stream.
If it is T, then *STANDARD-OUTPUT* is used for the stream.
ARGS are used for OPEN-FILE calls."
  (etypecase destination
    ((or pathname string)
     (let ((stream (apply #'open destination :direction :output args)))
       (unwind-protect
            (funcall function stream)
         (close stream))))
    (stream
     (funcall function destination))
    (null
     (with-output-to-string (stream)
       (funcall function stream)))
    (t
     (funcall function *standard-output*))))       
    
(defmacro with-output-to-destination ((var destination &rest args) &body body)
  "Evaluate BODY with VAR bound to a stream created from DESTINATION.
If DESTINATION is a pathname, then it is opened for writing.
If it is a string, then it is interpreted as a PATHNAME.
If it is a stream, then it is used as it is.
If it is NIL, then WITH-OUTPUT-TO-STRING is used to create the stream.
If it is T, then *STANDARD-OUTPUT* is used for the stream.
ARGS are used for OPEN-FILE calls."
  `(call-with-output-to-destination ,destination (lambda (,var) ,@body) ,@args))
@mmontone
Copy link
Author

I've just realized this is equivalent to UIOP/STREAM:WITH-OUTPUT

@mmontone
Copy link
Author

My version supports passing arguments to OPEN when opening files, and that's important for my use cases, as I may want to overwrite, etc. So this may still be useful.

@mmontone mmontone reopened this Jul 23, 2023
@ruricolist
Copy link
Owner

The one concern I have it here is interpreting strings as files -- cl:with-output-to-string, serapeum:with-string, and uiop:with-output all allow writing to a string with a fill pointer as if it were a stream. The function should at least check for a fill pointer and treat it as a stream in that case.

@mmontone
Copy link
Author

mmontone commented Jul 23, 2023 via email

@mmontone
Copy link
Author

How about this:

(defun call-with-output-to-destination (destination function &rest args)
  "Evaluate FUNCTION with a stream created from DESTINATION as argument.
If DESTINATION is a pathname, then open the file for writing. ARGS are used in the OPEN call.
If it is a string with a fill-pointer, WITH-OUTPUT-TO-STRING is used to create a stream for it.
If it is a stream, then it is used as it is.
If it is NIL, then WITH-OUTPUT-TO-STRING is used to create the stream.
If it is T, then *STANDARD-OUTPUT* is used for the stream."
  (etypecase destination
    (pathname
     (let ((stream (apply #'open destination :direction :output args)))
       (unwind-protect
            (funcall function stream)
         (close stream))))
    (string
     (assert (array-has-fill-pointer-p destination)
             nil "Destination string doesn't have a fill-pointer")
     (let ((result nil))
       (with-output-to-string (stream destination)
         (setf result (funcall function stream)))
       result))
    (stream
     (funcall function destination))
    (null
     (with-output-to-string (stream)
       (funcall function stream)))
    ((eql t)
     (funcall function *standard-output*))))
    
(defmacro with-output-to-destination ((var destination &rest args) &body body)
  "Evaluate BODY with VAR bound to a stream created from DESTINATION.
If DESTINATION is a pathname, then open the file for writing. ARGS are used in the OPEN call.
If it is a string with a fill-pointer, use WITH-OUTPUT-TO-STRING to create a stream for it.
If it is a stream, then it is used as it is.
If it is NIL, then WITH-OUTPUT-TO-STRING is used to create the stream.
If it is T, then *STANDARD-OUTPUT* is used for the stream."
  `(call-with-output-to-destination ,destination (lambda (,var) ,@body) ,@args))

Strings are not interpreted as pathnames anymore, to avoid any confusion. And are required to have a fill-pointer.

@ruricolist
Copy link
Owner

That sounds good to me, if you'd like to make an MR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants