Skip to main content

C++ 文档生成器。

项目描述

乌尔法比

派皮 Waf Python 测试 黑色的 Flake8 点安装

我们希望为我们的 C++ 项目提供一个可配置且易于使用的 Sphinx API 文档生成器。为了实现这一点,我们向其他人寻求灵感:

那么什么是wurfapi

  • 本质上,我们找到了 Gasp 放手的地方。我们借用了模板的想法,使其高度可配置。

  • 我们通过自动运行 Doxygen 来生成初始 API 文档,使其易于使用。

  • 我们将 Doxygen XML 解析为易于使用的 Python 字典。可以在模板中使用。

  • 我们为其他后端(替换 Doxygen)准备了扩展,例如 https://github.com/foonathan/standardese准备就绪后。

地位

我们目前在以下项目中使用 wurfapi:

… 还有很多。

用法

我们建议您在虚拟环境中安装 wurfapi 和 sphinx。要使用扩展程序,需要执行以下步骤:

  1. 创建虚拟环境:

    Follow the https://docs.python.org/3/tutorial/venv.html
  2. 安装扩展:

    pip install sphinx
    pip install wurfapi
  3. 通过运行生成初始Sphinx文档:

    mkdir docs
    cd docs
    python sphinx-quickstart

    您将需要输入有关您的项目的一些基本信息,例如项目名称等。

  4. 打开sphinx-quickstart生成的conf.py并添加以下内容:

    # Append or insert 'wurfapi' in the extensions list
    extensions = ['wurfapi']
    
    # wurfapi options - relative to your docs dir
    wurfapi = {
      'source_paths': ['../src'],
      'recursive': True,
      'parser': {'type': 'doxygen', 'download': True,  'warnings_as_error': True}
    }
  5. 要为类生成 API 文档,请打开一个.rst文件,例如index.rst如果您运行sphinx-quickstart。假设我们要为命名空间项目中名为test的类生成文档。

    为此,我们将以下指令添加到 rst 文件:

    .. wurfapi:: class_synopsis.rst
      :selector: project::coffee::machine

    这样index.rst就变成了这样:

      Welcome to Coffee's documentation!
      ===================================
    
      .. toctree::
        :maxdepth: 2
        :caption: Contents:
    
      .. wurfapi:: class_synopsis.rst
          :selector: project::coffee::machine
    
      .. wurfapi:: class_synopsis.rst
          :selector: project::coffee::recipe
    
    
      Indices and tables
      ==================
    
      * :ref:`genindex`
      * :ref:`modindex`
      * :ref:`search`
    
    
    To do this we use the ``class_synopsis.rst`` template.
  6. 生成文档

    制作html

标签和参考

为了引用 API 中的不同元素,我们添加了一个自定义 Sphinx 角色:wurfapi:

: wurfapi:角色将尝试从给定的文本中推断出唯一名称。例如,如果您想引用唯一名称 foo::bar::baz::func(std::string var)并且foo::bar::baz中没有其他名为func的成员函数,您可以通过以下方式引用它写作:wurfapi:`foo::bar::baz::func`

另一方面,如果有一个具有唯一名称的函数 foo::bar::baz::function(std::string var) :wurfapi:`foo::bar::baz::func`可以与两个 func 匹配和函数,会抛出错误。在这种情况下,这可以通过添加左括号来解决::wurfapi:`foo::bar::baz::func(`

您可以稍后在本自述文件中阅读有关唯一名称的更多信息。

在 readthedocs.org 上运行

要在 readthedocs.org 上使用它,您需要安装wurfapi Sphinx 扩展。这可以通过在文档文件夹中添加requirements.txt来完成。readthedocs.org 可以配置为 在构建项目时使用requirements.txt 。只需将wurfapi放入 requirements.txt即可。

氧气问题

没有什么是完美的,Doxygen 也不是。有时 Doxygen 会出错,例如在以下示例中:

class foo
{
private:
    class bar;
};

Doxygen 错误地报告bar具有公共范围(也在此处报告 https://bit.ly/2BWPllZ)。要处理此类问题,在 Doxygen 中修复之前,您可以执行以下操作:

将 API 的补丁列表添加到您的conf.py文件中。扩展之前的示例,我们可以添加以下修复:

wurfapi = {
  'source_paths': ['../src'],
  'recursive': True,
  'parser': {
    'type': 'doxygen', 'download': True,  'warnings_as_error': True,
     'patch_api': [
      {'selector': 'foo::bar', 'key': 'access', 'value': 'private'}
    ]
  }
}

patch_api允许您访问已解析的 API 信息并更新某些值选择是您要更新的实体的唯一名称。进一步查看“字典布局”部分以获取更多信息。

折叠内联命名空间

对于符号版本控制,您可以使用内联命名空间,但通常您不希望这些出现在文档中,因为这些对您的用户来说大多是不可见的。

使用wurfapi您可以折叠内联命名空间,以便将其从范围等中删除。

例子:

namespace foo { inline namespace v1_2_3 { struct bar{}; } }

bar 的范围是foo::v1_2_3。如果您折叠内联命名空间,它将只是foo

您必须处理的第一个问题是 Doxygen 目前不支持内联命名空间。所以我们需要先给 API 打补丁:

wurfapi = {
  'source_paths': ['../src'],
  'recursive': True,
  'parser': {
    'type': 'doxygen', 'download': True,  'warnings_as_error': True,
     'patch_api': [
      {'selector': 'foo::v1_2_3', 'key': 'inline', 'value': True}
    ]
  }
}

在此之后,我们可以折叠命名空间:

wurfapi = {
  'source_paths': ['../src'],
  'recursive': True,
  'parser': {
    'type': 'doxygen', 'download': True,  'warnings_as_error': True,
     'patch_api': [
      {'selector': 'foo::v1_2_3', 'key': 'inline', 'value': True}
    ],
    'collapse_inline_namespaces': [
      "foo::v1_2_3"
    ]
  }
}

现在您可以将bar称为foo::bar。请注意,折叠命名空间会影响您在生成文档时编写的选择器。

自定义模板

您可以编写自己的自定义模板来生成第一个输出。为此,您只需编写一个兼容 Jinja2 的 rst 模板并将其放在某个文件夹中。将user_templates键添加到conf.py文件中的wurfapi 配置字典将使其可用。

例如:

wurfapi = {
    'source_paths': ['../src', '../examples/header/header.h'],
    'recursive': True,
    'user_templates': 'rst_templates',
    'parser': {
        'type': 'doxygen', 'download': True, 'warnings_as_error': True
    }
}

exclude_patterns = ['rst_templates/*.rst']

现在我们可以在rst_templates文件夹中使用*.rst文件,例如,如果我们有一个class_list.rst模板,我们可以像这样使用它:

.. wurfapi:: class_list.rst
    :selector: project::coffee

发布新版本

  1. 编辑NEWS.rstwscriptsrc/wurfapi/wurfapi.py(设置正确的VERSION

  2. ./waf upload

源代码

测试

通过将--run_tests传递给 waf,测试将自动运行:

./waf --run_tests

这遵循了似乎是“最佳实践”的建议,即在 virtualenv 中以可编辑模式安装包。

录音

一堆测试使用一个名为Record的类,在 ( test/record.py ) 中定义。Record类用于将输出存储为来自不同解析和渲染操作的文件。

例如,假设我们要确保解析器函数返回某个 dict对象。然后我们可以记录那个dict

recorder = record.Record(filename='test.json',
                         recording_path='/tmp/recording',
                         mismatch_path='/tmp/mismatch')

recorder.record(data={'foo': 2, 'bar': 3})

如果与之前的记录相比数据发生变化,则会检测到不匹配。要更新录音,只需删除录音文件。

测试目录

您还会注意到一堆测试采用了一个名为 testdirectory的参数。testdirectory是一个pytest夹具,它代表文件系统上的一个临时目录。运行测试时,您会注意到这些临时测试目录弹出 在项目根目录下的pytest_temp目录下。

您可以在此处阅读更多相关信息:

开发人员说明

关于创建扩展 的sphinx文档: http ://www.sphinx-doc.org/en/stable/extdev/index.html#dev-extensions

字典布局

我们希望支持不同的“后端”,例如 Doxygen 来解析源代码。为了使这成为可能,我们定义了一个内部源代码描述格式。然后我们将例如 Doxygen XML 翻译为此并使用它来呈现 API 文档。

这样,可以使用不同的“后端”(例如 Doxygen2)作为源代码解析器,并且可以生成 API 文档。

唯一名称

为了能够引用 API 中的不同实体,我们需要为它们分配一个名称。

我们在这里使用与standardese中描述的类似方法。

这意味着实体的唯一名称是具有所有范围的名称,例如foo::bar::baz

  • 对于函数,唯一名称包含签名(参数类型和成员函数 cv-qualifier 和 ref-qualifier),例如foo::bar::baz::func()foo::bar::baz::func(int a , 字符*) 常量。有关详细信息,请参阅cppreference

  • 对于类模板特化,唯一名称包括特化参数。例如:

    // Here the unique-name is just 'foo'
    template<class T>
    class foo {};
    
    // Here the unique name is foo<int>
    template<>
    class foo<int> {};
  • 除了类型之外,我们还有已解析文件的条目。对于文件,唯一名称将是项目根目录的相对路径。

  • 对于定义,我们将使用定义的名称。举个例子:

    #define PROJECT_VERSION "1.0.0"

    这里唯一名称将是PROJECT_VERSION

API 字典

内部结构是具有不同 API 实体的字典。实体的 唯一名称是键,实体类型也是 Python 字典的值,例如:

api = {
  'unique-name': { ... },
  'unique-name': { ... },
  ...
}

为了使这一点更具体,请考虑以下代码:

namespace ns1
{
  class shape
  {
    void print(int a) const;
  };

  namespace ns2
  {
    struct box
    {
      void hello();
    };

    void print();
  }
}

解析上述代码将生成以下 API 字典:

api = {
  'ns1': { 'kind': 'namespace', ...},
  'ns1::shape': { 'kind': 'class', ... },
  'ns1::shape::print(int) const': { kind': function' ... },
  'ns1::ns2': { 'kind': 'namespace', ... },
  'ns1::ns2::box': { 'kind': 'struct', ... },
  'ns1::ns2::box::hello()': { kind': function' ... },
  'ns1::ns2::print()': { 'kind': 'function', ...},
  'ns1.hpp': { 'kind': 'file', ...}
}

不同的实体类型公开了有关 API 的不同信息。我们将在下面记录不同的类型。

我们将一些键设置为可选的,其标记方式如下:

api = {
  'unique-name': {
    'some_key': ...
    Optional('an_optional_key'): ...
  },
  ...
}

命名空间种类

表示 C++ 命名空间的 Python 字典:

info = {
  'kind': 'namespace',
  'name': 'unqualified-name',
  'scope': 'unique-name' | None,
  'members: [ 'unique-name', 'unique-name' ],
  'briefdescription': paragraphs,
  'detaileddescription': paragraphs,
  'inline': True | False
}

注意:目前 Doxygen 不支持解析内联命名空间。因此,您需要使用补丁 API 手动将值从False更改为True 。也许在某个时候https://github.com/doxygen/doxygen/issues/6741 它会被支持。

班级| 结构种类

表示 C++ 类或结构的 Python 字典:

info = {
  'kind': 'class' | 'struct',
  'name': 'unqualified-name',
  'location': location,
  'scope': 'unique-name' | None,
  'access': 'public' | 'protected' | 'private',
  Optional('template_parameters'): template_parameters,
  'members: [ 'unique-name', 'unique-name' ],
  'briefdescription': paragraphs,
  'detaileddescription': paragraphs
}

枚举| 枚举类种类

表示 C++ 枚举或枚举类的 Python 字典:

info = {
  'kind': 'enum',
  'name': 'unqualified-name',
  'location': location,
  'scope': 'unique-name' | None,
  'access': 'public' | 'protected' | 'private',
  'values: [
    {
      'name': 'somename',
      'briefdescription': paragraphs,
      'detaileddescription': paragraphs,
      Optional('value'): 'some value'
    }
   ],
  'briefdescription': paragraphs,
  'detaileddescription': paragraphs
}

类型定义| 使用种类

表示 C++ using 或 typedef 语句的 Python 字典:

info = {
  'kind': 'typedef' | 'using',
  'name': 'unqualified-name',
  'location': location,
  'scope': 'unique-name' | None,
  'access': 'public' | 'protected' | 'private',
  'type': type,
  'briefdescription': paragraphs,
  'detaileddescription': paragraphs
}

定义种类

代表 C/C++ 的 Python 字典定义:

info = {
  'kind': 'define',
  'name': 'name',
  'location': location,
  Optional('initializer'): 'some_value',
  Optional('parameters'): [{
      'name': 'somestring',
      Optional('description'): paragraphs
  }],
  'briefdescription': paragraphs,
  'detaileddescription': paragraphs
}

定义的内容将在初始化字段中。如果定义采用记录的参数,这些参数将位于参数键下。

例子:

  1. 定义初始化器:

    #define VERSION "1.0.2"
  2. 用参数定义初始化器:

    #define min(X, Y)  ((X) < (Y) ? (X) : (Y))

文件种类

表示项目中文件的 Python 字典:

info = {
  'kind': 'file',
  'name': 'somefile.hpp',
  'path': 'relative/path/to/somefile.hpp',
}

功能种类

表示 C++ 函数的 Python 字典:

  info = {
    'kind': 'function',
    'name': 'unqualified-name',
    'location': location,
    'scope': 'unique-name' | None,
    Optional('return'): {
      'type': type,
      'description': paragraphs
    }
    Optional('template_parameters'): template_parameters,
    'is_const': True | False,
    'is_static': True | False,
    'is_virtual': True | False,
    'is_explicit': True | False,
    'is_inline': True | False,
    'is_constructor': True | False,
    'is_destructor': True | False,
    'trailing_return': True | False,
    'access': 'public' | 'protected' | 'private',
    'briefdescription: paragraphs,
    'detaileddescription: paragraphs,
    'parameters': [
      { 'type': type, Optional('name'): 'somename', 'description': paragraphs },
      ...
    ]
}

如果函数是构造函数或析构函数,则返回键是可选的

可变种类

表示 C++ 变量的 Python 字典:

info = {
  'kind': 'variable',
  'name': 'unqualified-name',
  Optional('value'): 'some value',
  'type': type,
  'location': location,
  'is_static': True | False,
  'is_mutable': True | False,
  'is_volatile': True | False,
  'is_const': True | False,
  'is_constexpr': True | False,
  'scope': 'unique-name' | None,
  'access': 'public' | 'protected' | 'private',
  'briefdescription: paragraphs,
  'detaileddescription: paragraphs,
}

位置项目

表示位置的 Python 字典:

location = {
  Optional('include'): 'some/header.h',
  'path': 'src/project/header.h',
  'line': 10
  }
  • 包含将与 Sphinx conf.py中wurfapi字典中指定的 任何include_paths相关。

  • 路径将相对于项目根文件夹。

类型项目

表示 C++ 类型的 Python 列表:

type = [
  {
    'value': 'sometext',
    Optional('link'): link
  }, ...
]

将类型作为项目列表,我们可以创建到嵌套类型的链接,例如说我们有一个std::unique_ptr<impl>并且我们希望将impl设为链接。这可能看起来像:

"type": [
  {
    "value": "std::unique_ptr<"
  },
  {
    "link": {"url": False, "value": "project::impl"},
    "value": "impl"
  },
  {
    "value": ">"
  }
]

类型列表中的任何空格都应从 Doxygen 输出一直保留到类型列表中。首先,简单地输出类型的值就足够了。不应注入空格或其他内容。

参数

表示函数参数的字典:

parameter = {
  'type': type,
  Optional('name'): 'somestring',
  Optional('description'): paragraphs
}

对于参数,名称也包含在类型列表中。原因是某些参数可能非常复杂,名称嵌入在类型中,例如:

void function(int (*(*foo)())[3]);

这是一个函数,它接受一个参数foo,它是指针函数,它返回指向 int 数组 3 的指针 - 不错吧?无论如何,在这种情况下,参数名称嵌入在参数的类型中。因此,我们采取了简单的做法,wurfapi将始终在类型中包含参数名称。

例如,函数void test(int b)的参数字典 可以是:

{
   'type': [{'value': 'int '}, {'value': 'b'}],
   'name': 'b'
}

模板参数

表示模板参数的 Python 字典列表:

template_parameters = [{
  'type': type,
  'name': 'somestring',
  Optional('default'): type,
  Optional('description'): paragraphs
}]

文字信息

文本信息存储在列表段落中:

paragraphs = [paragraph]

一个段落由一系列段落元素组成:

paragraph = [
      {
        "kind": "text" | "code" | "list" | "bold" | "italic",
        ...
      },
    ]

段落元素可以是“文本”、“代码”或“列表”三种类型之一:

text = {
  'kind': 'text',
  'content': 'hello',
  Optional('link'): link
  }

code = {
  'kind': 'code',
  'content': 'void print();',
  'is_block': true | false
}

list = {
  'kind': 'list',
  'ordered': true | false,
  'items': [paragraphs] # Each item is a list of paragraphs
}

函数的唯一名称问题

问题等效的 C++ 函数签名可以用多种不同的方式编写:

void hello(const int *x); // x is a pointer to const int
void hello(int const *x); // x is a pointer to const int

我们还可以将星号 ( * ) 向左移动:

void hello(const int* x); // x is a pointer to const int
void hello(int const* x); // x is a pointer to const int

因此,在将函数签名转换为unique-name时,我们需要一些方法来规范化函数签名。我们不能简单地依赖刺痛比较。

根据大量的谷歌搜索,很难为此编写正则表达式。相反,我们将尝试使用解析器:

我们只需要解析表示为 http://www.externsoft.ch/media/swf/cpp11-iso.html#parameters_and_qualifiers的函数参数列表。

生成的输出

由于我们将使用 Doxygen 的 XML 输出作为扩展的输入,我们需要一个地方来存储它。我们将它存储在系统临时文件夹中,例如,如果项目名称在 Linux 上为“foobar”, 则为 /tmp/wurfapi-foobar-123456,其中123456是源目录路径的哈希值。除了 Doxygen 的 XML,我们还为不同的指令存储生成的 rst。这对于调试以查看我们是否生成损坏的 rst 非常有用。

json 格式的 API 可以在_build/.doctree/wurfapi_api.json中找到。

路径和目录

  • 源目录:在 Sphinx 中,源目录是我们的 .rst 文件所在的位置。这是您在构建文档时传递给sphinx-build的内容。我们将在我们的扩展中使用它来查找 C++ 源代码和输出自定义模板。

笔记

项目详情


下载文件

下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。

内置分布

wurfapi-9.0.0-py2.py3-none-any.whl (47.8 kB 查看哈希

已上传 py2 py3