• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

camatcode / basenji / ab50d6f56050b932de65618c18e8ebcab2472c56

27 Jul 2025 02:55PM UTC coverage: 81.44% (-0.3%) from 81.695%
ab50d6f56050b932de65618c18e8ebcab2472c56

Pull #72

github

camatcode
perf: introduce command/query telemetry
Pull Request #72: perf: introduce telemetry

83 of 96 new or added lines in 13 files covered. (86.46%)

2 existing lines in 1 file now uncovered.

1154 of 1417 relevant lines covered (81.44%)

492.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.47
/lib/basenji/reader/pdf_reader.ex
1
defmodule Basenji.Reader.PDFReader do
2
  @moduledoc false
3
  use Basenji.TelemetryHelpers
4

5
  import Basenji.Reader
6

7
  def format, do: :pdf
4✔
8

9
  def file_extensions, do: ["pdf"]
789✔
10

11
  def get_magic_numbers, do: [%{offset: 0, magic: [0x25, 0x50, 0x44, 0x46, 0x2D]}]
787✔
12

13
  def close(_any), do: :ok
5✔
14

15
  def get_entries(pdf_file_path, _opts \\ []) do
16
    with {:ok, %{pages: pages}} <- get_metadata(pdf_file_path) do
790✔
17
      padding = String.length("#{pages}")
789✔
18

19
      file_entries =
789✔
20
        1..pages
21
        |> Enum.map(fn idx ->
22
          %{file_name: "#{String.pad_leading("#{idx}", padding, "0")}.jpg"}
3,156✔
23
        end)
24

25
      {:ok, %{entries: file_entries}}
26
    end
27
  end
28

29
  def get_entry_stream!(pdf_file_path, entry) do
30
    file_name = entry[:file_name]
53✔
31
    {page_num, _rest} = Integer.parse(file_name)
53✔
32

33
    create_resource(fn ->
53✔
34
      with {:ok, output} <- exec("pdftoppm", ["-f", "#{page_num}", "-singlefile", "-jpeg", "-q", pdf_file_path]) do
53✔
35
        [output |> :binary.bin_to_list()]
36
      end
37
    end)
38
  end
39

40
  def read(pdf_file_path, _opts \\ []) do
41
    telemetry_wrap [:basenji, :process], %{action: "read_pdf"} do
788✔
NEW
42
      with {:ok, %{entries: file_entries}} <- get_entries(pdf_file_path) do
×
43
        file_entries =
788✔
44
          file_entries
45
          |> Enum.map(fn entry ->
46
            entry
47
            |> Map.put(:stream_fun, fn -> get_entry_stream!(pdf_file_path, entry) end)
3,152✔
48
          end)
49

50
        {:ok, %{entries: file_entries}}
51
      end
52
    end
53
  end
54

55
  def get_metadata(pdf_file_path) do
56
    with {:ok, output} <- exec("pdfinfo", ["-isodates", pdf_file_path]) do
790✔
57
      metadata =
789✔
58
        String.split(output, "\n")
59
        |> Map.new(fn line ->
60
          String.split(line, ":", parts: 2)
61
          |> case do
14,202✔
62
            [k, v] -> to_metadata(k, v)
14,202✔
63
            [v] -> to_metadata("unknown_#{System.monotonic_time()}", v)
×
64
          end
65
        end)
66

67
      {:ok, metadata}
68
    end
69
  end
70

71
  defp to_metadata(k, v) do
72
    k = k |> String.trim() |> ProperCase.snake_case() |> String.to_atom()
14,202✔
73
    v = convert_value(k, v |> String.trim())
14,202✔
74
    {k, v}
75
  end
76

77
  defp convert_value(:creation_date, v), do: DateTimeParser.parse!(v)
789✔
78
  defp convert_value(:mod_date, v), do: DateTimeParser.parse!(v)
789✔
79
  defp convert_value(:pages, v), do: String.to_integer(v)
789✔
80

81
  defp convert_value(:filesize, v) do
82
    {first, _rest} = Integer.parse(v)
789✔
83
    first
789✔
84
  end
85

86
  defp convert_value(:pagesize, v) do
87
    String.split(v, " ")
88
    |> Enum.reduce([], fn part, acc ->
89
      Integer.parse(part)
90
      |> case do
3,156✔
91
        {coord, _rest} -> [coord | acc]
1,578✔
92
        _ -> acc
1,578✔
93
      end
94
    end)
95
    |> case do
789✔
96
      [x, y] -> {x, y}
789✔
97
      _ -> v
×
98
    end
99
  end
100

101
  defp convert_value(_k, "yes"), do: true
×
102
  defp convert_value(_k, "no"), do: false
6,312✔
103
  defp convert_value(_k, "none"), do: nil
789✔
104
  defp convert_value(_k, v), do: v
3,156✔
105
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc