Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Deprecate `:links` option for `:unsafely_name_processes_for_observer` in favour of `:report_links_to_observer` configuration entry
* [Set label](https://hexdocs.pm/elixir/Process.html#set_label/1) for all the Membrane processes (i.e. for both Membrane components like pipelines, elements and bins and for utility components like subprocess supervisors)
* Update required Elixir version to `~> 1.17`
* Add a Mix task for generating demos [#1067](https://github.com/membraneframework/membrane_core/pull/1067)
* Add Mix tasks for easier initialization of components [#1059](https://github.com/membraneframework/membrane_core/pull/1059)

## 1.2.6
Expand Down
137 changes: 137 additions & 0 deletions lib/mix/tasks/membrane.demo.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
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_dir>] [<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_search_list [".", "livebooks"]

@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(:req)
response = Req.get!(@demos_readme_url)

demos_info =
response.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_dir = Path.join(target_dir, "membrane_demo")
execute_git_command(["clone", "-q", "--depth", "1", @demos_clone_url, repo_dir])
end

defp copy_specific_demos(target_dir, demos_names) do
repo_dir = Path.join(target_dir, "membrane_demo")
execute_git_command(["clone", "-q", "--depth", "1", "-n", @demos_clone_url, repo_dir])

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_dir)
end

defp copy_demo(target_dir, demo_name) do
repo_dir = Path.join(target_dir, "membrane_demo")

Enum.reduce_while(@demos_search_list, :error, fn demo_dir, _status ->
demo_path = Path.join([repo_dir, demo_dir, demo_name])

execute_git_command(["sparse-checkout", "set", Path.join(demo_dir, demo_name)], repo_dir)
execute_git_command(["checkout"], repo_dir)

case File.cp_r(demo_path, Path.join(target_dir, Path.basename(demo_path))) do
{:ok, _files} -> {:halt, :ok}
{:error, :enoent, _dir} -> {:cont, :error}
end
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
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ defmodule Membrane.Mixfile do
opts = [
plt_local_path: "priv/plts",
flags: [:error_handling, :unmatched_returns],
plt_add_apps: [:mix]
plt_add_apps: [:mix, :req]
Comment thread
varsill marked this conversation as resolved.
]

if System.get_env("CI") == "true" do
Expand Down Expand Up @@ -255,6 +255,7 @@ defmodule Membrane.Mixfile do
{:makeup_diff, "~> 0.1", only: :dev, runtime: false},
{:dialyxir, "~> 1.1", only: :dev, runtime: false},
{:credo, "~> 1.7", only: :dev, runtime: false},
{:req, "~> 0.5.17", only: [:dev, :test], runtime: false},
# Testing
{:mox, "~> 1.0", only: :test},
{:mock, "~> 0.3.8", only: :test},
Expand Down
7 changes: 7 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,26 @@
"ex_doc": {:hex, :ex_doc, "0.39.3", "519c6bc7e84a2918b737aec7ef48b96aa4698342927d080437f61395d361dcee", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "0590955cf7ad3b625780ee1c1ea627c28a78948c6c0a9b0322bd976a079996e1"},
"excoveralls": {:hex, :excoveralls, "0.18.3", "bca47a24d69a3179951f51f1db6d3ed63bca9017f476fe520eb78602d45f7756", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "746f404fcd09d5029f1b211739afb8fb8575d775b21f6a3908e7ce3e640724c6"},
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"junit_formatter": {:hex, :junit_formatter, "3.4.0", "d0e8db6c34dab6d3c4154c3b46b21540db1109ae709d6cf99ba7e7a2ce4b1ac2", [:mix], [], "hexpm", "bb36e2ae83f1ced6ab931c4ce51dd3dbef1ef61bb4932412e173b0cfa259dacd"},
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
"makeup_diff": {:hex, :makeup_diff, "0.1.1", "01498f8c95970081297837eaf4686b6f3813e535795b8421f15ace17a59aea37", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "fadb0bf014bd328badb7be986eadbce1a29955dd51c27a9e401c3045cf24184e"},
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
"mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
"mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"},
"qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"},
"ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"},
"req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
}
51 changes: 51 additions & 0 deletions test/mix/tasks/membrane.demo_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
defmodule Mix.Tasks.Membrane.DemoTest do
use ExUnit.Case
alias Mix.Tasks.Membrane.Demo

setup do
Mix.shell(Mix.Shell.Process)
end

describe "All listed demos are pulled" do
@tag :tmp_dir
test "individually", %{tmp_dir: tmp} do
Demo.run(["-l"])

get_available_demos_list()
|> Enum.each(fn demo ->
Demo.run(["-d", tmp, demo])

receive do
msg ->
assert {:mix_shell, :info, _message} = msg
assert File.exists?(Path.join(tmp, demo))
end
end)
end

@tag :tmp_dir
test "all at once", %{tmp_dir: tmp} do
demos_list = get_available_demos_list()
Demo.run(["-d", tmp] ++ demos_list)

Enum.each(demos_list, fn demo ->
receive do
msg ->
assert {:mix_shell, :info, _message} = msg
assert File.exists?(Path.join(tmp, demo))
end
end)
Comment on lines +18 to +37

@FelonEkonom FelonEkonom Feb 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's check at least if pulled demo contains lib directory, to ensure it didn't fail at some step

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not every demo contains lib directory, for examples some demos are livebooks or just .exs scripts, but I added an assertion checking if the demo path actually exists.

end
end

defp get_available_demos_list() do
Demo.run(["-l"])

receive do
{:mix_shell, :info, [table]} ->
table
|> String.split("\n")
|> Enum.map(&Regex.named_captures(~r/\| (?<name>\w+)/, &1)["name"])
end
end
end
Loading