## 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.
