Current section
Files
Jump to
Current section
Files
lib/tasks/new/exercise.ex
defmodule Mix.Tasks.New.Exercise do
use Mix.Task
import Mix.Generator
import Mix.Utils, only: [camelize: 1]
alias Workshop.Exercise
alias Workshop.Exercises
@shortdoc "Create a new exercise for a workshop"
@moduledoc """
Creates a new exercise for a workshop.
It expects a name for the new exercise
mix new.workshop NAME
The path will be named after the given NAME. Given `my_exercise` it will
result in a workshop named *My Exercise*.
The exercises will be ordered by a weight set as a module attribute in the
generated *name/exercise.exs* file. It will increment by 1000 for every new
exercise, which should give ample slots to move exercises around after
they have been created, if the exercise order should change.
"""
@spec run(OptionParser.argv) :: :ok
def run(argv) do
Workshop.start([], [])
{opts, argv, _} = OptionParser.parse(argv, switches: [])
path = Workshop.Session.get(:exercises_folder)
# update current working dir
File.cd!(path)
case argv do
[] -> Mix.raise "Expected NAME to be given. Please use `mix new.exercise NAME`"
[name|_] ->
name = Path.basename(Path.expand(name))
unless Exercise.valid_name?(name) do
Mix.raise "Exercise name must start with a letter and have only lowercase " <>
"letters, numbers and underscore, got: #{inspect name}"
end
if Exercise.name_taken?(name) do
Mix.raise "The name #{inspect name} has already been taken. Please use " <>
"another name for this exercise."
end
mod = camelize(name)
title = snake_case_to_headline(name)
weight = get_next_exercise_weight
exercise_folder = Path.expand("#{name}", path)
case File.mkdir_p(exercise_folder) do
:ok ->
File.cd!(exercise_folder, fn ->
do_generate_exercise(path, title, mod, weight, opts)
Mix.shell.info """
The new exercise has been created in:
#{exercise_folder}
If you want to change the precedence of this exercise you can simply
change the assigned @weight in the created exercise.exs-file.
"""
end)
end
end
end
# calculate the next weight value for the next exercise
@weight_increment 1000
defp get_next_exercise_weight do
case Enum.reverse(Exercises.list_by_weight!) do
[{weight, _} | _] ->
weight + @weight_increment
[] ->
@weight_increment
end
end
defp snake_case_to_headline(name) do
name
|> String.split("_")
|> Enum.map(&String.capitalize/1)
|> Enum.join(" ")
end
defp do_generate_exercise(name, title, mod, weight, _opts) do
assigns = [name: name, title: title, module: mod, weight: weight]
create_file "exercise.exs", exercise_template(assigns)
create_directory "files"
create_directory "solution"
create_directory "test"
create_file "test/test_helper.exs", test_helper_template(assigns)
create_file "test/check.exs", check_template(assigns)
end
embed_template :exercise, """
defmodule Workshop.Exercise.<%= @module %> do
use Workshop.Exercise
@title "<%= @title %>"
@weight <%= @weight %>
# Write an exercise description that make the user capable of solving the
# given `@task`.
@description \"""
@todo, write this exercise
# What's next?
Get the task for this exercise by executing `mix workshop.task`. When you are
done writing a solution it can be checked and verified using the
`mix workshop.check` command.
When the workshop check pass you can proceed to the next exercise by executing
the `mix workshop.next` command.
If you are confused you could try `mix workshop.hint`. Otherwise ask your
instructor or follow the directions on `mix workshop.help`.
\"""
@task \"""
@todo, write the task for this exercise
\"""
@hint [
"@todo, write a couple of hints for the solving this exercise"
]
end
"""
embed_template :test_helper, """
defmodule Workshop.Exercise.<%= @module %>Check.Helper do
def exec(solution_dir) do
# Locate and load and perhaps start the users solution.
# The following example assumes that the user solution is located
# in a file called *exercise.exs*:
# "exercise.exs"
# |> Path.expand(solution_dir)
# |> Code.require_file
# load and run the solution checker
Code.require_file("check.exs", __DIR__)
Workshop.Exercise.<%= @module %>Check.run()
end
end
"""
embed_template :check, """
defmodule Workshop.Exercise.<%= @module %>Check do
use Workshop.Validator
verify "verify something" do
# return value can be `:ok`, `{:warning, message}`, or `{:error, message}`
:ok
end
verify "verify something else" do
:ok
end
end
"""
end