2022-02-05 11:40:09 -05:00
|
|
|
|
#!/usr/bin/env ruby
|
2024-03-21 09:30:47 -04:00
|
|
|
|
|
|
|
|
|
# SPDX-FileCopyrightText: 2024 Redict Contributors
|
|
|
|
|
# SPDX-FileCopyrightText: 2024 Salvatore Sanfilippo <antirez at gmail dot com>
|
|
|
|
|
#
|
|
|
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
# SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
|
|
2021-04-13 17:58:05 -04:00
|
|
|
|
# coding: utf-8
|
2016-04-20 13:06:19 -04:00
|
|
|
|
# gendoc.rb -- Converts the top-comments inside module.c to modules API
|
2018-07-01 01:24:50 -04:00
|
|
|
|
# reference documentation in markdown format.
|
2016-04-20 13:06:19 -04:00
|
|
|
|
|
|
|
|
|
# Convert the C comment to markdown
|
|
|
|
|
def markdown(s)
|
|
|
|
|
s = s.gsub(/\*\/$/,"")
|
2021-01-13 09:14:51 -05:00
|
|
|
|
s = s.gsub(/^ ?\* ?/,"")
|
|
|
|
|
s = s.gsub(/^\/\*\*? ?/,"")
|
2016-04-20 13:06:19 -04:00
|
|
|
|
s.chop! while s[-1] == "\n" || s[-1] == " "
|
2017-07-14 05:29:28 -04:00
|
|
|
|
lines = s.split("\n")
|
|
|
|
|
newlines = []
|
2022-06-21 10:00:24 -04:00
|
|
|
|
# Fix some markdown
|
2017-07-14 05:29:28 -04:00
|
|
|
|
lines.each{|l|
|
2022-06-21 10:00:24 -04:00
|
|
|
|
# Rewrite RM_Xyz() to RedisModule_Xyz().
|
|
|
|
|
l = l.gsub(/(?<![A-Z_])RM_(?=[A-Z])/, 'RedisModule_')
|
|
|
|
|
# Fix more markdown, except in code blocks indented by 4 spaces, which we
|
|
|
|
|
# don't want to mess with.
|
2021-01-13 09:14:51 -05:00
|
|
|
|
if not l.start_with?(' ')
|
2021-01-20 04:47:06 -05:00
|
|
|
|
# Add backquotes around RedisModule functions and type where missing.
|
|
|
|
|
l = l.gsub(/(?<!`)RedisModule[A-z]+(?:\*?\(\))?/){|x| "`#{x}`"}
|
|
|
|
|
# Add backquotes around c functions like malloc() where missing.
|
2022-06-08 08:30:04 -04:00
|
|
|
|
l = l.gsub(/(?<![`A-z.])[a-z_]+\(\)/, '`\0`')
|
2021-01-20 04:47:06 -05:00
|
|
|
|
# Add backquotes around macro and var names containing underscores.
|
|
|
|
|
l = l.gsub(/(?<![`A-z\*])[A-Za-z]+_[A-Za-z0-9_]+/){|x| "`#{x}`"}
|
2021-04-13 17:58:05 -04:00
|
|
|
|
# Link URLs preceded by space or newline (not already linked)
|
2021-04-19 14:35:14 -04:00
|
|
|
|
l = l.gsub(/(^| )(https?:\/\/[A-Za-z0-9_\/\.\-]+[A-Za-z0-9\/])/,
|
|
|
|
|
'\1[\2](\2)')
|
2021-04-13 17:58:05 -04:00
|
|
|
|
# Replace double-dash with unicode ndash
|
|
|
|
|
l = l.gsub(/ -- /, ' – ')
|
2017-07-14 05:29:28 -04:00
|
|
|
|
end
|
2021-04-13 17:58:05 -04:00
|
|
|
|
# Link function names to their definition within the page
|
|
|
|
|
l = l.gsub(/`(RedisModule_[A-z0-9]+)[()]*`/) {|x|
|
|
|
|
|
$index[$1] ? "[#{x}](\##{$1})" : x
|
|
|
|
|
}
|
2017-07-14 05:29:28 -04:00
|
|
|
|
newlines << l
|
|
|
|
|
}
|
|
|
|
|
return newlines.join("\n")
|
2016-04-20 13:06:19 -04:00
|
|
|
|
end
|
|
|
|
|
|
2021-04-13 17:58:05 -04:00
|
|
|
|
# Linebreak a prototype longer than 80 characters on the commas, but only
|
|
|
|
|
# between balanced parentheses so that we don't linebreak args which are
|
|
|
|
|
# function pointers, and then aligning each arg under each other.
|
|
|
|
|
def linebreak_proto(proto, indent)
|
|
|
|
|
if proto.bytesize <= 80
|
|
|
|
|
return proto
|
|
|
|
|
end
|
|
|
|
|
parts = proto.split(/,\s*/);
|
|
|
|
|
if parts.length == 1
|
|
|
|
|
return proto;
|
|
|
|
|
end
|
|
|
|
|
align_pos = proto.index("(") + 1;
|
|
|
|
|
align = " " * align_pos
|
|
|
|
|
result = parts.shift;
|
|
|
|
|
bracket_balance = 0;
|
|
|
|
|
parts.each{|part|
|
|
|
|
|
if bracket_balance == 0
|
|
|
|
|
result += ",\n" + indent + align
|
|
|
|
|
else
|
|
|
|
|
result += ", "
|
|
|
|
|
end
|
|
|
|
|
result += part
|
|
|
|
|
bracket_balance += part.count("(") - part.count(")")
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
end
|
|
|
|
|
|
2016-04-20 13:06:19 -04:00
|
|
|
|
# Given the source code array and the index at which an exported symbol was
|
|
|
|
|
# detected, extracts and outputs the documentation.
|
|
|
|
|
def docufy(src,i)
|
|
|
|
|
m = /RM_[A-z0-9]+/.match(src[i])
|
2017-07-14 05:29:28 -04:00
|
|
|
|
name = m[0]
|
|
|
|
|
name = name.sub("RM_","RedisModule_")
|
2016-04-20 13:06:19 -04:00
|
|
|
|
proto = src[i].sub("{","").strip+";\n"
|
2017-07-14 05:29:28 -04:00
|
|
|
|
proto = proto.sub("RM_","RedisModule_")
|
2021-04-13 17:58:05 -04:00
|
|
|
|
proto = linebreak_proto(proto, " ");
|
|
|
|
|
# Add a link target with the function name. (We don't trust the exact id of
|
|
|
|
|
# the generated one, which depends on the Markdown implementation.)
|
|
|
|
|
puts "<span id=\"#{name}\"></span>\n\n"
|
|
|
|
|
puts "### `#{name}`\n\n"
|
2016-04-20 13:06:19 -04:00
|
|
|
|
puts " #{proto}\n"
|
2022-05-03 03:34:18 -04:00
|
|
|
|
puts "**Available since:** #{$since[name] or "unreleased"}\n\n"
|
2016-04-20 13:06:19 -04:00
|
|
|
|
comment = ""
|
|
|
|
|
while true
|
|
|
|
|
i = i-1
|
|
|
|
|
comment = src[i]+comment
|
|
|
|
|
break if src[i] =~ /\/\*/
|
|
|
|
|
end
|
|
|
|
|
comment = markdown(comment)
|
|
|
|
|
puts comment+"\n\n"
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-13 17:58:05 -04:00
|
|
|
|
# Print a comment from line until */ is found, as markdown.
|
|
|
|
|
def section_doc(src, i)
|
|
|
|
|
name = get_section_heading(src, i)
|
|
|
|
|
comment = "<span id=\"#{section_name_to_id(name)}\"></span>\n\n"
|
|
|
|
|
while true
|
|
|
|
|
# append line, except if it's a horizontal divider
|
|
|
|
|
comment = comment + src[i] if src[i] !~ /^[\/ ]?\*{1,2} ?-{50,}/
|
|
|
|
|
break if src[i] =~ /\*\//
|
|
|
|
|
i = i+1
|
|
|
|
|
end
|
|
|
|
|
comment = markdown(comment)
|
|
|
|
|
puts comment+"\n\n"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# generates an id suitable for links within the page
|
|
|
|
|
def section_name_to_id(name)
|
|
|
|
|
return "section-" +
|
|
|
|
|
name.strip.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/^-+|-+$/, '')
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Returns the name of the first section heading in the comment block for which
|
|
|
|
|
# is_section_doc(src, i) is true
|
|
|
|
|
def get_section_heading(src, i)
|
|
|
|
|
if src[i] =~ /^\/\*\*? \#+ *(.*)/
|
|
|
|
|
heading = $1
|
|
|
|
|
elsif src[i+1] =~ /^ ?\* \#+ *(.*)/
|
|
|
|
|
heading = $1
|
|
|
|
|
end
|
|
|
|
|
return heading.gsub(' -- ', ' – ')
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Returns true if the line is the start of a generic documentation section. Such
|
|
|
|
|
# section must start with the # symbol, i.e. a markdown heading, on the first or
|
|
|
|
|
# the second line.
|
|
|
|
|
def is_section_doc(src, i)
|
|
|
|
|
return src[i] =~ /^\/\*\*? \#/ ||
|
|
|
|
|
(src[i] =~ /^\/\*/ && src[i+1] =~ /^ ?\* \#/)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def is_func_line(src, i)
|
|
|
|
|
line = src[i]
|
|
|
|
|
return line =~ /RM_/ &&
|
|
|
|
|
line[0] != ' ' && line[0] != '#' && line[0] != '/' &&
|
|
|
|
|
src[i-1] =~ /\*\//
|
|
|
|
|
end
|
|
|
|
|
|
2022-04-28 01:16:20 -04:00
|
|
|
|
puts "---\n"
|
2022-05-03 03:34:18 -04:00
|
|
|
|
puts "title: \"Modules API reference\"\n"
|
|
|
|
|
puts "linkTitle: \"API reference\"\n"
|
2022-04-28 01:16:20 -04:00
|
|
|
|
puts "weight: 1\n"
|
|
|
|
|
puts "description: >\n"
|
|
|
|
|
puts " Reference for the Redis Modules API\n"
|
|
|
|
|
puts "aliases:\n"
|
|
|
|
|
puts " - /topics/modules-api-ref\n"
|
|
|
|
|
puts "---\n"
|
|
|
|
|
puts "\n"
|
2022-02-03 03:25:37 -05:00
|
|
|
|
puts "<!-- This file is generated from module.c using\n"
|
|
|
|
|
puts " utils/generate-module-api-doc.rb -->\n\n"
|
|
|
|
|
src = File.open(File.dirname(__FILE__) ++ "/../src/module.c").to_a
|
2021-04-13 17:58:05 -04:00
|
|
|
|
|
|
|
|
|
# Build function index
|
|
|
|
|
$index = {}
|
|
|
|
|
src.each_with_index do |line,i|
|
|
|
|
|
if is_func_line(src, i)
|
|
|
|
|
line =~ /RM_([A-z0-9]+)/
|
|
|
|
|
name = "RedisModule_#{$1}"
|
|
|
|
|
$index[name] = true
|
2016-04-20 13:06:19 -04:00
|
|
|
|
end
|
2021-04-13 17:58:05 -04:00
|
|
|
|
end
|
|
|
|
|
|
2022-02-03 03:25:37 -05:00
|
|
|
|
# Populate the 'since' map (name => version) if we're in a git repo.
|
|
|
|
|
$since = {}
|
|
|
|
|
git_dir = File.dirname(__FILE__) ++ "/../.git"
|
|
|
|
|
if File.directory?(git_dir) && `which git` != ""
|
|
|
|
|
`git --git-dir="#{git_dir}" tag --sort=v:refname`.each_line do |version|
|
|
|
|
|
next if version !~ /^(\d+)\.\d+\.\d+?$/ || $1.to_i < 4
|
|
|
|
|
version.chomp!
|
|
|
|
|
`git --git-dir="#{git_dir}" cat-file blob "#{version}:src/module.c"`.each_line do |line|
|
|
|
|
|
if line =~ /^\w.*[ \*]RM_([A-z0-9]+)/
|
|
|
|
|
name = "RedisModule_#{$1}"
|
|
|
|
|
if ! $since[name]
|
|
|
|
|
$since[name] = version
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-13 17:58:05 -04:00
|
|
|
|
# Print TOC
|
|
|
|
|
puts "## Sections\n\n"
|
|
|
|
|
src.each_with_index do |_line,i|
|
|
|
|
|
if is_section_doc(src, i)
|
|
|
|
|
name = get_section_heading(src, i)
|
|
|
|
|
puts "* [#{name}](\##{section_name_to_id(name)})\n"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
puts "* [Function index](#section-function-index)\n\n"
|
|
|
|
|
|
|
|
|
|
# Docufy: Print function prototype and markdown docs
|
|
|
|
|
src.each_with_index do |_line,i|
|
|
|
|
|
if is_func_line(src, i)
|
|
|
|
|
docufy(src, i)
|
|
|
|
|
elsif is_section_doc(src, i)
|
|
|
|
|
section_doc(src, i)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# Print function index
|
|
|
|
|
puts "<span id=\"section-function-index\"></span>\n\n"
|
|
|
|
|
puts "## Function index\n\n"
|
|
|
|
|
$index.keys.sort.each{|x| puts "* [`#{x}`](\##{x})\n"}
|
|
|
|
|
puts "\n"
|