Xfile: a better way to work with files and directories in Elixir

The truth is out there… if you can find it

Recursively List Files

I kept encountering shortcomings when using Elixir’s built-in File module. One of the bits of boilerplate code that I added to a few different projects was functionality that let me recursively list files stored in a nested directory structure. In adherence to DRY principles, I turned that boilerplate into its own package. Now I can easily traverse deeply nested directories in a uniform way:

iex(1)> Xfile.ls!("deps") 
|> Enum.to_list()
["deps/metrics/.hex", "deps/metrics/LICENSE", "deps/metrics/.fetch",
"deps/metrics/README.md", "deps/metrics/hex_metadata.config",
"deps/metrics/rebar.lock", ...]
  1. Xfile returns its results as a lazily-evaluated stream. This has important performance benefits if you ever wade into folders with indecent numbers of files. You just have to enumerate the stream (here, this is done by converting it to a list via Enum.to_list/1 )
  2. Xfile returns the full path segment, e.g. dir/file.txt whereas the built-in File.ls/1 returns just the basename, e.g. file.txt . When I used the File.ls/1, I often found myself needing to concatenate the parent directory onto the results before I could do anything with them.
  3. Xfile.ls/2 supports recursively listing files, so I can easily peer into deeply nested directory structures without re-inventing wheels.

Grep File Contents

One of my favorite bash commands is grep -rl — I usually rely on it instead of my IDE when I’m searching a project. Now I can return a list of files that contain any line that matches the pattern:

iex> Xfile.grep_rl("raise Error", "projekt", recursive: true) 
|> Enum.to_list()
[
"projekt/lib/foo/service.ex",
"projekt/lib/db.ex"
]

Head, Tail, and counting lines

Finally, I will mention a couple other convenience functions that can spare you from writing more boilerplate code after researching solutions on Stackoverflow: head , tail , and line_count .

iex> Xfile.head(".gitignore", 3) 
|> Enum.to_list()
[
"# The directory Mix will write compiled artifacts to.\n",
"/_build/\n",
"\n"
]

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store