如何通过宏枚举指定子模块并读取其@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
相关产品推荐
相关产品推荐

