Skip to content

Commit ca61d2b

Browse files
committed
Refactor chunk_extractor api
simplify the logic, get rid of direction parameter now it only tries to retrieve the chunk from the top to the down got rid of direction param and custom chunk.sh script use shell sed command
1 parent 5af516a commit ca61d2b

File tree

4 files changed

+87
-128
lines changed

4 files changed

+87
-128
lines changed

lib/diff/hex/chunk_extractor.ex

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
defmodule Diff.Hex.ChunkExtractor do
2-
@enforce_keys [:file_path, :from_line, :lines_to_read, :direction]
3-
defstruct Enum.map(@enforce_keys, &{&1, nil}) ++
4-
[
5-
errors: [],
6-
raw: nil,
7-
parsed: nil
8-
]
2+
@enforce_keys [:file_path, :from_line, :to_line, :right_line]
3+
defstruct Enum.map(@enforce_keys, &{&1, nil}) ++ [errors: [], raw: nil, parsed: nil]
94

105
def run(params) do
11-
__MODULE__
12-
|> struct!(params)
13-
|> parse_integers([:from_line, :lines_to_read])
14-
|> validate_direction()
6+
params
7+
|> cast()
8+
|> parse_integers([:right_line, :from_line, :to_line])
9+
|> normalize([:from_line, :right_line])
10+
|> validate_to_line()
1511
|> system_read_raw_chunk()
1612
|> parse_chunk()
17-
|> remove_trailing_newline()
13+
|> case do
14+
%{errors: []} = chunk -> {:ok, chunk}
15+
%{errors: _} = chunk -> {:error, chunk}
16+
end
17+
end
18+
19+
defp cast(params) do
20+
struct_keys =
21+
struct(__MODULE__)
22+
|> Map.from_struct()
23+
|> Enum.map(fn {key, _val} -> key end)
24+
25+
struct!(__MODULE__, Map.take(params, struct_keys))
1826
end
1927

2028
defp parse_integers(chunk, fields) do
@@ -25,7 +33,7 @@ defmodule Diff.Hex.ChunkExtractor do
2533
value = chunk |> Map.get(field) |> parse_value()
2634

2735
case value do
28-
:error -> %{chunk | errors: {:parse_integer, "#{field} must be a number"}}
36+
:error -> %{chunk | errors: [{:parse_integer, "#{field} must be a number"} | chunk.errors]}
2937
integer -> Map.put(chunk, field, integer)
3038
end
3139
end
@@ -36,26 +44,39 @@ defmodule Diff.Hex.ChunkExtractor do
3644
with {int, _} <- Integer.parse(number), do: int
3745
end
3846

39-
defp validate_direction(%{direction: direction} = chunk) when direction in ["up", "down"] do
47+
defp normalize(%{errors: [_ | _]} = chunk, _), do: chunk
48+
49+
defp normalize(chunk, fields) do
50+
Enum.reduce(fields, chunk, &normalize_field/2)
51+
end
52+
53+
defp normalize_field(field, chunk) do
54+
case Map.fetch!(chunk, field) do
55+
value when value > 0 -> chunk
56+
_ -> Map.put(chunk, field, 1)
57+
end
58+
end
59+
60+
defp validate_to_line(%{errors: [_ | _]} = chunk), do: chunk
61+
62+
defp validate_to_line(%{from_line: from_line, to_line: to_line} = chunk)
63+
when from_line < to_line do
4064
chunk
4165
end
4266

43-
defp validate_direction(chunk) do
44-
error = {:direction, "direction must be either \"up\" or \"down\""}
67+
defp validate_to_line(chunk) do
68+
error = {:param, "from_line parameter must be less than to_line"}
4569
%{chunk | errors: [error | chunk.errors]}
4670
end
4771

4872
defp system_read_raw_chunk(%{errors: [_ | _]} = chunk), do: chunk
4973

5074
defp system_read_raw_chunk(chunk) do
51-
chunk_sh = Application.app_dir(:diff, ["priv", "chunk.sh"])
52-
5375
path = chunk.file_path
5476
from_line = to_string(chunk.from_line)
55-
lines_to_read = to_string(chunk.lines_to_read)
56-
direction = chunk.direction
77+
to_line = to_string(chunk.to_line)
5778

58-
case System.cmd(chunk_sh, [path, from_line, lines_to_read, direction], stderr_to_stdout: true) do
79+
case System.cmd("sed", ["-n", "#{from_line},#{to_line}p", path], stderr_to_stdout: true) do
5980
{raw_chunk, 0} ->
6081
%{chunk | raw: raw_chunk}
6182

@@ -71,42 +92,21 @@ defmodule Diff.Hex.ChunkExtractor do
7192
parsed =
7293
chunk.raw
7394
|> String.split("\n")
74-
|> Enum.map(fn line -> %{line_text: line} end)
75-
76-
set_line_numbers(%{chunk | parsed: parsed})
95+
|> remove_trailing_newline()
96+
|> Enum.with_index(chunk.from_line)
97+
|> Enum.with_index(chunk.right_line)
98+
# prepend line with whitespace to compensate the space introduced by leading + and - in diffs
99+
|> Enum.map(fn {{line, from}, to} ->
100+
%{text: " " <> line, from_line_number: from, to_line_number: to}
101+
end)
102+
103+
%{chunk | parsed: parsed}
77104
end
78105

79-
defp set_line_numbers(%{direction: "down"} = chunk) do
80-
%{chunk | parsed: parsed_with_line_numbers(chunk.parsed, chunk.from_line)}
81-
end
82-
83-
defp set_line_numbers(%{direction: "up"} = chunk) do
84-
offset = chunk.from_line - length(chunk.parsed) + 1
85-
86-
%{chunk | parsed: parsed_with_line_numbers(chunk.parsed, offset)}
87-
end
88-
89-
defp parsed_with_line_numbers(parsed_chunk, starting_number) when is_binary(starting_number) do
90-
parsed_with_line_numbers(parsed_chunk, starting_number)
91-
end
92-
93-
defp parsed_with_line_numbers(parsed_chunk, starting_number) do
94-
parsed_chunk
95-
|> Enum.with_index(starting_number)
96-
|> Enum.map(fn {line, line_number} -> Map.put_new(line, :line_number, line_number) end)
97-
end
98-
99-
defp remove_trailing_newline(%{errors: [_ | _]} = chunk), do: {:error, chunk}
100-
101-
defp remove_trailing_newline(chunk) do
102-
[trailing_line | reversed_tail] = Enum.reverse(chunk.parsed)
103-
104-
chunk =
105-
case trailing_line do
106-
%{line_text: ""} -> %{chunk | parsed: Enum.reverse(reversed_tail)}
107-
%{line_text: _text} -> chunk
108-
end
109-
110-
{:ok, chunk}
106+
defp remove_trailing_newline(lines) do
107+
lines
108+
|> Enum.reverse()
109+
|> tl()
110+
|> Enum.reverse()
111111
end
112112
end

lib/diff/hex/hex.ex

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,14 @@ defmodule Diff.Hex do
101101
end
102102
end
103103

104-
def get_chunk(package, version, file_name, from_line, direction) do
105-
path = tmp_path("package-#{package}-#{version}-")
104+
def get_chunk(params) do
105+
path = tmp_path("chunk")
106106

107-
chunk_extractor_params = %{
108-
file_path: Path.join(path, file_name),
109-
from_line: from_line,
110-
direction: direction,
111-
lines_to_read: 20
112-
}
107+
chunk_extractor_params = Map.put(params, :file_path, Path.join(path, params.file_name))
113108

114109
try do
115-
with {:ok, tarball} <- get_tarball(package, version),
116-
:ok <- unpack_tarball(tarball, [file_name], path),
110+
with {:ok, tarball} <- get_tarball(params.package, params.version),
111+
:ok <- unpack_tarball(tarball, [params.file_name], path),
117112
{:ok, %{parsed: parsed_chunk}} <- Diff.Hex.ChunkExtractor.run(chunk_extractor_params) do
118113
{:ok, parsed_chunk}
119114
else

priv/chunk.sh

Lines changed: 0 additions & 21 deletions
This file was deleted.

test/diff/hex/chunk_extractor_test.exs

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,72 +17,57 @@ defmodule Diff.Hex.ChunkExtractorTest do
1717
on_exit(fn -> File.rm!(path) end)
1818

1919
# some deafult params
20-
%{params: %{file_path: path, lines_to_read: 2, from_line: 1, direction: "down"}}
21-
end
22-
23-
describe "validates direction" do
24-
test "down", %{params: params} do
25-
{:ok, %{errors: errors}} = ChunkExtractor.run(%{params | direction: "down"})
26-
assert [] = errors
27-
end
28-
29-
test "up", %{params: params} do
30-
{:ok, %{errors: errors}} = ChunkExtractor.run(%{params | direction: "up"})
31-
assert [] = errors
32-
end
33-
34-
test "error when direction is neither up nor down", %{params: params} do
35-
{:error, %{errors: errors}} = ChunkExtractor.run(%{params | direction: "left"})
36-
assert "direction must be either \"up\" or \"down\"" = Keyword.get(errors, :direction)
37-
end
20+
%{
21+
params: %{file_path: path, right_line: 1, from_line: 1, to_line: 2}
22+
}
3823
end
3924

4025
describe "reads raw chunk from the file_path" do
4126
test "reads first 2 lines down", %{params: params} do
42-
{:ok, %{raw: raw}} = ChunkExtractor.run(%{params | direction: "down"})
27+
{:ok, %{raw: raw}} = ChunkExtractor.run(%{params | from_line: 1, to_line: 2})
4328
assert "foo 1\nbar 2\n" = raw
4429
end
4530

46-
test "reads first 2 lines up", %{params: params} do
47-
{:ok, %{raw: raw}} = ChunkExtractor.run(%{params | direction: "up", from_line: 2})
48-
assert "foo 1\nbar 2" = raw
49-
end
50-
5131
test "error when file doesn't exist", %{params: params} do
5232
{:error, %{errors: errors}} = ChunkExtractor.run(%{params | file_path: "non_existent"})
5333
assert Keyword.get(errors, :system) =~ ~r/non_existent: No such file/
5434
end
5535

56-
test "error when arguments are not valid", %{params: params} do
57-
{:error, %{errors: errors}} = ChunkExtractor.run(%{params | from_line: -1})
58-
assert Keyword.get(errors, :system) =~ ~r/illegal offset/
36+
test "returns from 1 when from_line is negative", %{params: params} do
37+
{:ok, %{parsed: actual}} =
38+
ChunkExtractor.run(%{params | from_line: -2, right_line: -2, to_line: 2})
39+
40+
assert [
41+
%{from_line_number: 1, to_line_number: 1},
42+
%{from_line_number: 2, to_line_number: 2}
43+
] = actual
5944
end
6045

61-
test "reads 2 lines up from the middle", %{params: params} do
62-
{:ok, %{raw: raw}} = ChunkExtractor.run(%{params | direction: "up", from_line: 3})
63-
assert "bar 2\nbaz 3" = raw
46+
test "error when arguments are not valid", %{params: params} do
47+
{:error, %{errors: errors}} = ChunkExtractor.run(%{params | from_line: -4, to_line: -3})
48+
assert Keyword.get(errors, :param) == "from_line parameter must be less than to_line"
6449
end
6550

66-
test "reads 2 lines down from the middle", %{params: params} do
67-
{:ok, %{raw: raw}} = ChunkExtractor.run(%{params | direction: "down", from_line: 2})
68-
assert "bar 2\nbaz 3\n" = raw
51+
test "reads 2 lines from the middle", %{params: params} do
52+
{:ok, %{raw: raw}} = ChunkExtractor.run(%{params | from_line: 2, to_line: 4})
53+
assert "bar 2\nbaz 3\nbaf 4\n" = raw
6954
end
7055
end
7156

7257
describe "parse_chunk" do
7358
test "parses raw chunk into list of structs", %{params: params} do
7459
{:ok, %{parsed: actual}} = ChunkExtractor.run(params)
75-
assert [%{line_text: "foo 1"}, %{line_text: "bar 2"}] = actual
60+
assert [%{text: " foo 1"}, %{text: " bar 2"}] = actual
7661
end
7762

78-
test "sets line_numbers when direction is down", %{params: params} do
79-
{:ok, %{parsed: actual}} = ChunkExtractor.run(%{params | direction: "down", from_line: 2})
80-
assert [%{line_number: 2}, %{line_number: 3}] = actual
81-
end
63+
test "sets line_numbers", %{params: params} do
64+
{:ok, %{parsed: actual}} =
65+
ChunkExtractor.run(%{params | from_line: 2, right_line: 1, to_line: 3})
8266

83-
test "sets line_numbers when direction is up", %{params: params} do
84-
{:ok, %{parsed: actual}} = ChunkExtractor.run(%{params | direction: "up", from_line: 3})
85-
assert [%{line_number: 2}, %{line_number: 3}] = actual
67+
assert [
68+
%{from_line_number: 2, to_line_number: 1},
69+
%{from_line_number: 3, to_line_number: 2}
70+
] = actual
8671
end
8772
end
8873
end

0 commit comments

Comments
 (0)