gemini-ex/lib/gemini.ex

102 lines
2.7 KiB
Elixir

defmodule Gemini do
@moduledoc """
Documentation for `Gemini`.
"""
@spec request(URI.t() | String.t()) :: {:ok, Gemini.Response.t()} | {:error, term()}
@doc """
Sends a Gemini request to the given URI.
"""
def request(uri) when is_binary(uri) do
request(URI.parse(uri))
end
def request(%URI{host: host} = uri) do
port = gemini_port(uri)
case Socket.SSL.connect(host, port) do
{:error, reason} ->
{:error, reason}
{:ok, sock} ->
Socket.Stream.send(sock, "#{URI.to_string(uri)}\r\n")
case Socket.Stream.recv(sock) do
{:error, reason} ->
{:error, reason}
{:ok, data} ->
Gemini.Response.parse(data)
end
end
end
defp gemini_port(%URI{scheme: "gemini", port: nil}), do: 1965
defp gemini_port(%URI{scheme: "gemini", port: port}), do: port
@type line ::
{:text, String.t()}
| {:link, URI.t(), String.t() | nil}
| {:preformatted, String.t(), String.t() | nil}
| {:heading, String.t(), 1 | 2 | 3}
| {:list_item, String.t()}
| {:quoted, String.t()}
@link_line_regex ~r/\s*([^\s]+)(?:\s+([^\s]+))?/
@spec parse(String.t()) :: [line()]
def parse(doc) do
{lines, _, _} =
doc
|> String.split("\n")
|> Enum.reduce({[], false, nil}, fn line, {lines, in_preformatting, preformatting_alt} ->
preformatting_toggle = match?("```" <> _, line)
cond do
preformatting_toggle && in_preformatting ->
{lines, false, nil}
preformatting_toggle && !in_preformatting ->
"```" <> alt = line
{lines, true, alt}
in_preformatting ->
{[{:preformatted, line, preformatting_alt} | lines], true, preformatting_alt}
true ->
case line do
"=>" <> rest ->
{link, text} =
case Regex.run(@link_line_regex, rest) do
[_, link] -> {link, nil}
[_, link, text] -> {link, text}
end
{[{:link, URI.parse(link), text} | lines], false, nil}
"###" <> rest ->
{[{:heading, String.trim(rest), 3} | lines], false, nil}
"##" <> rest ->
{[{:heading, String.trim(rest), 2} | lines], false, nil}
"#" <> rest ->
{[{:heading, String.trim(rest), 1} | lines], false, nil}
"* " <> rest ->
{[{:list_item, String.trim(rest)} | lines], false, nil}
">" <> rest ->
{[{:quoted, String.trim(rest)} | rest], false, nil}
line ->
{[{:text, line} | lines], false, nil}
end
end
end)
lines
end
end