自定义语法高亮适配器

您可以通过实现和注册一个语法高亮适配器来将其他语法高亮集成到 Asciidoctor 中。您可以从头开始编写一个新的适配器,也可以扩展甚至替换内置的适配器之一。

创建新适配器

要实现新适配器,您必须创建一个扩展 Asciidoctor::SyntaxHighlighter::Base 类的类,为 source-highlighter 属性值注册该适配器,并实现所需的方法。所需方法取决于适配器是用于客户端(在浏览器中运行)还是构建时(在文档转换时运行)的语法高亮。

以下是一个为 Prism.js 语法高亮库编写和注册语法高亮适配器的示例。Prism.js 是一个客户端语法高亮器,这意味着它在浏览器中运行。因此,适配器只需实现与客户端语法高亮相关的方法,包括 formatdocinfo?docinfo

示例 1. Prism.js 的语法高亮适配器
class PrismSyntaxHighlighter < Asciidoctor::SyntaxHighlighter::Base
  register_for 'prism'

  def format node, lang, opts
    opts[:transform] = proc do |pre, code|
      code['class'] = %(language-#{lang}) if lang
    end
    super
  end

  def docinfo? location
    location == :footer
  end

  def docinfo location, doc, opts
    base_url = doc.attr 'prismdir', %(#{opts[:cdn_base_url]}/prism/1.15.0)
    slash = opts[:self_closing_tag_slash]
    unless (theme_name = doc.attr 'prism-style', 'prism') == 'prism'
      theme_name = %(prism-#{theme_name})
    end
    %(<link rel="stylesheet" href="#{base_url}/themes/#{theme_name}.min.css"#{slash}>
<script src="#{base_url}/prism.min.js"></script>
<script src="#{base_url}/components/prism-ruby.min.js"></script>)
  end
end

将此代码保存到名为 prism-syntax-highlighter.rb 的文件中。然后在调用 Asciidoctor 时要求加载此文件,并将 source-highlighter=prism 设置为激活它。

$ asciidoctor -r ./prism-syntax-highlighter -a source-highlighter=prism document.adoc

您还可以为在转换期间运行的语法高亮器定义一个适配器。我们将在此过程中探讨如何扩展内置适配器。

扩展现有适配器

与创建新适配器不同,您可以自定义内置适配器,方法是扩展它、覆盖其行为,并可选择替换它。

要扩展适配器,您需要使用 Asciidoctor::SyntaxHighlighter.for 方法按名称查找内置适配器的引用,创建一个扩展它的类,使用唯一名称(如果您想替换它,也可以使用相同名称)注册适配器,并覆盖提供您要修改的行为的任何方法。

这是自定义现有适配器的基本模板

class CustomAdapter < (Asciidoctor::SyntaxHighlighter.for 'rouge')
  register_for 'rouge'

  # override methods go here
end

让我们看一些自定义内置适配器的示例。

docinfo

让我们覆盖 Pygments 的适配器,以防止它向 HTML 添加样式表(可能因为样式将由不同的样式表提供)。

示例 2. Pygments 的扩展语法高亮适配器
class ExtendedPygmentsSyntaxHighlighter < (Asciidoctor::SyntaxHighlighter.for 'pygments')
  register_for 'pygments'

  def docinfo? location
    false
  end
end

将此代码保存到名为 extended-pygments-syntax-highlighter.rb 的文件中。然后,在调用 Asciidoctor 时要求加载此文件,并将 source-highlighter=pygments 设置为激活它,就像您通常所做的那样。

$ asciidoctor -r ./extended-pygments-syntax-highlighter.rb -a source-highlighter=pygments document.adoc

如果您想修改内置适配器以响应 pygments-stylesheet 属性指定的自定义样式表的位置,您可以通过扩展适配器并覆盖 docinfo 方法来实现。

class ExtendedPygmentsSyntaxHighlighter < (Asciidoctor::SyntaxHighlighter.for 'pygments')
  register_for :pygments

  def docinfo location, doc, opts
    stylesheet = doc.attr 'pygments-stylesheet', './pygments.css'
    if opts[:linkcss]
      slash = opts[:self_closing_tag_slash]
      %(<link rel="stylesheet" href="#{stylesheet}"#{slash}>)
    else
      stylesheet = doc.normalize_system_path stylesheet
      %(<style>
#{doc.read_asset stylesheet, label: 'stylesheet', normalize: true}
</style>)
    end
  end
end

如果您想装饰内置行为,您可以在方法内的任何位置调用 super 方法,以委托给内置适配器提供的行为。

highlight

假设您总是希望行进行编号,无论文档中的设置如何。您可以通过覆盖 highlight 方法来实现这一点,在 opts 参数上设置 :number_lines 键,然后使用 super 委托回内置适配器。

def highlight node, source, lang, opts
  opts[:number_lines] = true
  super
end

create_formatter (Rouge)

当使用 Rouge 作为语法高亮器时,您可以通过覆盖 create_formatter 方法来定制格式化程序。这允许您添加自定义逻辑来处理源代码中的特定令牌。

假设您想查找代码注释中的裸 URL 并将它们转换为链接(即,自动链接),就像在 AsciiDoc 中一样。您可以通过将额外的逻辑编织到格式化程序中来实现,该格式化程序会查找 Comment 类别中的令牌并对该值应用替换。

class ExtendedRougeSyntaxHighlighter < (Asciidoctor::SyntaxHighlighter.for 'rouge')
  register_for 'rouge'

  def create_formatter node, source, lang, opts
    formatter = super
    formatter.singleton_class.prepend (Module.new do
      def safe_span tok, safe_val
        if tok.token_chain[0].matches? ::Rouge::Token::Tokens::Comment
          safe_val = safe_val.gsub(/https?:\/\/\S+/, '<a href="\&">\&</a>')
        end
        super
      end
    end)
    formatter
  end
end

由于格式化程序可以访问语法高亮器识别的所有代码令牌,因此此技术开辟了许多可能性。例如,您可以查找 Java 代码中 Keyword 类别下的 Type 令牌,并创建一个指向 API 文档的链接。create_formatter 方法的 lang 参数会告诉您令牌所属的源语言(例如 java)。

要研究您可能感兴趣覆盖的逻辑,请浏览 内置语法高亮适配器 的代码。