Module: Exa::Internal::Util

Defined in:
lib/exa/internal/util.rb

Constant Summary collapse

JSON_CONTENT =
%r{application/(json|problem\+json)}i.freeze
JSONL_CONTENT =
%r{application/x-ndjson}i.freeze

Class Method Summary collapse

Class Method Details

.build_query(query) ⇒ Object



33
34
35
36
# File 'lib/exa/internal/util.rb', line 33

def build_query(query)
  return nil if query.nil? || query.empty?
  URI.encode_www_form(query)
end

.close_fused!(enum) ⇒ Object



126
127
128
129
130
# File 'lib/exa/internal/util.rb', line 126

def close_fused!(enum)
  if enum.respond_to?(:close)
    enum.close
  end
end

.decode_content(headers, stream:) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/exa/internal/util.rb', line 38

def decode_content(headers, stream:)
  case headers["content-type"]
  when JSON_CONTENT
    json = stream.to_a.join
    JSON.parse(json, symbolize_names: true)
  when JSONL_CONTENT
    stream.map { JSON.parse(_1, symbolize_names: true) }
  when /^text\/event-stream/
    decode_sse(stream)
  else
    StringIO.new(stream.to_a.join)
  end
end

.decode_lines(enum) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/exa/internal/util.rb', line 64

def decode_lines(enum)
  Enumerator.new do |y|
    buffer = String.new
    enum.each do |chunk|
      buffer << chunk
      while (idx = buffer.index(/\r?\n/))
        y << buffer.slice!(0..idx)
      end
    end
    y << buffer unless buffer.empty?
  end
end

.decode_sse(enum) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/exa/internal/util.rb', line 77

def decode_sse(enum)
  lines = decode_lines(enum)
  Enumerator.new do |y|
    event = {event: nil, data: String.new, id: nil, retry: nil}
    lines.each do |line|
      stripped = line.strip
      if stripped.empty?
        y << event.dup if event[:data]&.length&.positive?
        event = {event: nil, data: String.new, id: nil, retry: nil}
        next
      end

      case stripped
      when /^event:(.*)$/
        event[:event] = Regexp.last_match(1).strip
      when /^data:(.*)$/
        event[:data] << Regexp.last_match(1).lstrip << "\n"
      when /^id:(.*)$/
        event[:id] = Regexp.last_match(1).strip
      when /^retry:(\d+)$/
        event[:retry] = Regexp.last_match(1).to_i
      end
    end
  end
end

.deep_merge_hash(base, extra) ⇒ Object



22
23
24
25
26
27
28
29
30
31
# File 'lib/exa/internal/util.rb', line 22

def deep_merge_hash(base, extra)
  return base unless extra
  base.merge(extra) do |_k, old_val, new_val|
    if old_val.is_a?(Hash) && new_val.is_a?(Hash)
      deep_merge_hash(old_val, new_val)
    else
      new_val
    end
  end
end

.force_charset!(content_type, text:) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
# File 'lib/exa/internal/util.rb', line 52

def force_charset!(content_type, text:)
  return text unless content_type
  return text if text.encoding == Encoding::UTF_8
  if (match = /charset=([^;]+)/i.match(content_type))
    encoding = Encoding.find(match[1])
    text.force_encoding(encoding)
  end
  text
rescue ArgumentError
  text
end

.fused_enum(enum, &on_close) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/exa/internal/util.rb', line 103

def fused_enum(enum, &on_close)
  closed = false
  wrapper = Enumerator.new do |y|
    begin
      enum.each { y << _1 }
    ensure
      unless closed
        closed = true
        on_close&.call
      end
    end
  end

  wrapper.define_singleton_method(:close) do
    unless closed
      closed = true
      on_close&.call
    end
  end

  wrapper
end

.normalized_headers(headers) ⇒ Object



15
16
17
18
19
20
# File 'lib/exa/internal/util.rb', line 15

def normalized_headers(headers)
  headers.each_with_object({}) do |(key, value), acc|
    next if value.nil?
    acc[key.to_s.downcase] = value.to_s
  end
end