diff options
Diffstat (limited to '')
-rwxr-xr-x | utils/generate-module-api-doc.rb | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/utils/generate-module-api-doc.rb b/utils/generate-module-api-doc.rb new file mode 100755 index 0000000..8829434 --- /dev/null +++ b/utils/generate-module-api-doc.rb @@ -0,0 +1,205 @@ +#!/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 "---\n" +puts "title: \"Modules API reference\"\n" +puts "linkTitle: \"API reference\"\n" +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" +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 + +# 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" |