defmodule CurlReq do @external_resource "README.md" @moduledoc @external_resource |> File.read!() |> String.split("") |> Enum.fetch!(1) @type inspect_opt :: {:label, String.t()} @doc """ Inspect a Req struct in curl syntax. Returns the unchanged `req`, just like `IO.inspect/2`. ## Examples iex> Req.new(url: URI.parse("https://www.google.com")) ...> |> CurlReq.inspect() ...> # |> Req.request!() """ @spec inspect(Req.Request.t(), [inspect_opt()]) :: Req.Request.t() def inspect(req, opts \\ []) do case Keyword.get(opts, :label) do nil -> IO.puts(to_curl(req)) label -> IO.puts([label, ": ", to_curl(req)]) end req end defp run_steps(req) do Enum.reduce(req.request_steps, req, fn {step_name, step}, req -> case step.(req) do {_req, _response_or_error} -> raise "The request was stopped by #{step_name} request_step." next_req -> next_req end end) end @doc """ Transforms a Req request into a curl command. Supported curl flags are: * `-b` * `-H` * `-X` * `-I` * `-d` ## Examples iex> Req.new(url: URI.parse("https://www.google.com")) ...> |> CurlReq.to_curl() ~S(curl -H "accept-encoding: gzip" -H "user-agent: req/0.4.14" -X GET https://www.google.com) """ @spec to_curl(Req.Request.t(), Keyword.t()) :: String.t() def to_curl(req, options \\ []) do req = if Keyword.get(options, :run_steps, true) do run_steps(req) else req end cookies = case Map.get(req.headers, "cookie") do nil -> [] [cookies] -> ["-b", cookies] end headers = req.headers |> Enum.reject(fn {key, _val} -> key == "cookie" end) |> Enum.flat_map(fn {key, value} -> ["-H", "#{key}: #{value}"] end) body = case req.body do nil -> [] body -> ["-d", body] end redirect = case req.options do %{redirect: true} -> ["-L"] _ -> [] end method = case req.method do nil -> ["-X", "GET"] :head -> ["-I"] m -> ["-X", String.upcase(to_string(m))] end url = [to_string(req.url)] CurlReq.Shell.cmd_to_string( "curl", headers ++ cookies ++ body ++ method ++ redirect ++ url ) end @doc """ Transforms a curl command into a Req request. Supported curl command line flags are supported: * `-H`/`--header` * `-X`/`--request` * `-d`/`--data` * `-b`/`--cookie` * `-I`/`--head` * `-F`/`--form` * `-L`/`--location` * `-u`/`--user` The `curl` command prefix is optional > #### Info {: .info} > > Only string inputs are supported. That means for example `-d @data.txt` will not load the file or `-d @-` will not read from stdin ## Examples iex> CurlReq.from_curl("curl https://www.google.com") %Req.Request{method: :get, url: URI.parse("https://www.google.com")} iex> ~S(curl -d "some data" https://example.com) |> CurlReq.from_curl() %Req.Request{method: :get, body: "some data", url: URI.parse("https://example.com")} iex> CurlReq.from_curl("curl -I https://example.com") %Req.Request{method: :head, url: URI.parse("https://example.com")} iex> CurlReq.from_curl("curl -b cookie_key=cookie_val https://example.com") %Req.Request{method: :get, headers: %{"cookie" => ["cookie_key=cookie_val"]}, url: URI.parse("https://example.com")} """ @doc since: "0.98.4" @spec from_curl(String.t()) :: Req.Request.t() def from_curl(curl_command), do: CurlReq.Macro.parse(curl_command) @doc """ Same as `from_curl/1` but as a sigil. The benefit here is, that the Req.Request struct will be created at compile time and you don't need to escape the string ## Examples iex> import CurlReq ...> ~CURL(curl "https://www.google.com") %Req.Request{method: :get, url: URI.parse("https://www.google.com")} iex> import CurlReq ...> ~CURL(curl -d "some data" "https://example.com") %Req.Request{method: :get, body: "some data", url: URI.parse("https://example.com")} iex> import CurlReq ...> ~CURL(curl -I "https://example.com") %Req.Request{method: :head, url: URI.parse("https://example.com")} iex> import CurlReq ...> ~CURL(curl -b "cookie_key=cookie_val" "https://example.com") %Req.Request{method: :get, headers: %{"cookie" => ["cookie_key=cookie_val"]}, url: URI.parse("https://example.com")} """ defmacro sigil_CURL(curl_command, modifiers) defmacro sigil_CURL({:<<>>, _line_info, [command]}, _extra) do command |> CurlReq.Macro.parse() |> Macro.escape() end end