ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Erlang/OTP 源码内文档体系实战:深入解析 -moduledoc 与 -doc 属性、元数据与 EEP-48 文档块

Erlang/OTP 源码内文档体系实战:深入解析 -moduledoc 与 -doc 属性、元数据与 EEP-48 文档块 编程语言语言运行时标准库编译器并发编程【免费下载链接】otpErlang/OTP项目地址https://gitcode.com/gh_mirrors/ot/otp点击查看免费下载在 Erlang/OTP 中文档不再只是游离于代码之外的 Markdown 文件而是可以通过-moduledoc与-doc两个模块属性直接内嵌在.erl源码中随模块一起编译、分发与检索。本篇技术指南以 Erlang 参考手册的 Documentation 章节为主体结合 模块属性、编译器文档提取实现 与 code:get_doc/1 等仓库源码系统讲解如何为模块、函数、用户自定义类型和回调编写源码内文档如何通过元数据标注since、deprecated、group、equiv等信息以及如何利用 EEP-48 文档块在 shell、IDE 和 ExDoc 中消费这些文档。读完后你将能够在自己的 Erlang 项目中直接落地这套代码即文档的实践。前提说明-doc属性自 Erlang/OTP 27 起可用。文中所有行为均以当前仓库Erlang/OTP 主分支为准示例代码可直接复制运行。从两个属性说起-moduledoc与-doc在 Erlang 中写文档是通过-moduledoc和-doc两个模块属性完成的。最基本的形态如下-module(arith). -moduledoc A module for basic arithmetic. . -export([add/2]). -doc Adds two numbers.. add(One, Two) - One Two.这里有三条关键规则-moduledoc属性必须出现在第一个-doc属性或第一个函数声明之前它描述整个模块的用途-doc属性必须紧邻其所修饰的函数或属性之前可被-doc修饰的属性包括用户自定义类型-type、-opaque以及行为模块属性-callback。关于-doc属性允许的值modules.md 给出了更完整的定义文档可以是一个字面量字符串或 UTF-8 编码的二进制字符串两者等价-doc(Example \docs\). -doc(Example \docs\/utf8). -doc ~S/Example docs/. -doc Example docs -doc ~B|Example docs|.为了可读性官方推荐优先使用普通strings或三重引号字符串。除字符串外-doc还接受{file, Filename}形式的外部文件引用、false隐藏实体以及map()形式的元数据详见下文。每个实体只能有一个文档字符串条目但可以有多个元数据条目。默认情况下文档属性的格式是 Markdown可通过模块文档元数据中的format键改变。至于 Markdown 语法本身官方推荐参考 GitHub 的 Basic writing and formatting syntax 作为起点。文档元数据Documentation metadata除了正文还可以通过以 map 为参数的-moduledoc或-doc属性为文档条目附加元数据-module(arith). -moduledoc A module for basic arithmetic. . -moduledoc #{since 1.0}. -export([add/2]). -doc Adds two numbers.. -doc(#{since 1.0}). add(One, Two) - One Two.元数据由文档工具用来向用户提供额外信息。一个实体允许存在多个元数据条目这些 map 会被合并重复键以最后出现的为准-doc Adds two numbers.. -doc #{since 1.0, author Joe}. -doc #{since 2.0}. add(One, Two) - One Two.上面这段代码最终产生#{since 2.0, author Joe}。元数据 map 的键和值可以是任意类型但官方建议键只用原子atoms、值只用字符串strings以保证文档工具的兼容性。从源码实现看编译器在beam_doc.erl中对元数据做了专门的归类处理文档入口的元数据键被区分为[group, since, deprecated, equiv]函数/类型/回调与[since, deprecated, format]模块级两类见 lib/compiler/src/beam_doc.erl。其中maybe_add_since/2实现了模块级since向函数级元数据的继承逻辑当函数自身未显式给出since、而模块元数据中带有since时会自动补入该值见 lib/compiler/src/beam_doc.erl。外部文档文件-doc {file, Path}当文档较长时-moduledoc和-doc属性也可以把文档正文放到外部文件中使用-doc {file, path/to/doc.md}指向它。路径是相对于该-doc属性所在文件的。例如%% doc/add.md Adds two numbers.%% src/arith.erl -doc({file, ../doc/add.md}). add(One, Two) - One Two.这种方式适合把冗长的模块说明拆解为独立页面保持.erl源码的清爽。文档模块Documenting a module模块描述应当包含如何使用 API 的细节以及不同函数协同工作的示例。这里很适合使用图片和其他图示来展示模块的用法。如果内容过长与其把大段文字堆在-moduledoc属性里不如拆到外部页面。-moduledoc应当以一个简短段落开头介绍模块然后再深入细节-module(arith). -moduledoc A module for basic arithmetic. This module can be used to add and subtract values. For example: erlang 1 arith:subtract(arith:add(2, 3), 1). 4 .注意这里在-moduledoc内部使用了四重反引号来包裹 Erlang 代码块从而避免与文档属性本身的三重引号冲突。这个技巧在文档较长、需要内嵌代码示例时非常实用。Moduledoc 元数据-moduledoc有三个保留元数据键键类型含义sinceunicode:chardata()模块是在应用的哪个版本引入的。若设置了它模块内所有未单独指定since的函数、类型和回调都会自动继承同一值deprecatedunicode:chardata()在文档中显示一段文字说明该模块已弃用以及应改用什么替代formatunicode:chardata()本模块所有文档使用的格式默认是text/markdown应使用标准的 MIME type 书写组合使用示例-moduledoc {file, ../doc/arith.asciidoc}. -moduledoc #{since 0.1, format text/asciidoc}. -moduledoc #{deprecated Use the Erlang arithmetic operators instead.}.文档函数、用户自定义类型与回调函数、类型和回调都可以用-doc属性来文档化。每条目同样应以简短段落开头描述用途再按需展开细节。与模块文档不同不建议在函数/类型/回调的文档中包含图片或图表因为这部分文档会被 IDE 和 shell 的\c:h/1直接展示给用户。-doc A number that can be used by the arith module. We use a special number here so that we know that this number comes from this module. . -opaque number() :: {arith, erlang:number()}. -doc Adds two numbers. ### Example:1 arith:add(arith:number(1), arith:number(2)). {arith, 3}. -spec add(number(), number()) - number(). add({arith, One}, {arith, Two}) - {arith, One Two}.文档中的示例可以通过m:ct_doctest进行自动化测试。在 lib/common_test/src/ct_doctest.erl 中可以看到ct_doctest专门运行文档中的示例默认查找 Markdown 代码块中形如 Erlang shell 会话以N为提示符N从1开始的代码块并执行验证从而保证文档示例正确、不过时、风格一致。典型用法是在 Common Test 套件中调用ct_doctest:module(my_module)all() - [doctests]. doctests(_Config) - ct_doctest:module(my_module).Doc 元数据-doc有四个保留元数据键键类型含义sinceunicode:chardata()该函数、类型或回调是在应用的哪个版本引入的deprecatedunicode:chardata()显示一段说明该条目已弃用及替代方案的文字。若代码中用-deprecated属性标记了某个函数编译器会自动插入这个键groupunicode:chardata()该函数、类型或回调所属的分组。工具如 shell 自动补全、文档生成器可以据此将同一组的条目聚合列出通常以组名作为指示equivunicode:chardata() \| F/A \| F(...)注明该函数等价于模块中的另一个函数。可以用Func/Arity、Func(Args)或 unicode 字符串描述exportedboolean()标识该条目是否被导出。此值由编译器自动设置用户不应自行设置equiv的两种写法-doc #{equiv add/3}. add(One, Two) - add(One, Two, []). add(One, Two, Options) - ...-doc #{equiv add(One, Two, [])}. -spec add(One :: number(), Two :: number()) - number(). add(One, Two) - add(One, Two, []). add(One, Two, Options) - ...注意写入 EEP-48 文档块元数据时equiv的值会被转换成字符串。exported的自动赋值逻辑同样可以在编译器实现中找到beam_doc.erl在生成文档入口时会根据导出状态填充该键供文档工具区分公开 API与仅因被引用而可见的类型。Doc 签名Doc signatures文档签名是一段用于描述函数及其参数的简短文本。默认情况下它由-spec或函数定义中的参数名推导而来add(One, Two) - One Two. -spec sub(One :: integer(), Two :: integer()) - integer(). sub(X, Y) - X - Y.上面会得到签名add(One, Two)和sub(One, Two)。注意sub/2虽然函数体参数叫X, Y但签名取自-spec中的One, Two。对于类型和回调签名从类型/回调规范推导-type number(Value) :: {arith, Value}. %% signature will be number(Value) -opaque number() :: {arith, number()}. %% signature will be number() -callback increment(In :: number()) - Out. %% signature will be increment(In) -callback increment(In) - Out when In :: number(). %% signature will be increment(In)如果无法从代码中轻松得出漂亮的签名则改用 MFA 语法例如add/2、number/1、increment/1。还可以通过把自定义签名放在-doc属性的第一行来手动指定。该签名必须是函数声明中-之前的形式-doc add(One, Two) Adds two numbers. . add(A, B) - A B.这会生成签名add(One, Two)且签名行会从文档字符串中移除因此最终进入文档的正文只有Adds two numbers。此机制对函数、类型和回调均适用。Markdown 中的链接在 Markdown 文档中任何内联代码段中看起来像 MFA 的内容都会被自动识别为链接。例如-doc See sub/2 for more details.如果当前模块中存在sub/2就会自动创建指向它的链接。也可以把sub/2直接用作链接目标下面几种写法效果完全相同-doc See subtract for more details. -doc See [sub/2] for more details. -doc See [subtract] for more details [subtract]: sub/2 . -doc See [subtract][1] for more details [1]: sub/2 .链接还可以指向其他实体语法汇总如下目标语法示例远程函数module:function/arityarith:sub/2模块m前缀 可选锚点m:arith、m:arith#anchor类型t前缀 函数语法t:number/0、t:arith:number/0回调c前缀 函数语法c:increment/0、c:arith:increment/0当前应用的附加页面普通链接[release notes](https://link.gitcode.com/i/2470ea2fe4f90cb85a70bbb31076e349)其他应用的附加页面e前缀 应用名 可选锚点e:stdlib:unicode_usage、e:stdlib:unicode_usage#notes-about-raw-filenames这些前缀约定在 OTP 自身的文档体系中被广泛使用。例如模块文档中常见的e:system:documentation.md#doc-metadata、e:kernel:eep48_chapter.md、e:erts:erlc_cmd.md等写法正是e前缀跨应用链接的实际应用。可见与隐藏什么是文档中的公开 API一个 Erlangm:application通常由公开模块和私有模块组成。默认情况下应用内所有模块都是可见的通过设置-moduledoc false.可以将特定模块从可用 API 列表中隐藏。模块内部则由公开/私有函数与类型属性组成。默认情况下所有导出的函数、导出的类型和回调都被视为可见属于模块公开 API 的一部分任何被其他可见类型属性引用的未导出类型也是可见的但不算公开 API。例如-export([example/0]). -type private() :: one. -spec example() - private(). example() - one.函数example/0被导出且引用了未导出的类型private/0因此两者都会被标记为可见private/0的元数据字段exported会被设为false表明它不属于公开 API。如果需要把可见实体隐藏则要把-doc属性设为false-export([example/0]). -type private() :: one. -spec example() - private(). -doc false. example() - one.由于example/0被显式标记为隐藏example/0和private/0都会被隐藏。需要特别注意的是给自动隐藏的实体未导出的函数或类型添加任何文档都会被忽略并产生警告。这类函数只能用普通注释来记录。从编译器的视角看可见性追踪是beam_doc.erl的一项核心职责。其内部维护了hidden_types、user_defined_types、types_from_exported_funs等状态集合专门跟踪被导出函数引用、因而需要在文档中展示但并未导出的类型并在处理-doc false.隐藏标记时对可达类型图type_dependencydigraph进行联动判定见 lib/compiler/src/beam_doc.erl。编译与获取文档EEP-48 文档块与h/1Erlang 编译器在编译模块时默认会把文档插入 EEP-48 文档块。如果不需要可以给compile:file/1传入no_docs选项或给 erlc 传no_docs这样就不会插入文档块erlc no_docs my_module.erl文档可以通过code:get_doc/1获取或在 shell 中用内建命令h/1查看1 h(arith). arith A module for basic arithmetic. 2 h(arith, add). add(One, Two) Adds two numbers.code:get_doc/1在 lib/kernel/src/code.erl 中的实现会默认从[eep48, debug_info]两个来源依次查找eep48对应get_doc_chunk/2读取编译期写入的文档块debug_info对应get_doc_chunk_from_ast/1从 AST 调试信息中重建#docs_v1{}。返回结构是#docs_v1{}记录这也是 ExDoc 等工具消费文档的统一数据模型。在编译器一侧beam_doc.erl中的maybe_add_since、maybe_add_deprecation等函数在把文档属性写进文档块之前会完成since继承、deprecated自动注入等预处理lib/compiler/src/beam_doc.erl同时它维护的docsmap 记录了每个{function | type | opaque | nominal | callback, Name, Arity}条目对应的状态隐藏、文档文本、元数据这正是 EEP-48 文档块的生成基础lib/compiler/src/beam_doc.erl。用 ExDoc 生成 HTML/ePub 文档ExDoc 对从 Markdown 生成文档提供了内建支持。最简单的途径是使用rebar3_ex_doc插件在rebar3.config中做如下配置%% Enable the plugin {plugins, [rebar3_ex_doc]}. {ex_doc, [ {extras, [README.md]}, {main, README.md}, {source_url, https://github.com/namespace/your_app} ]}.配置完成后运行rebar3 ex_doc即可把文档生成到doc/index.html。更详细的可选参数参见rebar3_ex_doc的文档。也可以下载 ExDoc 的 release escript bundle从命令行直接运行用法通过ex_doc --help查看。如果计划用 ExDoc 生成 HTML/ePub官方强烈建议完整阅读其文档以充分利用格式与选项能力。总结与实践建议把文档写进 Erlang 源码核心收益是文档与代码同源、随编译分发、随时可通过h/1与code:get_doc/1检索并在 ExDoc 等工具下生成站点。实践要点可归纳为结构与位置-moduledoc必须在所有-doc/函数声明之前-doc必须紧贴所修饰的函数、类型-type/-opaque或回调-callback。正文格式默认 Markdown长文优先拆到外部文件-doc {file, Path}路径相对于属性所在文件模块级示例可用-moduledoc #{format text/asciidoc}切换格式。元数据纪律键用原子、值用字符串善用模块级since的自动继承、-deprecated属性的自动注入、group分组与equiv等价标注exported留给编译器设置。可见性控制用-doc false./-moduledoc false.隐藏实体不要给自动隐藏的未导出函数/类型写文档会告警。示例可测文档中的 shell 会话示例可用ct_doctest:module/1纳入 Common Test 运行保证示例始终正确。关闭与消费不需要文档块时用no_docs/no_docs需要展示时用h/1、code:get_doc/1或 ExDoc。这套机制在 OTP 自身的标准库中已被大规模采用例如lib/stdlib/src/argparse.erl、lib/stdlib/src/array.erl、lib/stdlib/src/binary.erl等模块均以-moduledoc ...的形式维护源码内文档lib/common_test/src/ct_doctest.erl则用同样的属性书写并被其自身测试体系验证。参照这些真实用例你可以在自己的项目中建立同样规范、可检索、可测试的文档体系。赞分享编程语言语言运行时标准库编译器并发编程【免费下载链接】otpErlang/OTP项目地址https://gitcode.com/gh_mirrors/ot/otp点击查看免费下载相关推荐Erlang/OTP EEP-48 文档存储与格式深度解析从 docs_v1 数据模型到 code:get_doc 与 shell_docs 渲染Erlang/OTP EEP 48 文档存储与格式深度解析从 docs_v1 数据模型到 code:get_doc 与 shell_docs 渲染 EEP 4编程语言语言运行时标准库编译器并发编程Erlang/OTP EEP-48 文档存储与格式全解析从 docs_v1 Chunk 到 application/erlanghtml 渲染Erlang/OTP EEP 48 文档存储与格式全解析从 docs_v1 Chunk 到 application/erlanghtml 渲染 导读 本文基编程语言语言运行时标准库编译器并发编程Erlang/OTP 中的 EDoc 文档生成器完全指南从注释标签到 EEP-48 Doc ChunksErlang/OTP 中的 EDoc 文档生成器完全指南从注释标签到 EEP 48 Doc Chunks EDoc 是 Erlang/OTP 自带的程序文档生编程语言语言运行时标准库编译器并发编程上一篇Iosevka 27.2.0 变更深度解析新增技术符号字形与样式集指派修正下一篇Wagtail 1.9 版本深度解析修订对比、多对多关系与 StreamField 上下文增强创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表