Is Elixir a scripting language?

Elixir is known for being a language made for building distributed applications
that scale, are massively concurrent, and have self-healing properties. All of
these adjectives paint Elixir in a grandiose light. And for good reasons!

But is Elixir also a language that can be used for the more mundane tasks of
this world like scripting? I think the answer is a definite yes.

To see this, let’s take a look at all the ways we can write scripts using
Elixir. We’ll build using the same example, going from simple to more complex
solutions.

Our script will simply create a new markdown file with today’s date so we can
write our To Do list. The structure of the directories will be YYYY/MM/DD.md,
so we will nest each day under a month and each month under a year.

# Get today's datedate=Date.utc_today()year=Integer.to_string(date.year)month=Integer.to_string(date.month)day=Integer.to_string(date.day)# Generate the month's full path with YYYY/MM formatmonth_path=File.cwd!()|>Path.join(year)|>Path.join(month)# Create the month and year directoriesFile.mkdir_p(month_path)# Generate the filename with today's datefilename=Path.join(month_path,day)<>".md"# Check existence so we don't override a fileunlessFile.exists?(filename)do# include sample headerheader="""
---
date: #{Date.to_string(date)}
---
To Do
=====
- What do you need to accomplish today?
"""# write to the fileFile.open(filename,[:write],fnfile->IO.write(file,header)end)end# Print out confirmation messagefinal_message="""
> Created #{Path.relative_to_cwd(filename)}
"""IO.puts(final_message)

Excellent! That is the full extent of our script. Let’s now see how we can run
it.

A second way to run the script is to make it into an executable. This is perhaps
an extension of the one above, but I include it as a separate step because I
think it could prove useful in some projects.

Our todo.exs and bin/todo scripts are examples of standalone scripts. They
are useful when you want to run a task that is self-contained. bin/todo has
the benefit that the user of the script need not know that the script is running
Elixir (though it still needs to have Elixir installed). It’s just a script like
any other one you may find in your bin/ folder.

The benefit of running this script via mix (as opposed to the previous two
options) is that the script is part of your project, so it has access to all the
code you have defined in the project.

Let’s bring that point home by extracting most of the logic to a TodoBuilder
module:

# lib/todo_builder.exdefmoduleTodoBuilderdodefrun(date)doyear=Integer.to_string(date.year)month=Integer.to_string(date.month)day=Integer.to_string(date.day)month_path=File.cwd!()|>Path.join(year)|>Path.join(month)File.mkdir_p(month_path)filename=Path.join(month_path,day)<>".md"unlessFile.exists?(filename)doheader="""
---
date: #{Date.to_string(date)}
---
To Do
=====
- What do you need to accomplish today?
"""File.open(filename,[:write],fnfile->IO.write(file,header)end)end{:ok,filename}endend

Having it as a script that can run with our project code is nice. But sometimes
we want to make it a more explicit part of our project. Turning our script into
a mix task can do just that. This is especially true if we expect external
parties to use our project since mix tasks have the extra benefit of
documentation!

Let’s change our script into a mix task. In the tasker project,

Create a lib/mix/tasks/ directory

Create a todo.ex task

Move the code we have in scripts/todo.exs into that file

Add use Mix.Task at the top of the module

Add a @shortdoc and @moduledoc with descriptions of what the task does

If you’re interested on how this gets used in the wild, take a look at how
Phoenix and Ecto use mix tasks for commonly performed actions and for
their generators. Things such as mix phx.new, mix phx.server, and mix
ecto.migrate are all mix tasks!

Wow, it’s been a long road but we’re finally down to the last way we can script
in Elixir (that I know of).

One of the built-in mix tasks that comes with mix is mix escript.build. It
packages your project and dependencies into a binary that is executable. It even
embeds Elixir as part of the script, so it can be used so long as a machine has
Erlang/OTP without requiring Elixir to be present.

Let’s get to it. We have to first update our mix.exs file to define an entry
point for the escript.build task,

# mix.exsdefprojectdo[app::tasker,# other options## add this line belowescript:escript()]end# add the entry pointdefpescriptdo[main_module:Tasker.TodoCLI]end

Much like a mix task, an escript has the ability to use the rest of your
project’s codebase. But it has that ability because the project and its
dependencies are compiled and packaged in. That means you can use it outside
of your mix project. And since Elixir is embedded, all you need is to have
Erlang/OTP installed. So it makes our little script into an executable that is
easily shareable with others!

To test that, feel free to move the ./tasker script out of your mix
project into another directory and run it. You’ll see it does its work just
fine!

We covered a lot of ground, and we saw that Elixir has great tooling for
creating scripts. But I would be remiss if I didn’t include a mention of the
OptionParser module. It’s a little module that helps parse command line
options from something like this,