Skip to content

Commit 6570541

Browse files
author
José Valim
committed
Add typespecs and to_string conversion to some IO APIs
1 parent b7da298 commit 6570541

File tree

2 files changed

+64
-22
lines changed

2 files changed

+64
-22
lines changed

lib/elixir/lib/io.ex

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,26 @@ end
77

88
defmodule IO do
99
@moduledoc """
10-
Module responsible for doing IO. Many functions in this
11-
module expects an IO device and an io data encoded in UTF-8.
12-
Use the bin* functions if the data is binary, useful when
13-
working with raw bytes or when no unicode conversion should
14-
be performed.
10+
Functions handling IO.
1511
12+
Many functions in this module expects an IO device as argument.
1613
An IO device must be a pid or an atom representing a process.
1714
For convenience, Elixir provides `:stdio` and `:stderr` as
1815
shortcuts to Erlang's `:standard_io` and `:standard_error`.
1916
20-
An io data can be:
17+
The majority of the functions expect data encoded in UTF-8
18+
and will do a conversion to string, via the `String.Chars`
19+
protocol (as shown in typespecs).
2120
22-
* A list of integers representing a string. Any unicode
23-
character must be represented with one entry in the list,
24-
this entry being an integer with the codepoint value;
25-
26-
* A binary in which unicode characters are represented
27-
with many bytes (Elixir's default representation);
28-
29-
* A list of binaries or a list of char lists (as described above);
21+
The functions starting with `bin*` expects iodata as arguments,
22+
i.e. iolists or binaries with no particular encoding.
3023
3124
"""
3225

26+
@type device :: atom | pid
27+
@type chardata :: char_list | String.Chars.t
28+
@type nodata :: { :error, term } | :eof
29+
3330
import :erlang, only: [group_leader: 0]
3431

3532
defmacrop is_iolist(data) do
@@ -50,6 +47,7 @@ defmodule IO do
5047
for instance `{:error, :estale}` if reading from an
5148
NFS file system.
5249
"""
50+
@spec read(device, :line | non_neg_integer) :: chardata | nodata
5351
def read(device // group_leader, chars_or_line)
5452

5553
def read(device, :line) do
@@ -72,6 +70,7 @@ defmodule IO do
7270
for instance `{:error, :estale}` if reading from an
7371
NFS file system.
7472
"""
73+
@spec binread(device, :line | non_neg_integer) :: iodata | nodata
7574
def binread(device // group_leader, chars_or_line)
7675

7776
def binread(device, :line) do
@@ -105,8 +104,9 @@ defmodule IO do
105104
#=> "error"
106105
107106
"""
108-
def write(device // group_leader(), item) when is_iolist(item) do
109-
:io.put_chars map_dev(device), item
107+
@spec write(device, chardata) :: :ok
108+
def write(device // group_leader(), item) do
109+
:io.put_chars map_dev(device), to_chardata(item)
110110
end
111111

112112
@doc """
@@ -115,6 +115,7 @@ defmodule IO do
115115
116116
Check `write/2` for more information.
117117
"""
118+
@spec binwrite(device, iodata) :: :ok | { :error, term }
118119
def binwrite(device // group_leader(), item) when is_iolist(item) do
119120
:file.write map_dev(device), item
120121
end
@@ -124,9 +125,10 @@ defmodule IO do
124125
but adds a newline at the end. The argument is expected
125126
to be a chardata.
126127
"""
127-
def puts(device // group_leader(), item) when is_iolist(item) do
128+
@spec puts(device, chardata) :: :ok
129+
def puts(device // group_leader(), item) do
128130
erl_dev = map_dev(device)
129-
:io.put_chars erl_dev, [item, ?\n]
131+
:io.put_chars erl_dev, [to_chardata(item), ?\n]
130132
end
131133

132134
@doc """
@@ -142,13 +144,15 @@ defmodule IO do
142144
IO.inspect Process.list
143145
144146
"""
147+
@spec inspect(term, Keyword.t) :: term
145148
def inspect(item, opts // []) do
146149
inspect group_leader(), item, opts
147150
end
148151

149152
@doc """
150153
Inspects the item with options using the given device.
151154
"""
155+
@spec inspect(device, term, Keyword.t) :: term
152156
def inspect(device, item, opts) when is_list(opts) do
153157
opts = Keyword.put_new(opts, :pretty, true)
154158

@@ -178,6 +182,8 @@ defmodule IO do
178182
for instance `{:error, :estale}` if reading from an
179183
NFS file system.
180184
"""
185+
@spec getn(chardata, pos_integer) :: chardata | nodata
186+
@spec getn(device, chardata) :: chardata | nodata
181187
def getn(prompt, count // 1)
182188

183189
def getn(prompt, count) when is_integer(count) do
@@ -194,8 +200,9 @@ defmodule IO do
194200
the number of unicode codepoints to be retrieved.
195201
Otherwise, `count` is the number of raw bytes to be retrieved.
196202
"""
203+
@spec getn(device, chardata, pos_integer) :: chardata | nodata
197204
def getn(device, prompt, count) do
198-
:io.get_chars(map_dev(device), prompt, count)
205+
:io.get_chars(map_dev(device), to_chardata(prompt), count)
199206
end
200207

201208
@doc """
@@ -210,8 +217,9 @@ defmodule IO do
210217
for instance `{:error, :estale}` if reading from an
211218
NFS file system.
212219
"""
220+
@spec gets(device, chardata) :: chardata | nodata
213221
def gets(device // group_leader(), prompt) do
214-
:io.get_line(map_dev(device), prompt)
222+
:io.get_line(map_dev(device), to_chardata(prompt))
215223
end
216224

217225
@doc """
@@ -230,8 +238,9 @@ defmodule IO do
230238
Enum.each IO.stream(:stdio, :line), &IO.write(&1)
231239
232240
"""
233-
def stream(device, line_or_bytes) do
234-
fn(acc, f) -> stream(map_dev(device), line_or_bytes, acc, f) end
241+
@spec stream(device, :line | pos_integer) :: Enumerable.t
242+
def stream(device, line_or_codepoints) do
243+
fn(acc, f) -> stream(map_dev(device), line_or_codepoints, acc, f) end
235244
end
236245

237246
@doc """
@@ -240,6 +249,7 @@ defmodule IO do
240249
241250
This reads the io as a raw binary.
242251
"""
252+
@spec binstream(device, :line | pos_integer) :: Enumerable.t
243253
def binstream(device, line_or_bytes) do
244254
fn(acc, f) -> binstream(map_dev(device), line_or_bytes, acc, f) end
245255
end
@@ -272,4 +282,7 @@ defmodule IO do
272282
defp map_dev(:stdio), do: :standard_io
273283
defp map_dev(:stderr), do: :standard_error
274284
defp map_dev(other), do: other
285+
286+
defp to_chardata(list) when is_list(list), do: list
287+
defp to_chardata(other), do: to_string(other)
275288
end

lib/elixir/test/elixir/io_test.exs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ Code.require_file "test_helper.exs", __DIR__
22

33
defmodule IOTest do
44
use ExUnit.Case, async: true
5+
import ExUnit.CaptureIO
56

67
test :read_with_count do
78
{ :ok, file } = File.open(Path.expand('../fixtures/file.txt', __FILE__), [:charlist])
@@ -77,4 +78,32 @@ defmodule IOTest do
7778
assert "日\n" == IO.binread(file, :line)
7879
assert File.close(file) == :ok
7980
end
81+
82+
test :puts_with_chardata do
83+
assert capture_io(fn -> IO.puts("hello") end) == "hello\n"
84+
assert capture_io(fn -> IO.puts('hello') end) == "hello\n"
85+
assert capture_io(fn -> IO.puts(:hello) end) == "hello\n"
86+
assert capture_io(fn -> IO.puts(13) end) == "13\n"
87+
end
88+
89+
test :write_with_chardata do
90+
assert capture_io(fn -> IO.write("hello") end) == "hello"
91+
assert capture_io(fn -> IO.write('hello') end) == "hello"
92+
assert capture_io(fn -> IO.write(:hello) end) == "hello"
93+
assert capture_io(fn -> IO.write(13) end) == "13"
94+
end
95+
96+
test :gets_with_chardata do
97+
assert capture_io("foo\n", fn -> IO.gets("hello") end) == "hello"
98+
assert capture_io("foo\n", fn -> IO.gets('hello') end) == "hello"
99+
assert capture_io("foo\n", fn -> IO.gets(:hello) end) == "hello"
100+
assert capture_io("foo\n", fn -> IO.gets(13) end) == "13"
101+
end
102+
103+
test :getn_with_chardata do
104+
assert capture_io("foo\n", fn -> IO.getn("hello", 3) end) == "hello"
105+
assert capture_io("foo\n", fn -> IO.getn('hello', 3) end) == "hello"
106+
assert capture_io("foo\n", fn -> IO.getn(:hello, 3) end) == "hello"
107+
assert capture_io("foo\n", fn -> IO.getn(13, 3) end) == "13"
108+
end
80109
end

0 commit comments

Comments
 (0)