Files
n8n-openai-adapter/lib/elixir/lib/io.ex
T
2013-12-09 18:14:34 +01:00

295 lines
8.0 KiB
Elixir

defexception IO.StreamError, [:reason, :message] do
def exception(opts) do
reason = opts[:reason]
formatted = iolist_to_binary(:file.format_error(reason))
IO.StreamError[message: "error during streaming: #{formatted}", reason: reason]
end
end
defmodule IO do
@moduledoc """
Functions handling IO.
Many functions in this module expects an IO device as argument.
An IO device must be a pid or an atom representing a process.
For convenience, Elixir provides `:stdio` and `:stderr` as
shortcuts to Erlang's `:standard_io` and `:standard_error`.
The majority of the functions expect data encoded in UTF-8
and will do a conversion to string, via the `String.Chars`
protocol (as shown in typespecs).
The functions starting with `bin*` expects iodata as arguments,
i.e. iolists or binaries with no particular encoding.
"""
@type device :: atom | pid
@type chardata :: char_list | String.Chars.t
@type nodata :: { :error, term } | :eof
import :erlang, only: [group_leader: 0]
defmacrop is_iolist(data) do
quote do
is_list(unquote(data)) or is_binary(unquote(data))
end
end
@doc """
Reads `count` characters from the IO device or until
the end of the line if `:line` is given. It returns:
* `data` - The input characters.
* `:eof` - End of file was encountered.
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec read(device, :line | non_neg_integer) :: chardata | nodata
def read(device // group_leader, chars_or_line)
def read(device, :line) do
:io.get_line(map_dev(device), '')
end
def read(device, count) when count >= 0 do
:io.get_chars(map_dev(device), '', count)
end
@doc """
Reads `count` bytes from the IO device or until
the end of the line if `:line` is given. It returns:
* `data` - The input characters.
* `:eof` - End of file was encountered.
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
def binread(device // group_leader, chars_or_line)
def binread(device, :line) do
case :file.read_line(map_dev(device)) do
{ :ok, data } -> data
other -> other
end
end
def binread(device, count) when count >= 0 do
case :file.read(map_dev(device), count) do
{ :ok, data } -> data
other -> other
end
end
@doc """
Writes the given argument to the given device.
By default the device is the standard output.
The argument is expected to be a chardata (i.e.
a char list or an unicode binary).
It returns `:ok` if it succeeds.
## Examples
IO.write "sample"
#=> "sample"
IO.write :stderr, "error"
#=> "error"
"""
@spec write(device, chardata) :: :ok
def write(device // group_leader(), item) do
:io.put_chars map_dev(device), to_chardata(item)
end
@doc """
Writes the given argument to the given device
as a binary, no unicode conversion happens.
Check `write/2` for more information.
"""
@spec binwrite(device, iodata) :: :ok | { :error, term }
def binwrite(device // group_leader(), item) when is_iolist(item) do
:file.write map_dev(device), item
end
@doc """
Writes the argument to the device, similar to `write/2`,
but adds a newline at the end. The argument is expected
to be a chardata.
"""
@spec puts(device, chardata) :: :ok
def puts(device // group_leader(), item) do
erl_dev = map_dev(device)
:io.put_chars erl_dev, [to_chardata(item), ?\n]
end
@doc """
Inspects and writes the given argument to the device
followed by a newline. A set of options can be given.
It sets by default pretty printing to true and the
width to be the width of the device, with a minimum
of 80 characters.
## Examples
IO.inspect Process.list
"""
@spec inspect(term, Keyword.t) :: term
def inspect(item, opts // []) do
inspect group_leader(), item, opts
end
@doc """
Inspects the item with options using the given device.
"""
@spec inspect(device, term, Keyword.t) :: term
def inspect(device, item, opts) when is_list(opts) do
opts = Keyword.put_new(opts, :pretty, true)
unless Keyword.get(opts, :width) do
opts = case :io.columns(device) do
{ :ok, width } -> Keyword.put(opts, :width, width)
{ :error, _ } -> opts
end
end
puts device, Kernel.inspect(item, opts)
item
end
@doc """
Gets a number of bytes from the io device. If the
io device is a unicode device, `count` implies
the number of unicode codepoints to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
It returns:
* `data` - The input characters.
* `:eof` - End of file was encountered.
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec getn(chardata, pos_integer) :: chardata | nodata
@spec getn(device, chardata) :: chardata | nodata
def getn(prompt, count // 1)
def getn(prompt, count) when is_integer(count) do
getn(group_leader, prompt, count)
end
def getn(device, prompt) do
getn(device, prompt, 1)
end
@doc """
Gets a number of bytes from the io device. If the
io device is a unicode device, `count` implies
the number of unicode codepoints to be retrieved.
Otherwise, `count` is the number of raw bytes to be retrieved.
"""
@spec getn(device, chardata, pos_integer) :: chardata | nodata
def getn(device, prompt, count) do
:io.get_chars(map_dev(device), to_chardata(prompt), count)
end
@doc """
Reads a line from the IO device. It returns:
* `data` - The characters in the line terminated
by a LF (or end of file).
* `:eof` - End of file was encountered.
* `{:error, reason}` - Other (rare) error condition,
for instance `{:error, :estale}` if reading from an
NFS file system.
"""
@spec gets(device, chardata) :: chardata | nodata
def gets(device // group_leader(), prompt) do
:io.get_line(map_dev(device), to_chardata(prompt))
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line if `:line` is given or by a given
number of codepoints.
This reads the IO as utf-8. Check out
`IO.binstream/2` to handle the IO as a raw binary.
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
## Examples
Here is an example on how we mimic an echo server
from the command line:
Enum.each IO.stream(:stdio, :line), &IO.write(&1)
"""
@spec stream(device, :line | pos_integer) :: Enumerable.t
def stream(device, line_or_codepoints) do
Stream.unfold(map_dev(device), &do_stream(&1, line_or_codepoints))
end
@doc """
Converts the io device into a Stream. The device is
iterated line by line or by a number of bytes. This
reads the IO as a raw binary.
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t
def binstream(device, line_or_bytes) do
Stream.unfold(map_dev(device), &do_binstream(&1, line_or_bytes))
end
@doc false
def do_stream(device, what) do
case read(device, what) do
:eof ->
nil
{ :error, reason } ->
raise IO.StreamError, reason: reason
data ->
{ data, device }
end
end
@doc false
def do_binstream(device, what) do
case binread(device, what) do
:eof ->
nil
{ :error, reason } ->
raise IO.StreamError, reason: reason
data ->
{ data, device }
end
end
# Map the Elixir names for standard io and error to Erlang names
defp map_dev(:stdio), do: :standard_io
defp map_dev(:stderr), do: :standard_error
defp map_dev(other), do: other
defp to_chardata(list) when is_list(list), do: list
defp to_chardata(other), do: to_string(other)
end