102 lines
2.7 KiB
Elixir
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
|