Skip to content

Commit

Permalink
add expand task
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinschweikert committed Jan 10, 2025
1 parent dab1f5e commit 298c5b9
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
107 changes: 107 additions & 0 deletions lib/mix/tasks/curl_req/expand.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
defmodule Mix.Tasks.CurlReq.Expand do
use Mix.Task

def run(_args) do
files =
"lib/**/*.{ex,exs}"
|> Path.wildcard(match_dot: true)
|> Enum.filter(&File.regular?/1)

yes =
"""
Should we expand the ~CURL() in the following files?
#{Enum.join(files, "\n")}
"""
|> Mix.shell().yes?()

if yes do
for file <- files do
expand_file(file)
end
else
Mix.shell().info("Operation aborted")
end
end

defp expand(source) when is_binary(source) do
{_quoted, patches} =
source
|> Sourceror.parse_string!()
|> Macro.postwalk([], fn
{:sigil_CURL, _dot_meta, [{:<<>>, _, _} | _] = _rest} = quoted, patches ->
{:ok, env} = __ENV__ |> Macro.Env.define_import([line: 2], CurlReq)

Check warning on line 33 in lib/mix/tasks/curl_req/expand.ex

View workflow job for this annotation

GitHub Actions / Elixir 1.15 - OTP 25.3.2.12

Macro.Env.define_import/3 is undefined or private
# TODO: get range when Sourceror.get_range/1 returns nil
if range = Sourceror.get_range(quoted) do
expanded = Macro.expand_once(quoted, env)
reconstructed = reconstruct(expanded)
replacement = reconstructed |> Sourceror.to_string()
patch = %{range: range, change: replacement}
{quoted, [patch | patches]}
else
{quoted, patches}
end

quoted, patches ->
{quoted, patches}
end)

Sourceror.patch_string(source, patches)
end

defp expand_file(filename) do
patched_code =
filename
|> File.read!()
|> expand()

File.write!(filename, patched_code)
end

# Walks and transforms the AST so that
# %{__struct__: Some.Module, key: val, ...}
# becomes
# %Some.Module{key: val, ...}.

defp reconstruct(ast) do
Macro.postwalk(ast, fn
# Match on a literal map: {:%{}, meta, fields}
{:%{}, meta, fields} = node ->
case Keyword.pop(fields, :__struct__) do
{nil, _} ->
# It's just a normal map, not a struct
node

{mod, rest} ->
# We found __struct__: mod
# Turn that into:
#
# {:%, meta, [
# alias_ast_for(mod),
# {:%{}, meta, rest}
# ]}
#
{:%, meta,
[
module_to_alias_ast(mod),
{:%{}, meta, rest}
]}
end

other ->
other
end)
end

# Converts an atom module (e.g. Req.Request)
# into an AST alias: {:__aliases__, [alias: false], [:Req, :Request]}
defp module_to_alias_ast(mod) when is_atom(mod) do
mod
# ["Foo", "Bar", "Baz"]
|> Module.split()
|> Enum.map(&String.to_atom/1)
|> then(fn aliases ->
{:__aliases__, [alias: false], aliases}
end)
end
end
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ defmodule CurlReq.MixProject do
[
{:req, "~> 0.4.0 or ~> 0.5.0"},
{:jason, "~> 1.4"},
{:sourceror, "~> 1.7"},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:blend, "~> 0.4.1", only: :dev}
]
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"},
"sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
}

0 comments on commit 298c5b9

Please sign in to comment.