Current section
Files
Jump to
Current section
Files
lib/parser/parser.ex
defmodule Gitly.Parser do
@moduledoc """
A module to parse repository URLs and return metadata.
This module can handle various formats of repository URLs and convert them
into a standardized metadata map.
"""
@doc """
Parses a binary string and returns a metadata map.
The following strings are valid:
* owner/repo
* https://host.com/owner/repo
* https://host.com/owner/repo.git
* host.com/owner/repo
* host:owner/repo
## Parameters
* `str` - A binary string representing the repository URL.
## Returns
* `{:ok, map}` where the map contains:
* `:host` - the host of the repository
* `:owner` - the owner of the repository
* `:repo` - the repository name
* `:ref` - the reference (default is "main")
* `{:error, reason}` if the parsing fails.
## Examples
iex> Gitly.Parser.parse("iwatakeshi/gitly")
{:ok, %{host: "github.com", owner: "iwatakeshi", repo: "gitly", ref: "main"}}
iex> Gitly.Parser.parse("https://github.com/iwatakeshi/gitly")
{:ok, %{host: "github.com", owner: "iwatakeshi", repo: "gitly", ref: "main"}}
iex> Gitly.Parser.parse("github.com/iwatakeshi/gitly")
{:ok, %{host: "github.com", owner: "iwatakeshi", repo: "gitly", ref: "main"}}
iex> Gitly.Parser.parse("github:iwatakeshi/gitly")
{:ok, %{host: "github.com", owner: "iwatakeshi", repo: "gitly", ref: "main"}}
iex> Gitly.Parser.parse("blah")
{:error, "Invalid URL"}
"""
@spec parse(binary) :: {:ok, map} | {:error, String.t()}
def parse(str) when is_binary(str) do
parse(str, %{})
end
@doc """
Parses a binary string with additional options and returns a metadata map.
This function is similar to `parse/1` but allows specifying additional options.
## Parameters
* `str` - A binary string representing the repository URL.
* `opts` - A map of options:
* `:host` - the host of the repository (default is "github.com")
* `:ref` - the ref of the repository (default is "main")
## Returns
* `{:ok, map}` where the map contains the parsed metadata.
* `{:error, reason}` if the parsing fails.
## Examples
iex> Gitly.Parser.parse("iwatakeshi/gitly", %{host: "gitlab.com", ref: "develop"})
{:ok, %{host: "gitlab.com", owner: "iwatakeshi", repo: "gitly", ref: "develop"}}
"""
@spec parse(binary, map) :: {:ok, map} | {:error, String.t()}
def parse(str, opts)
when is_binary(str) and is_map(opts) do
cond do
# If the URL is an absolute URL
Regex.match?(~r/^https?:\/\//, str) ->
parse_absolute_url(str, opts)
# If the URL is an URL without a protocol
Regex.match?(~r/^[^\/]+\.[^\/]+\/.+/, str) ->
parse_absolute_url(str, opts)
# If the URL is in the format host:owner/repo
Regex.match?(~r/^[^:]+:.+\/.+/, str) ->
parse_host_colon_url(str, opts)
# If the URL is a shorthand URL
true ->
parse_shorthand_url(str, opts)
end
end
@doc false
defp parse_absolute_url(str, opts) do
str
|> remove_http()
|> remove_git_extension()
|> split()
|> case do
[host | rest] when length(rest) >= 2 ->
repo = List.last(rest)
owner_group_path = rest |> Enum.drop(-1) |> Enum.join("/")
result =
Map.put(opts, :host, host)
|> Map.put(:owner, owner_group_path)
|> Map.put(:repo, repo)
|> Map.put(:ref, opts[:ref] || "main")
{:ok, result}
_ ->
{:error, "Invalid URL"}
end
end
@doc false
defp parse_host_colon_url(str, opts) do
case String.split(str, ":") do
[host, owner_repo] ->
case String.split(owner_repo, "/") do
[owner, repo] ->
result =
Map.put(opts, :host, host <> ".com")
|> Map.put(:owner, owner)
|> Map.put(:repo, repo)
|> Map.put(:ref, opts[:ref] || "main")
{:ok, result}
_ ->
{:error, "Invalid URL"}
end
_ ->
{:error, "Invalid URL"}
end
end
@doc false
defp parse_shorthand_url(str, opts) do
parts = String.split(str, "/")
case parts do
[owner | rest] when length(rest) >= 1 ->
repo = Enum.join(rest, "/")
result =
Map.put(opts, :host, opts[:host] || "github.com")
|> Map.put(:owner, owner)
|> Map.put(:repo, repo)
|> Map.put(:ref, opts[:ref] || "main")
{:ok, result}
_ ->
{:error, "Invalid URL"}
end
end
@doc false
defp remove_http(str) do
Regex.replace(~r/^https?:\/\//, str, "")
end
@doc false
defp remove_git_extension(str) do
Regex.replace(~r/\.git$/, str, "")
end
@doc false
defp split(str), do: String.split(str, "/")
end