1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
#!/usr/bin/env ruby
# coding: utf-8
# gendoc.rb -- Converts the top-comments inside module.c to modules API
# reference documentation in markdown format.
# Convert the C comment to markdown
def markdown(s)
s = s.gsub(/\*\/$/,"")
s = s.gsub(/^ ?\* ?/,"")
s = s.gsub(/^\/\*\*? ?/,"")
s.chop! while s[-1] == "\n" || s[-1] == " "
lines = s.split("\n")
newlines = []
# Fix some markdown
lines.each{|l|
# 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.
if not l.start_with?(' ')
# 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.
l = l.gsub(/(?<![`A-z.])[a-z_]+\(\)/, '`\0`')
# Add backquotes around macro and var names containing underscores.
l = l.gsub(/(?<![`A-z\*])[A-Za-z]+_[A-Za-z0-9_]+/){|x| "`#{x}`"}
# Link URLs preceded by space or newline (not already linked)
l = l.gsub(/(^| )(https?:\/\/[A-Za-z0-9_\/\.\-]+[A-Za-z0-9\/])/,
'\1[\2](\2)')
# Replace double-dash with unicode ndash
l = l.gsub(/ -- /, ' – ')
end
# Link function names to their definition within the page
l = l.gsub(/`(RedisModule_[A-z0-9]+)[()]*`/) {|x|
$index[$1] ? "[#{x}](\##{$1})" : x
}
newlines << l
}
return newlines.join("\n")
end
# 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
# 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])
name = m[0]
name = name.sub("RM_","RedisModule_")
proto = src[i].sub("{","").strip+";\n"
proto = proto.sub("RM_","RedisModule_")
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"
puts " #{proto}\n"
puts "**Available since:** #{$since[name] or "unreleased"}\n\n"
comment = ""
while true
i = i-1
comment = src[i]+comment
break if src[i] =~ /\/\*/
end
comment = markdown(comment)
puts comment+"\n\n"
end
# 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
puts "<!-- This file is generated from module.c using\n"
puts " redis/redis:utils/generate-module-api-doc.rb -->\n\n"
src = File.open(File.dirname(__FILE__) ++ "/../src/module.c").to_a
# 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
end
end
# 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
# 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"
|