You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过宏枚举指定子模块并读取其@doc注释

问题

项目中MyApp.MySubmodules目录及子目录下存在多个模块,示例代码如下:

defmodule MyApp.MySubmodules.Mod1 do
    @doc "this module does AA"
  #
end

defmodule MyApp.MySubmodules.Mod2 do
    @doc "this is for CC"
  #
end

# ...

defmodule MyApp.MySubmodules.Mod3 do
    @doc "something afdsafdsfds"

  #
end

defmodule MyApp.MySubmodules.Xyz.Mod4 do
    @doc "blah-blah-blah-blah"

  #
end

期望在MyApp.MainModule中通过use MyModuleParse,让宏在编译时自动生成如下模块属性:

@my_modules %{
    mod1: "this module does AA",
    mod2: "this is for CC",
    mod3: "something afdsafdsfds",
    xyz_mod4: "blah-blah-blah-blah"
}

核心需求:无需手动维护模块信息,编译时自动枚举MyApp.MySubmodules下的所有模块,读取其@doc注释,最终用于HTML页面展示。

实现方案

1. 创建MyModuleParse宏模块

该模块负责在编译阶段扫描指定根模块的所有子模块,提取@doc注释并生成目标映射:

defmodule MyModuleParse do
  defmacro __using__(opts) do
    root_module = Keyword.get(opts, :root, MyApp.MySubmodules)
    root_module_str = to_string(root_module)

    # 编译时获取所有已加载模块,过滤出根模块的子模块
    modules =
      :code.all_loaded()
      |> Enum.map(&elem(&1, 0))
      |> Enum.filter(fn mod ->
        mod_str = to_string(mod)
        String.starts_with?(mod_str, root_module_str) and mod != root_module
      end)

    # 处理每个模块,生成键值对:转换模块名为下划线格式,提取@doc内容
    module_entries =
      Enum.map(modules, fn mod ->
        # 提取根模块之后的相对路径
        relative_path = String.replace(to_string(mod), root_module_str <> ".", "")
        # 把路径中的点替换为下划线,转成小写原子键
        key = relative_path |> String.replace(".", "_") |> String.downcase() |> String.to_atom()
        # 安全获取模块的@doc注释,默认空字符串
        doc = Module.get_attribute(mod, :doc, "")
        {key, doc}
      end)

    # 生成@my_modules属性的编译代码
    quote do
      @my_modules Map.new(unquote(module_entries))
    end
  end
end

2. 在MyApp.MainModule中使用

直接通过use调用宏,若根模块不是默认的MyApp.MySubmodules,可传入root选项指定:

defmodule MyApp.MainModule do
  # 如需指定自定义根模块:use MyModuleParse, root: Your.Custom.Root.Module
  use MyModuleParse

  # 后续可直接在模块内使用@my_modules
  def get_modules_doc do
    @my_modules
  end
end

关键细节说明

  • 编译时扫描逻辑:利用:code.all_loaded()在编译阶段获取已加载模块,需确保MyApp.MySubmodules下的所有模块在MyApp.MainModule编译前被加载。可通过调整mix.exs的编译顺序,或在use前显式require相关模块来保证。
  • 模块名转换规则:将相对模块路径中的点替换为下划线,转为小写原子,完全匹配需求中的键名格式(如Xyz.Mod4→xyz_mod4)。
  • @doc安全提取:使用Module.get_attribute/3获取@doc,当模块没有定义@doc时会返回默认的空字符串,避免编译报错。

内容的提问来源于stack exchange,提问作者Marco C. Stewart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 00:12:59