Generate typespec and defstruct from runtime values?

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! · GitHub

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 :confused:.

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?