Port utilities
To use the bindings from this module:
(import :std/misc/ports)
copy-port
(copy-port in out) -> void | error
in := input port to read from
out := output port to write to
Copy all data from port in to port out. Signals an error when in and out
aren't satisfying input-port?
and output-port?
, respectively.
Examples:
> (def nums (string-join (map number->string [1 2 3 4 5]) "\n" 'suffix))
> (call-with-output-file "~/testing/nums.txt"
(lambda (out)
(call-with-input-string nums
(lambda (in) (copy-port in out)))))
$ cat ~/testing/nums.txt ; unix-like command-line
1
2
3
4
5
> (copy-port (current-input-port) (current-output-port))
hello,
hello, ; duplicates what you type at the REPL
everyone!
everyone! ; quit with Ctrl-D
read-all-as-string
(read-all-as-string in) -> string | error
in := input port to read from
Reads all the contents of port in, returning a single string including all newline characters. Signals an error when in can't be read.
Examples:
> (import :std/srfi/13)
> (with-input-from-file "~/dev/gerbil/CHANGELOG.md"
(lambda ()
(string-take (read-all-as-string (current-input-port)) 80)))
"### 2-9-2019: Gerbil-v0.15.1\n\nPatch release to support Gambit v4.9.3\n\nDetails:\n-"
read-all-as-lines
(read-all-as-lines in [separator: #\newline] [include-separator?: #f]) -> list | error
in := input port to read from
separator := character to consider line ending
include-separator? := truth value, whether to include separator char in results
Reads all the contents of port in as a list of strings. The optional separator related keyword parameters specify what is considered a line ending and whether to include these separator characters in the line strings. Signals an error when in can't be read.
Examples:
> (import :std/srfi/1)
> (take (call-with-input-file "~/dev/gerbil/README.md" read-all-as-lines) 4)
("# Gerbil Scheme"
""
"Gerbil is an opinionated dialect of Scheme designed for Systems Programming,"
"with a state of the art macro and module system on top of the Gambit runtime.")
> (with-input-from-string "aa:bb:cc:dd::ff"
(lambda () (read-all-as-lines (current-input-port) separator: #\:)))
("aa" "bb" "cc" "dd" "" "ff")
read-file-string
(read-file-string path) -> string | error
path := path to file to read contents from
Reads contents of the file at path, returning a single string including all newline characters. Signals an error when path can't be read.
Note: There is another optional settings keyword parameter not shown above,
but it's not terribly interesting for this file reading procedure. Check section
17.7.1 Filesystem devices
of the Gambit Manual if you want to know more.
Examples:
$ cat ~/testing/nums.txt ; unix-like command-line
1
2
3
4
5
(map string->number
(string-split (read-file-string "~/testing/nums.txt") #\newline))
(1 2 3 4 5)
read-file-lines
(read-file-lines path) -> list | error
path := path to file to read contents from
Reads all lines of the file at path as a list of strings. Signals an error when path can't be read.
Note: There is another optional settings keyword parameter not shown above,
but it's not terribly interesting for this file reading procedure. Check section
17.7.1 Filesystem devices
of the Gambit Manual if you want to know more.
Examples:
$ cat ~/testing/nums.txt ; unix-like command-line
1
2
3
4
5
> (read-file-lines "~/testing/nums.txt")
("1" "2" "3" "4" "5")
;; Advent of code 2018, problem 01a: Sum a file of around 1000 exact integer values.
$ head -n5 ~/dev/aoc18/01/input.txt
+12
-13
+17
+17
-10
> (apply + (map string->number (read-file-lines "~/dev/aoc18/01/input.txt")))
508
read-all-as-u8vector
(read-all-as-u8vector in (bufsize 8192)) -> u8vector | error
in := input port to read from
bufsize := buffer size, defaults to 8192 bytes
Reads all the contents of port in, returning a single u8vector. Signals an error when in can't be read.
Examples:
> (def u8 (call-with-input-file "/path/to/file" read-all-as-u8vector))
> (u8vector-length u8)
98526
read-file-u8vector
(read-file-u8vector path settings: [] bufsize: 8192) -> u8vector | error
path := path to file to read contents from
settings := port settings, defaults to the empty list
bufsize := buffer size, defaults to 8192 bytes
Reads contents of the file at path, returning a single u8vector. Signals an error when path can't be read.
Check section 17.7.1 Filesystem devices
of the Gambit Manual if you want
to know more about the settings parameter.
Examples:
> (def u8 (read-file-u8vector "/path/to/file" bufsize: 1024))
> (u8vector-length u8)
98526
write-file-string
(write-file-string file string settings: [] newline-ending: #t) -> void | error
file := the file to be written to
string := the string to write
settings := Gambit path-settings (default [])
newline-ending := append newline if last character is not a newline (default #t)
Write string to file using the display
procedure. Check section
17.7.1 Filesystem devices
of the Gambit Manual if you want to know more about the settings
parameter.
Examples:
;; write "Hello, world!\n" to /tmp/foo.txt (may overwrite an existing file)
(write-file-string "/tmp/foo.txt" "Hello, world!") ; \n is appended automatically
;; by using a path-setting we can append a string to an existing file
(write-file-string "/tmp/foo.txt" "hi" settings: [append: #t])
;; the file content is now: "Hello, world!\nhi\n"
;; let's append another string without auto-enforcement of a newline ending
(write-file-string "/tmp/foo.txt" "ho" settings: [append: #t] newline-ending: #f)
;; the final file content is: "Hello, world!\nhi\nho" ; no trail newline character
write-file-lines
(write-file-lines file list settings: [] newline-ending: #t) -> void | error
file := the file to be written to
list := list of strings to write
settings := Gambit path-settings (default [])
newline-ending := append newline if last character is not a newline (default #t)
Write every entry of the list as newline separated line to file using
the display
procedure. Check section
17.7.1 Filesystem devices
of the Gambit Manual if you want to know more about the settings
parameter.
Examples:
(write-file-lines "/tmp/foo.txt" ["foo" "bar"])
$ cat /tmp/foo.txt ; unix-like command-line
foo
bar
force-current-outputs
(force-current-outputs) -> (void)
Force the current-output-port
and the current-error-port
.
Useful before you drop to a REPL, debugger or interactive prompt, or before you exit
the program.
Examples:
(force-current-outputs)
writeln
(writeln object [port]) -> (void)
Display a string representation of the Scheme object
as per write
, then display a line ending as
per newline
, using the optional port
which defaults to (current-output-port)
.
Examples:
> (writeln ['a 1 "foo"]) ;; response is written, return value is (void), unwritten
(a 1 \"foo\")
> (with-output (o #f) (writeln '(a 1 "foo") o))
"(a 1 \"foo\")\n"
output-contents
(output-contents contents ?port) -> void | error
contents := a string, byte array or procedure
port := an output port -- optional, defaults to (current-output-port)
Write the contents into the port: If it's a string, display
it;
if it's a byte array, use write-u8vector
; if it's a procedure, call it with the port as argument;
otherwise, throw an error.
output-contents
is notably useful as a helper within a function that
makes a port available to a consumer, e.g. by creating a port, using it once or several times
by calling output-contents, then closing it, such as call-with-output
.
Examples:
(def (create-foo contents)
(call-with-output-file ["/tmp/foo"] (cut output-contents foo <>)))
call-with-output
(call-with-output output-spec content-spec) -> output content per spec
call-with-output
creates an output port as designated by output-spec
, then
output content to it as designated by content-spec
.
The output-spec
is interpreted as follows:
- a port designates itself;
- the false value
#f
designates a fresh string output port, and the result returned is the final string content of the port; - the true value
#t
designates the(current-output-port)
; - a string designates a pathname to be open using
call-with-output-file
; - a list designates a list of settings to pass to
call-with-output-file
; - other values are invalid (a future version of Gerbil might accept additional values).
The content is handled as per output-content
: a string is displayed, a byte array is written,
a procedure is called with the port as argument.
The result returned depends on the specified output:
typically, it's the result of the procedure passed as content
(or void if a string or byte array is passed as content);
but if the output-spec
was false, a string containing the accumulated output is returned.
output-contents
is notably useful as a helper within a function that
makes a port available to a consumer, e.g. by creating a port, using it once or several times
by calling output-contents, then closing it, such as call-with-output
.
call-with-output
is a good helper for higher-order functions that themselves produce or wrap
text content, and whose output is as often used standalone or transcluded as part of other functions
(with a port as an argument in the latter case).
Examples:
;; pretty printer for a datastructure ms of type my-struct, with optional output-spec o
(def (pp-my-struct ms (o #f))
(call-with-output o
(lambda (port)
(display "my-struct { a: " port)
(pp-a (my-struct-a ms) port)
(display " b: " port)
(pp-a (my-struct-b ms) port)
(display " }" port))))
with-output
(with-output (o output-spec) body ...) -> same as (call-with-output output-spec (lambda (o) body ...))
(with-output (o) body ...) -> (call-with-output o (lambda (o) body ...))
with-output
is a simple macro that wraps around call-with-output
.
Interestingly, when its first argument is a list of one symbol, that symbol is used as both
input value for an output-spec
as per call-with-output
in the outer scope,
and as output binding for a resolved port in the inner scope,
allowing for seamless resolution of an output-spec
designator around the inner scope.
Examples:
;; same example as above using with-output
(def (pp-my-struct ms (o #f))
(with-output (o)
(display "my-struct { a: " o)
(pp-a (my-struct-a ms) o)
(display " b: " o)
(pp-a (my-struct-b ms) o)
(display " }" o)))
call-with-input
(call-with-input input-spec f) -> call f with specified input
call-with-input
creates an input port as designated by input-spec
, then
calls the function f
with that port as argument.
The input-spec
is interpreted as follows:
- a port designates itself;
- the true value
#t
designates the(current-input-port)
; - a string designates a port to be open by passing it to
call-with-output-string
; - a list designates the settings to pass to
call-with-output-file
; - other values are invalid (a future version of Gerbil might accept additional values).
The result returned is that of the call to function f
.
call-with-input
is a good helper for higher-order functions that themselves consume or wrap
the consumption of a character stream, and whose input is as a complete string or file as it is
a stream passed as part of larger parsing effort (with a port as an argument in the latter case).
Examples:
;; parser for a datastructure
(def (parse-my-struct i)
(call-with-input i
(lambda (port)
(let* ((a (parse-a-field port))
(b (parse-b-field port)))
(make-my-struct a b)))))
with-input
(with-input (i input-spec) body ...) -> same as (call-with-input input-spec (lambda (i) body ...))
(with-input (i) body ...) -> (call-with-input i (lambda (i) body ...))
with-input
is a simple macro that wraps around call-with-input
.
Interestingly, when its first argument is a list of one symbol, that symbol is used as both
input value for an input-spec
as per call-with-input
in the outer scope,
and as input binding for a resolved port in the inner scope,
allowing for seamless resolution of an input-spec
designator around the inner scope.
Examples:
;; same example as above using with-input
(def (parse-my-struct i)
(with-input (i)
(let* ((a (parse-a-field i))
(b (parse-b-field i)))
(make-my-struct a b))))
Port Destructor
(defmethod {destroy <port>} close-port)
The module also defines a destroy
method for ports, so that they can be used
in with-destroy
forms and other primitives that use the destroy idiom,
ensuring that ports will be closed even if an error is signaled somewhere within
the body.
Examples:
> (define (for-each-dir-entry dir proc)
(let ((dir-port (open-directory dir)))
(let loop ()
(let ((file (read dir-port)))
(if (eof-object? file)
(close-port dir-port)
(begin
(proc file)
(loop)))))))
;; could also be written like this utilizing with-destroy:
> (import :std/sugar)
> (define (for-each-dir-entry dir proc)
(let ((dir-port (open-directory dir)))
(with-destroy dir-port
;; dir-port will be closed upon exiting this scope
(let loop ((file (read dir-port)))
(unless (eof-object? file)
(proc file)
(loop (read dir-port)))))))
> (for-each-dir-entry "/home/username" displayln)
dev
downloads
videos
documents
desktop
pictures
music
testing