|
1 | 1 | defmodule Algora.Util do |
2 | 2 | @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 | + |
3 | 30 | def random_string do |
4 | 31 | binary = << |
5 | 32 | System.system_time(:nanosecond)::64, |
@@ -149,6 +176,7 @@ defmodule Algora.Util do |
149 | 176 | def format_name_list([x1, x2]), do: "#{x1} and #{x2}" |
150 | 177 | def format_name_list([x1, x2, x3]), do: "#{x1}, #{x2} and #{x3}" |
151 | 178 | def format_name_list([x1, x2 | xs]), do: "#{x1}, #{x2} and #{length(xs)} others" |
| 179 | + def format_name_list(_), do: nil |
152 | 180 |
|
153 | 181 | def initials(str, length \\ 2) |
154 | 182 | def initials(nil, _length), do: "" |
@@ -196,6 +224,74 @@ defmodule Algora.Util do |
196 | 224 | |> String.trim_leading("www.") |
197 | 225 | end |
198 | 226 |
|
| 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 | + |
199 | 295 | def get_gravatar_url(email, opts \\ []) do |
200 | 296 | default = Keyword.get(opts, :default, "") |
201 | 297 | size = Keyword.get(opts, :size, 460) |
|
0 commit comments