Current section

Files

Jump to
xcribe lib api_blueprint formatter.ex
Raw

lib/api_blueprint/formatter.ex

defmodule Xcribe.ApiBlueprint.Formatter do
alias Xcribe.JSON
def resource_group(%{resource_group: name}),
do: ("## Group " <> String.upcase("#{name}\n")) |> remove_underline()
def resource(%{resource: resource, path: path}) do
resource_name = resource |> remove_underline() |> capitalize()
resource_path = build_path(path, last_argument: false)
"## #{resource_name} #{resource_path}\n"
end
def resource_parameters(struct, descriptions \\ %{})
def resource_parameters(%{path_params: params}, _desc) when params == %{}, do: ""
def resource_parameters(%{path_params: params, path: path}, desc) do
params
|> resource_path_parameters(path)
|> format_parameters(desc)
end
def action_parameters(struct, descriptions \\ %{})
def action_parameters(%{path_params: params}, _desc) when params == %{}, do: ""
def action_parameters(%{path_params: params, path: path}, desc) do
resourse_params = params |> resource_path_parameters(path) |> Map.keys()
params
|> Map.drop(resourse_params)
|> format_parameters(desc)
end
def resource_action(%{resource: resource, path: path, action: action, verb: verb}) do
resource_name = resource |> remove_underline() |> capitalize()
resource_path = build_path(path, verb: verb, last_argument: true)
resource_action = action |> remove_underline()
"### #{resource_name} #{resource_action} #{resource_path}\n"
end
def overview(api_info) do
"""
FORMAT: 1A
HOST: #{fetch_key(api_info, :host, "")}
# #{fetch_key(api_info, :name, "")}
#{fetch_key(api_info, :description, "")}
"""
end
def full_request(struct, desc \\ %{}) do
[
request_description(struct),
request_headers(struct),
request_attributes(struct, desc),
response_description(struct),
response_headers(struct),
response_body(struct)
]
|> Enum.join()
end
def request_description(%{description: description, header_params: headers}),
do: "+ Request #{clean_description(description)} (#{find_content_type(headers)})\n"
def request_headers(%{header_params: []}), do: ""
def request_headers(%{header_params: headers}), do: headers_section(headers)
def response_headers(%{resp_headers: []}), do: ""
def response_headers(%{resp_headers: headers}), do: headers_section(headers)
def request_body(%{request_body: body}) when body == %{}, do: ""
def request_body(%{request_body: body}), do: body_section(body)
def request_attributes(structs, descriptions \\ %{})
def request_attributes(%{request_body: body}, _desc) when body == %{}, do: ""
def request_attributes(%{request_body: body}, desc), do: attributes_section(body, desc)
def response_body(%{resp_body: body}) when body == %{}, do: ""
def response_body(%{resp_body: body}), do: body_section(body)
def response_description(%{status_code: code, resp_headers: headers}),
do: "+ Response #{code} (#{find_content_type(headers)})\n"
defp resource_path_parameters(params, path) do
ending_arg =
case Regex.run(~r/{(\w*)}$/, path) do
[_, arg] -> arg
_ -> nil
end
params
|> Map.delete(ending_arg)
end
defp format_parameters(params, _desc) when params == %{}, do: ""
defp format_parameters(params, desc) do
params_list =
params
|> define_schema(desc)
|> ident_lines(1)
"+ Parameters\n\n" <> params_list <> "\n"
end
defp remove_underline(text) when is_binary(text), do: String.replace(text, "_", " ")
defp remove_underline(other), do: other
defp find_content_type(nil), do: "text/plain"
defp find_content_type(headers) do
headers
|> Enum.reduce("text/plain", &find_content_type_header/2)
end
defp find_content_type_header({"content-type", type}, _acc), do: type
defp find_content_type_header(_, acc), do: acc
defp headers_section(headers) do
title = "+ Headers\n\n" |> apply_tab(1)
headers = format_headers(headers)
if headers == "", do: "", else: title <> headers <> "\n"
end
def body_section(body) do
title = "+ Body\n\n" |> apply_tab(1)
body = format_body(body)
title <> body <> "\n"
end
def attributes_section(attrs, desc) do
title = "+ Attributes\n\n" |> apply_tab(1)
body =
attrs
|> define_schema(desc, camelize: false, required: false, prefix: "+ ")
|> ident_lines(2)
title <> body <> "\n"
end
defp define_schema(params, desc, opts \\ []) do
opts = Keyword.merge([camelize: true, required: true, prefix: ""], opts)
camelize = Keyword.fetch!(opts, :camelize)
required = Keyword.fetch!(opts, :required)
prefix = Keyword.fetch!(opts, :prefix)
Enum.reduce(params, "", fn {key, value}, acc ->
param = if camelize, do: Macro.camelize("+ " <> key), else: key
type = if required, do: "required, #{type_of(value)}", else: "#{type_of(value)}"
description = desc |> fetch_key(key, "The #{key}") |> remove_underline()
value = remove_underline(value)
acc <> "#{prefix}#{param}: `#{value}` (#{type}) - #{description}\n"
end)
end
defp type_of(item) when is_number(item), do: "number"
defp type_of(item) when is_binary(item), do: "string"
defp type_of(item) when is_boolean(item), do: "boolean"
defp capitalize(string) do
string
|> String.split()
|> Enum.map(&String.capitalize/1)
|> Enum.join(" ")
end
defp build_path(path, opts) do
options = Keyword.merge([verb: nil, last_argument: true], opts)
verb = Keyword.fetch!(options, :verb)
last_argument = Keyword.fetch!(options, :last_argument)
path
|> add_forward_slash()
|> remove_ending_argument(!last_argument)
|> camelize_params()
|> into_brackets(verb)
end
defp add_forward_slash(path),
do: if(Regex.match?(~r/\/$/, path), do: path, else: "#{path}/")
defp into_brackets(path, nil), do: "[#{path}]"
defp into_brackets(path, verb), do: "[#{String.upcase(verb)} #{path}]"
defp remove_ending_argument(path, false), do: path
defp remove_ending_argument(path, true) do
case Regex.run(~r/({\w*}\/$)/, path) do
nil -> path
[capture | _] -> String.replace(path, capture, "")
end
end
defp camelize_params(path) do
~r/({\w*})/
|> Regex.run(path)
|> case do
nil -> []
p -> Enum.uniq(p)
end
|> Enum.reduce(path, fn param, text -> String.replace(text, param, Macro.camelize(param)) end)
end
defp clean_description(description) do
description |> String.replace(~r/\W/, " ") |> String.replace(~r/\s+/, " ")
end
defp ident_lines(text, count) do
text
|> String.split("\n")
|> Enum.map(fn l -> if(l == "", do: "", else: apply_tab(l, count)) end)
|> Enum.join("\n")
end
defp apply_tab(text, count),
do: 1..count |> Enum.reduce(text, &concat_tab/2)
defp concat_tab(_count, text), do: " " <> text
defp format_headers(headers), do: headers |> Enum.reduce("", &add_header/2)
defp add_header({"content-type", _}, acc), do: acc
defp add_header({key, value}, acc),
do: apply_tab("#{key}: #{value}\n", 3) <> acc
defp format_body(body) when is_binary(body),
do: body |> JSON.decode!() |> format_body()
defp format_body(body),
do: body |> JSON.encode!(pretty: true) |> ident_lines(3)
defp fetch_key(map, key, default) do
case Map.fetch(map, key) do
{:ok, value} -> value
_ -> default
end
end
end