129 lines
3.5 KiB
Markdown
129 lines
3.5 KiB
Markdown
## 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.
|