Forked from andreaseriksson/convert_to_verified_routes.ex
Created
June 16, 2023 21:44
-
-
Save fastjames/e99cedfa132df03eeb1144c56020afe6 to your computer and use it in GitHub Desktop.
This is a mix task for converting old Phoenix routes to new verified routes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Mix.Tasks.ConvertToVerifiedRoutes do | |
@shortdoc "Fix routes" | |
use Mix.Task | |
@regex ~r/(Routes\.)(.*)_(path|url)\(.*?\)/ | |
@web_module MyAppWeb | |
def run(_) do | |
Path.wildcard("lib/**/*.*ex") | |
|> Enum.concat(Path.wildcard("test/**/*.*ex*")) | |
|> Enum.sort() | |
|> Enum.filter(&(File.read!(&1) |> String.contains?("Routes."))) | |
|> Enum.reduce(%{}, fn filename, learnings -> | |
test_filename(filename, learnings) | |
end) | |
:ok | |
end | |
def test_filename(filename, learnings) do | |
Mix.shell().info(filename) | |
content = File.read!(filename) | |
case replace_content(content, learnings) do | |
{:ok, content, learnings} -> | |
File.write!(filename, content) | |
learnings | |
_ -> | |
learnings | |
end | |
end | |
def replace_content(content, learnings) do | |
case Regex.run(@regex, content) do | |
[route|_] -> ask_about_replacement(content, route, learnings) | |
_ -> {:ok, content, learnings} | |
end | |
end | |
def ask_about_replacement(content, route, learnings) do | |
route = String.trim(route) | |
if verified_route = find_verified_route_from_string(route) do | |
replacement = Map.get(learnings, route) || ask_for_direct_match(route, verified_route) || ask_for_fallback(route, verified_route) | |
if replacement && (String.starts_with?(replacement, "~p\"/") || String.starts_with?(replacement, "url")) do | |
replace_content( | |
String.replace(content, route, replacement), | |
Map.put(learnings, route, replacement) | |
) | |
end | |
else | |
{:ok, content, learnings} | |
end | |
end | |
def ask_for_fallback(route, _verified_route) do | |
response = Mix.shell().prompt( | |
""" | |
What is the verified route for (type "skip" for skipping): | |
#{IO.ANSI.red}#{route}#{IO.ANSI.reset} | |
Start with: | |
~p"/.. | |
""") | |
response = String.trim("#{response}") | |
response != "" && response | |
end | |
def ask_for_direct_match(route, verified_route) do | |
if Mix.shell().yes?( | |
""" | |
Should we replace | |
#{IO.ANSI.red}#{route}#{IO.ANSI.reset} | |
with | |
#{IO.ANSI.green}#{verified_route}#{IO.ANSI.reset} | |
""") do | |
verified_route | |
end | |
end | |
def find_verified_route_from_string(route) do | |
parts = | |
route | |
|> String.replace("Routes.", "") | |
|> String.split("(") | |
with [route_helper, arguments|_] <- parts do | |
arguments = | |
arguments | |
|> String.replace_trailing(")", "") | |
|> String.split(",") | |
|> Enum.map(&String.trim/1) | |
@web_module.Router.__routes__() | |
|> Enum.find(fn %{helper: helper, plug_opts: plug_opts} -> | |
is_atom(plug_opts) && | |
Enum.member?(arguments, ":#{plug_opts}") && | |
String.starts_with?(route_helper, helper) | |
end) | |
|> case do | |
%{path: "" <> path} -> | |
path = interpolate_path_with_vars(path, arguments) | |
path = maybe_add_params(path, arguments) | |
if String.contains?(route_helper, "_url") do | |
~s[url(~p"#{path}")] | |
else | |
~s(~p"#{path}") | |
end | |
_ -> | |
nil | |
end | |
end | |
end | |
def interpolate_path_with_vars(path, arguments) do | |
arguments = Enum.slice(arguments, 2..10) | |
path | |
|> String.split("/") | |
|> Enum.filter(&String.starts_with?(&1, ":")) | |
|> Enum.with_index() | |
|> Enum.reduce(path, fn {slot, idx}, memo -> | |
argument = Enum.at(arguments, idx) | |
argument = "{#{argument}}" | |
String.replace(memo, slot, "##{argument}", global: false) | |
end) | |
end | |
def maybe_add_params(path, arguments) do | |
case Enum.filter(arguments, &String.contains?(&1, ": ")) do | |
[] -> | |
if "params" in arguments do | |
query_params = "{params}" | |
"#{path}?##{query_params}" | |
else | |
path | |
end | |
query_params -> | |
query_params = "{#{inspect(query_params) |> String.replace("\"", "") }}" | |
"#{path}?##{query_params}" | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment