## 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. ```elixir 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. ```elixir 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 contents |> Enum.sort(&alpha_asc_dir_last/2) |> Enum.map(&make_path(&1, path)) 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.