Current section
Files
Jump to
Current section
Files
lib/workshop.ex
defmodule Workshop do
use Application
@type workshop_root :: String.t
@type exercise_folder :: String.t
def start(_type, _args) do
if System.get_env("sandbox") do
# For development purposes. Use any of the workshop commands like this:
#
# $ sandbox=foo mix workshop.info
#
# To use the `foo` folder in the current directory as the target workshop.
System.get_env("sandbox") |> Path.expand |> File.cd!
end
Workshop.Supervisor.start_link([])
# load workshop meta info
Workshop.Session.get(:data_folder) |> Path.join("workshop.exs") |> Code.require_file
end
@doc """
Print the workshop version. This is exposed this way because the data would not be
available otherwise when the system has been made into a mix archive.
"""
@spec version :: String.t
def version, do: "0.5.1"
defdelegate validate, to: Workshop.Validate, as: :run
defdelegate doctor, to: Workshop.Doctor, as: :run
@doc """
Same as `locate_root/1` but will use the current working directory as the starting
point.
"""
@spec locate_root() :: {:ok, workshop_root}
| {:ok, workshop_root, exercise_folder}
| {:error, String.t}
def locate_root do
File.cwd! |> locate_root
end
@doc """
Attempt to find the workshop root folder by traversing the file system upwards until
it find a folder which contains a *.workshop*-folder that has an *exercises* folder
within.
This allow the user to execute workshop commands from anywhere in the workshop folder
structure.
It takes one argument which is the name of the path it should start looking from.
If the penultimate folder exist as well in the workshop exercises directory it will
get used as the current exercise in tasks that require a current exercise.
"""
@spec locate_root(String.t) :: {:ok, workshop_root}
| {:ok, workshop_root, exercise_folder}
| {:error, String.t}
def locate_root(folder) when is_binary(folder) do
do_locate_root folder
end
defp do_locate_root(folder, backtrack \\ nil) when is_binary(folder) do
candidate = Path.join(folder, ".workshop")
candidate_exercise_folder = Path.join(candidate, "exercises")
if File.exists?(candidate) and File.exists?(candidate_exercise_folder) do
possible_exercise_folder = strip_number_prefix(backtrack)
if exercise_exists? possible_exercise_folder, candidate_exercise_folder do
{:ok, folder, possible_exercise_folder}
else
{:ok, folder}
end
else
parent = Path.dirname(folder)
unless folder == parent do
do_locate_root parent, Path.basename(folder)
else
{:error, "Folder is not within a workshop"}
end
end
end
defp exercise_exists?(nil, _), do: false
defp exercise_exists?(exercise_name, exercise_folder) do
Path.wildcard("#{exercise_folder}/*#{exercise_name}/exercise.exs")
|> Enum.map(&Path.dirname/1) |> Enum.map(&Path.basename/1) # get the name of the dir
|> Enum.any?(&(&1 == exercise_name))
end
@valid_exercise_name_with_number_prefix ~r/^\d+_[a-z][\w_]*$/
def strip_number_prefix(nil), do: nil
def strip_number_prefix(subject) when is_binary subject do
if Regex.match?(@valid_exercise_name_with_number_prefix, subject) do
[_weight, name] = String.split(subject, "_", parts: 2)
name
else
subject
end
end
@doc """
Helper for running callbacks at certain points in the workflow.
The function expect the name of a callback (optionally) defined as a public function
on the workshop info module.
It will figure the arity of the callback function from the size of the args list.
"""
@spec run_callback(Atom, [Any]) :: Any
def run_callback(callback, args \\ []) do
if Code.ensure_loaded?(Workshop.Meta) and function_exported?(Workshop.Meta, callback, length args) do
apply(Workshop.Meta, callback, args)
end
end
end