Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions lib/elixir/lib/file/stream.ex
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ defmodule File.Stream do

counter = fn device ->
device = skip_bom_and_offset(device, raw, modes)
count_lines(device, path, pattern, read_function(stream), 0)
count_lines(device, path, pattern, read_function(stream), 0, :empty)
end

{:ok, open!(stream, modes, counter)}
Expand Down Expand Up @@ -229,21 +229,28 @@ defmodule File.Stream do
for mode <- modes, mode not in [:write, :append, :trim_bom], do: mode
end

defp count_lines(device, path, pattern, read, count) do
defp count_lines(device, path, pattern, read, count, last_byte) do
case read.(device) do
data when is_binary(data) and byte_size(data) > 0 ->
newlines = length(:binary.matches(data, pattern))
last = :binary.last(data)
count_lines(device, path, pattern, read, count + newlines, last)

data when is_binary(data) ->
count_lines(device, path, pattern, read, count + count_lines(data, pattern))
count_lines(device, path, pattern, read, count, last_byte)

:eof ->
count
case last_byte do
:empty -> 0
?\n -> count
_ -> count + 1
end

{:error, reason} ->
raise File.Error, reason: reason, action: "stream", path: path
end
end

defp count_lines(data, pattern), do: length(:binary.matches(data, pattern))

defp read_function(%{raw: true}), do: &IO.binread(&1, @read_ahead_size)
defp read_function(%{raw: false}), do: &IO.read(&1, @read_ahead_size)
end
Expand Down
28 changes: 28 additions & 0 deletions lib/elixir/test/elixir/file/stream_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,34 @@ defmodule File.StreamTest do
assert Enum.count(stream) == 2
end

test "counts lines without trailing newline" do
no_trailing = tmp_path("no_trailing.txt")
single_line = tmp_path("single_line.txt")
empty_file = tmp_path("empty.txt")

try do
File.write!(no_trailing, "line1\nline2\nline3")
File.write!(single_line, "hello")
File.write!(empty_file, "")

# 3 lines, no trailing newline
stream = stream!(@node, no_trailing)
assert Enum.count(stream) == 3

# 1 line, no newline at all
stream = stream!(@node, single_line)
assert Enum.count(stream) == 1

# empty file
stream = stream!(@node, empty_file)
assert Enum.count(stream) == 0
after
File.rm(no_trailing)
File.rm(single_line)
File.rm(empty_file)
end
end

test "reads and writes lines" do
src = fixture_path("file.txt")
dest = tmp_path("tmp_test.txt")
Expand Down
Loading