Current section

Files

Jump to
membrane_core lib mix tasks membrane.demo.ex
Raw

lib/mix/tasks/membrane.demo.ex

defmodule Mix.Tasks.Membrane.Demo do
@shortdoc "Download Membrane demos and examples"
@moduledoc """
Download Membrane demos and examples. Requires `git` installed.
$ mix membrane.demo [-a] [-l] [-d <repo_path>] [<demos> ...]
## Options
* `-l, --list` - List all demos available and their brief descriptions.
* `-a, --all` - Pull the repository with all demos.
* `-d, --directory` - Specify a directory where the demos should be placed in. By default they're placed in the current working directory
"""
use Mix.Task
@switches [
list: :boolean,
all: :boolean,
directory: :string
]
@aliases [
l: :list,
a: :all,
d: :directory
]
@demos_readme_url "https://raw.githubusercontent.com/membraneframework/membrane_demo/refs/heads/master/README.md"
@demos_clone_url "https://github.com/membraneframework/membrane_demo.git"
@impl true
def run([]) do
Mix.Tasks.Help.run(["membrane.demo"])
end
def run(argv) do
{opts, demos_names} = OptionParser.parse!(argv, aliases: @aliases, switches: @switches)
if opts[:list] do
list_available_demos()
end
target_dir = Path.expand(opts[:directory] || ".")
cond do
opts[:all] -> copy_all_demos(target_dir)
demos_names != [] -> copy_specific_demos(target_dir, demos_names)
true -> :ok
end
end
defp list_available_demos() do
{:ok, _apps} = Application.ensure_all_started([:inets, :ssl])
case :httpc.request(:get, {~c"#{@demos_readme_url}", []}, [{:autoredirect, true}], []) do
{:ok, {{_version, 200, _reason}, _headers, body}} ->
fetch_demos_info(body)
{:ok, {{_version, status, reason}, _headers, _body}} ->
Mix.raise("Failed to fetch demos list: HTTP #{status} #{reason}")
{:error, reason} ->
Mix.raise("Failed to fetch demos list: #{inspect(reason)}")
end
end
defp fetch_demos_info(body) do
demos_info =
List.to_string(body)
|> String.split("\n")
|> Enum.map(&Regex.named_captures(~r/^- \[(?<name>.*?)\]\(.*?\) - (?<description>.*)/, &1))
|> Enum.filter(&(&1 != nil))
|> Enum.map(
&Map.update!(
&1,
"description",
fn description -> Regex.replace(~r/\[(.*?)\]\(.*?\)/, description, "\\1") end
)
)
max_name_length =
demos_info
|> Enum.map(&String.length(&1["name"]))
|> Enum.max()
demos_table =
demos_info
|> Enum.map_join("\n", fn %{"name" => name, "description" => description} ->
dots_line = String.duplicate(".", max_name_length - String.length(name) + 3)
" | #{name} #{dots_line} | #{description}"
end)
Mix.shell().info(demos_table)
end
defp copy_all_demos(target_dir) do
repo_path = Path.join(target_dir, "membrane_demo")
execute_git_command(["clone", "-q", "--depth", "1", @demos_clone_url, repo_path])
end
defp copy_specific_demos(target_dir, demos_names) do
repo_path = Path.join(target_dir, "membrane_demo")
execute_git_command(["clone", "-q", "--depth", "1", "-n", @demos_clone_url, repo_path])
Enum.each(demos_names, fn demo_name ->
case copy_demo(target_dir, demo_name) do
:error ->
Mix.shell().error(
"No demo named #{demo_name}, run this task with -l to list all available demos"
)
:ok ->
Mix.shell().info("`#{demo_name}` demo created at #{Path.join(target_dir, demo_name)}")
end
end)
File.rm_rf!(repo_path)
end
defp copy_demo(target_dir, demo_name) do
repo_path = Path.join(target_dir, "membrane_demo")
demo_path = Path.join(repo_path, demo_name)
execute_git_command(["sparse-checkout", "set", demo_name], repo_path)
execute_git_command(["checkout"], repo_path)
case File.cp_r(demo_path, Path.join(target_dir, demo_name)) do
{:ok, _files} -> :ok
{:error, :enoent, _dir} -> :error
end
end
defp execute_git_command(argv, dir \\ ".") do
case System.cmd("git", argv, stderr_to_stdout: true, cd: dir) do
{_output, 0} ->
:ok
{output, exit_code} ->
Mix.shell().error("""
Git command `git #{Enum.join(argv, " ")} exited with code #{exit_code}. Output:
#{output}
""")
end
end
end