Recursively List All Files In A Directory With Elixir
Introduction
We wish to print a list of all the files in a directory, ignoring files beginning with certain characters, sorted alphabetically, with the directories last.
Finding the files
Code
Finds all files in the path
directory recursively,
ignoring any files or directories that start with any of the characters
in the @starts_with
module attribute.
defmodule Files do
@starts_with [".", "_"]
def find(path \\ "."), do: find(path, list_contents(path))
defp find(dir, files) do
Enum.reduce(files, {dir, []}, fn file, {path, contents} ->
{path, path |> Path.join(file) |> update_contents(file, contents)}
end)
end
defp list_contents(path), do: path |> File.ls!() |> ignore()
defp update_contents(path, file, contents) do
cond do
File.regular?(path) -> [file | contents]
File.dir?(path) -> [find(path) | contents]
end
end
defp ignore(filenames) do
Enum.reject(filenames, &(String.first(&1) in @starts_with))
end
end
Output
The data structure holding the results of the file search.
iex(1)> directory_tree = Files.find("hello")
{"hello",
[
"README.md",
{"hello/test",
[
{"hello/test/support", ["conn_case.ex"]},
"test_helper.exs",
{"hello/test/hello_web",
[{"hello/test/hello_web/controllers", ["error_json_test.exs"]}]}
]},
{"hello/lib",
[
"hello.ex",
{"hello/lib/hello", ["application.ex"]},
"hello_web.ex",
{"hello/lib/hello_web",
[
{"hello/lib/hello_web/controllers", ["error_json.ex"]},
"telemetry.ex",
"router.ex",
"endpoint.ex"
]}
]},
{"hello/priv", [{"hello/priv/static", ["robots.txt", "favicon.ico"]}]},
{"hello/config",
["config.exs", "dev.exs", "test.exs", "prod.exs", "runtime.exs"]},
"mix.exs"
]}
Sorting and printing
Code
Sort alphabetically, with directories last. Downcase before comparing strings.
defmodule Paths do
def puts(dir_tree), do: dir_tree |> print() |> Enum.join("\n") |> IO.puts()
defp print({path, contents}), do: path |> list(contents) |> List.flatten()
defp list(path, contents) do
|> Enum.sort(&alpha_asc_dir_last/2) |> Enum.map(&make_path(&1, path))
contents end
defp alpha_asc_dir_last({a, _}, {b, _}), do: fmt(a) < fmt(b)
defp alpha_asc_dir_last({_, _}, _), do: false
defp alpha_asc_dir_last(_, {_, _}), do: true
defp alpha_asc_dir_last(a, b), do: fmt(a) < fmt(b)
defp make_path(filename, path) when is_binary(filename), do: Path.join(path, filename)
defp make_path({path, contents}, _), do: list(path, contents)
defp fmt(f), do: String.downcase(f)
end
Output
Print all the files sorted.
iex(2)> Paths.puts(directory_tree)
hello/mix.exs
hello/README.md
hello/config/config.exs
hello/config/dev.exs
hello/config/prod.exs
hello/config/runtime.exs
hello/config/test.exs
hello/lib/hello.ex
hello/lib/hello_web.ex
hello/lib/hello/application.ex
hello/lib/hello_web/endpoint.ex
hello/lib/hello_web/router.ex
hello/lib/hello_web/telemetry.ex
hello/lib/hello_web/controllers/error_json.ex
hello/priv/static/favicon.ico
hello/priv/static/robots.txt
hello/test/test_helper.exs
hello/test/hello_web/controllers/error_json_test.exs
hello/test/support/conn_case.ex
:ok
Conclusion
Trying to do this without recursion made it difficult to sort directories first.