Current section

Files

Jump to
bb lib mix tasks bb.to_urdf.ex
Raw

lib/mix/tasks/bb.to_urdf.ex

# SPDX-FileCopyrightText: 2025 James Harton
#
# SPDX-License-Identifier: Apache-2.0
defmodule Mix.Tasks.Bb.ToUrdf do
@shortdoc "Export a BB robot definition to URDF format"
@moduledoc """
Export a BB robot definition to URDF XML format.
## Usage
mix bb.to_urdf MyApp.Robot --output robot.urdf
mix bb.to_urdf MyApp.Robot -o -
## Options
* `--output`, `-o` - Output file path. Use `-` for stdout.
If not specified, prints to stdout.
"""
use Mix.Task
alias BB.Urdf.Exporter
@switches [output: :string]
@aliases [o: :output]
@impl Mix.Task
def run(args) do
Mix.Task.run("compile")
case parse_args(args) do
{:ok, module, opts} ->
export_robot(module, opts)
{:error, message} ->
Mix.shell().error(message)
exit({:shutdown, 1})
end
end
defp parse_args(args) do
{opts, rest, invalid} = OptionParser.parse(args, strict: @switches, aliases: @aliases)
cond do
invalid != [] ->
{opt, _} = hd(invalid)
{:error, "Unknown option: #{opt}"}
rest == [] ->
{:error, "Usage: mix bb.to_urdf MODULE [--output FILE]"}
length(rest) > 1 ->
{:error, "Too many arguments. Expected one module name."}
true ->
{:ok, parse_module(hd(rest)), opts}
end
end
defp parse_module(module_string) do
module_string
|> String.split(".")
|> Enum.map(&String.to_atom/1)
|> Module.concat()
end
defp export_robot(module, opts) do
case Exporter.export(module) do
{:ok, xml} ->
write_output(xml, opts[:output])
{:error, {:module_not_found, mod, reason}} ->
Mix.shell().error("Module #{inspect(mod)} could not be loaded: #{inspect(reason)}")
exit({:shutdown, 1})
{:error, {:not_a_bb_module, mod}} ->
Mix.shell().error("Module #{inspect(mod)} does not use BB (no robot/0 function)")
exit({:shutdown, 1})
end
end
defp write_output(xml, nil), do: Mix.shell().info(xml)
defp write_output(xml, "-"), do: Mix.shell().info(xml)
defp write_output(xml, path) do
case File.write(path, xml) do
:ok ->
Mix.shell().info("Wrote URDF to #{path}")
{:error, reason} ->
Mix.shell().error("Failed to write #{path}: #{inspect(reason)}")
exit({:shutdown, 1})
end
end
end