gemini-ex/lib/gemini.ex

117 lines
3.0 KiB
Elixir
Raw Normal View History

2020-07-18 19:40:47 +00:00
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")
2020-07-18 20:27:40 +00:00
sock
|> receive_loop()
|> Gemini.Response.parse()
end
end
defp receive_loop(sock, acc \\ []) do
res = Socket.Stream.recv(sock)
case res do
{:ok, nil} ->
Socket.Stream.close!(sock)
acc
|> Enum.reverse()
|> IO.iodata_to_binary()
2020-07-18 19:40:47 +00:00
2020-07-18 20:27:40 +00:00
{:ok, data} ->
receive_loop(sock, [data | acc])
2020-07-18 19:40:47 +00:00
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_start, String.t() | nil}
| :preformatted_end
| {:preformatted, String.t()}
2020-07-18 19:40:47 +00:00
| {:heading, String.t(), 1 | 2 | 3}
| {:list_item, String.t()}
| {:quoted, String.t()}
2020-07-19 03:00:51 +00:00
@link_line_regex ~r/\s*([^\s]+)(?:\s+(.+))?/
2020-07-18 19:40:47 +00:00
@spec parse(String.t()) :: [line()]
def parse(doc) do
2020-07-19 03:00:51 +00:00
{lines, _} =
2020-07-18 19:40:47 +00:00
doc
|> String.split("\n")
|> Enum.reduce({[], false}, fn line, {lines, in_preformatting} ->
2020-07-18 19:40:47 +00:00
preformatting_toggle = match?("```" <> _, line)
cond do
preformatting_toggle && in_preformatting ->
{[:preformatting_end | lines], false}
2020-07-18 19:40:47 +00:00
preformatting_toggle && !in_preformatting ->
"```" <> alt = line
alt = if length(alt) == 0, do: nil, else: alt
{[{:preformatting_start, alt} | lines], true}
2020-07-18 19:40:47 +00:00
in_preformatting ->
{[{:preformatted, line} | lines], true}
2020-07-18 19:40:47 +00:00
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}
2020-07-18 19:40:47 +00:00
"###" <> rest ->
{[{:heading, String.trim(rest), 3} | lines], false}
2020-07-18 19:40:47 +00:00
"##" <> rest ->
{[{:heading, String.trim(rest), 2} | lines], false}
2020-07-18 19:40:47 +00:00
"#" <> rest ->
{[{:heading, String.trim(rest), 1} | lines], false}
2020-07-18 19:40:47 +00:00
"* " <> rest ->
{[{:list_item, String.trim(rest)} | lines], false}
2020-07-18 19:40:47 +00:00
">" <> rest ->
{[{:quoted, String.trim(rest)} | lines], false}
2020-07-18 19:40:47 +00:00
line ->
{[{:text, line} | lines], false}
2020-07-18 19:40:47 +00:00
end
end
end)
2020-07-19 03:08:19 +00:00
Enum.reverse(lines)
2020-07-18 19:40:47 +00:00
end
end