Skip to content

Commit c545006

Browse files
committed
add url helpers
1 parent 2dde0e7 commit c545006

File tree

1 file changed

+96
-0
lines changed

1 file changed

+96
-0
lines changed

lib/algora/shared/util.ex

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
defmodule Algora.Util do
22
@moduledoc false
3+
def to_csv(data, output_path) do
4+
rows = Enum.to_list(data)
5+
6+
# Collect all unique keys across all entries
7+
all_keys =
8+
rows
9+
|> Enum.flat_map(&Map.keys/1)
10+
|> Enum.uniq()
11+
|> Enum.sort()
12+
13+
# Normalize each row to have all fields
14+
normalized_rows =
15+
Enum.map(rows, fn row ->
16+
Enum.map(all_keys, fn key ->
17+
case Map.get(row, key) do
18+
list when is_list(list) -> Enum.join(list, ", ")
19+
value -> value
20+
end
21+
end)
22+
end)
23+
24+
[all_keys | normalized_rows]
25+
|> CSV.encode()
26+
|> Enum.join("")
27+
|> then(&File.write!(output_path, &1))
28+
end
29+
330
def random_string do
431
binary = <<
532
System.system_time(:nanosecond)::64,
@@ -149,6 +176,7 @@ defmodule Algora.Util do
149176
def format_name_list([x1, x2]), do: "#{x1} and #{x2}"
150177
def format_name_list([x1, x2, x3]), do: "#{x1}, #{x2} and #{x3}"
151178
def format_name_list([x1, x2 | xs]), do: "#{x1}, #{x2} and #{length(xs)} others"
179+
def format_name_list(_), do: nil
152180

153181
def initials(str, length \\ 2)
154182
def initials(nil, _length), do: ""
@@ -196,6 +224,74 @@ defmodule Algora.Util do
196224
|> String.trim_leading("www.")
197225
end
198226

227+
@doc """
228+
Extracts the root domain from a hostname, removing subdomains.
229+
230+
Examples:
231+
iex> extract_root_domain("www.example.com")
232+
"example.com"
233+
234+
iex> extract_root_domain("app.dexory.com")
235+
"dexory.com"
236+
237+
iex> extract_root_domain("example.com")
238+
"example.com"
239+
"""
240+
def extract_root_domain(nil), do: nil
241+
242+
def extract_root_domain(host) when is_binary(host) do
243+
host
244+
|> String.trim()
245+
|> String.downcase()
246+
|> then(fn h ->
247+
# Split by dots
248+
parts = String.split(h, ".")
249+
250+
# If we have more than 2 parts, take the last 2 (domain + TLD)
251+
# For most cases, this works (www.example.com -> example.com)
252+
# For edge cases like .co.uk, we'd need a public suffix list
253+
case length(parts) do
254+
n when n > 2 ->
255+
# Take last 2 parts
256+
parts
257+
|> Enum.take(-2)
258+
|> Enum.join(".")
259+
260+
_ ->
261+
# Already a root domain or invalid
262+
h
263+
end
264+
end)
265+
end
266+
267+
@doc """
268+
Extracts the root domain from a URL, removing scheme, path, and subdomains.
269+
270+
Examples:
271+
iex> url_to_root_domain("https://duckduckgo.com/careers")
272+
"duckduckgo.com"
273+
274+
iex> url_to_root_domain("http://bsky.social/about/join")
275+
"bsky.social"
276+
277+
iex> url_to_root_domain("jobs.ongoody.com/")
278+
"ongoody.com"
279+
280+
iex> url_to_root_domain("https://foxglove.dev/")
281+
"foxglove.dev"
282+
"""
283+
def url_to_root_domain(nil), do: nil
284+
285+
def url_to_root_domain(url) when is_binary(url) do
286+
# Add scheme if missing for proper URI parsing
287+
url_with_scheme = if String.contains?(url, "://"), do: url, else: "https://#{url}"
288+
289+
url_with_scheme
290+
|> URI.parse()
291+
|> then(fn uri -> uri.host end)
292+
|> extract_root_domain()
293+
end
294+
199295
def get_gravatar_url(email, opts \\ []) do
200296
default = Keyword.get(opts, :default, "")
201297
size = Keyword.get(opts, :size, 460)

0 commit comments

Comments
 (0)