I’m not very used to macros/compile time manipulation, but I will explain what I want to achieve and would like to know if it’s possible by any way.
I’m building a CLI framework for Elixir, with high inspirations on clap lib for rust. There you can do something like:
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Adds files to myapp
Add { name: Option<String> },
}
fn main() {
let cli = Cli::parse();
// You can check for the existence of subcommands, and if found use their
// matches just as you would the top level cmd
match &cli.command {
Commands::Add { name } => {
println!("'myapp add' was used, name is: {name:?}")
}
}
}
In other words, define a CLI struct and then parse the data structure with available/parsed input. So in the Elixir world I made some macros like: defcommand/2
, defcommand/3
and defoption/2
, that can be used as:
defmodule CLI do
use Nexus
defcommand :foo,
required: true,
type: :string,
doc: "Command that receives a string as argument and prints it."
defcommand :fizzbuzz, type: {:enum, ~w(fizz buzz)a}, doc: "Fizz bUZZ", required: true
defcommand :foo_bar, type: :null, doc: "Teste" do
defoption :some, short: :s
defcommand :baz, default: "hello", doc: "Hello"
defcommand :bar, default: "hello", doc: "Hello"
end
end
So, basically, given that macros/definitions of commands I would like construct a %CLI{}
struct, or better: a %__MODULE__{}
struct with fields :foo, :fizzbuzz
and submodules for subcommands like :foo_bar
that would be a sub-struct with :baz, :bar
fields and also typespecs for that, for example for the CLI
module:
defmodule CLI do
defmodule CLI.FooBar do
@type t :: %__MODULE__{baz: String.t, bar: String.t}
defstruct baz: “hello”, bar: “hello”
end
@type t :: %__MODULE__{foo: String.t, fizzbuzz: :fizz | :buzz, foo_bar: CLI.FooBar.t}
defstruct foo: nil, fizzbuzz: nil, foo_bar: %FooBar{}
end
However, commands and flags will only be available after compilation of their respective macros, so, how would be possible to achieve I want?
The complete source code for my library (is already usable) can be found in GitHub - zoedsoupe/nexus: CLI framework for Elixir, with magic!
EDIT 1: I think I could define a global macro called defcli
, and then receive commands and flags definitions inside it, but I don’t think it matches my usage requirements .
defmodule CLI do
use Nexus
defcli do
defoption :help, short: :h
defcommand :foo, default: “bar”, type: :string
end
end
That way I can build the typespec and struct with those definitions, but it would break the library contract for now, would be a super major change in the public API, so I would like another possible approach, if there is?