add doc generator

This commit is contained in:
Laurent Sansonetti
2011-07-14 17:25:43 -07:00
parent 99c897db44
commit f92d3fa7d6

View File

@@ -1,11 +1,177 @@
platforms_dir = ENV['platforms_dir']
sdk_version = ENV['sdk_version']
DOCSET_PATHS = %w{
/Library/Developer/Documentation/DocSets/com.apple.adc.documentation.AppleiOS4_3.iOSLibrary.docset/Contents/Resources/Documents/documentation/UIKit/Reference
/Library/Developer/Documentation/DocSets/com.apple.adc.documentation.AppleiOS4_3.iOSLibrary.docset/Contents/Resources/Documents/documentation/Cocoa/Reference
}
class DocGenerator
require 'rubygems'
require 'nokogiri'
require 'fileutils'
def parse_html_docref(node)
code = ''
code << node.xpath(".//p[@class='abstract']").text
code << "\n"
code << node.xpath(".//div[@class='api discussion']").text.sub(/^Discussion/, '')
code.strip!
code.gsub!(/^/m, ' # ')
code << "\n"
return code
end
def parse_type(type)
type = type.to_s
type.strip!
star = type.sub!(/\s*\*$/, '') # Remove pointer star.
case type
when /\*$/
# A double pointer, in MacRuby this becomes a Pointer.
'Pointer'
when 'id'
'Object'
when 'void'
'nil'
when 'SEL'
'Symbol'
when 'bool', 'BOOL'
'Boolean'
when 'float', 'double', 'CGFloat'
'Float'
when 'int', 'short', 'long', 'long long', 'NSInteger', 'NSUInteger'
'Integer'
when 'NSString', 'NSMutableString'
'String'
when 'NSArray', 'NSMutableArray'
'Array'
when 'NSDictionary', 'NSMutableDictionary'
'Hash'
else
type
end
end
def parse_html_class(name, doc)
# Find superclass (mandatory).
sclass = nil
doc.xpath("//table[@class='specbox']/tr").each do |node|
if md = node.text.match(/Inherits from([^ ]+)/)
sclass = md[1]
break
end
end
return nil unless sclass
code = doc.xpath(".//p[@class='abstract']")[0].text
code.gsub!(/^/m, '# ')
code << "\nclass #{name} < #{sclass}\n\n"
# Properties.
doc.xpath("//div[@class='api propertyObjC']").each do |node|
decl = node.xpath(".//div[@class='declaration']/div[@class='declaration']").text
readonly = decl.include?('readonly')
decl.sub!(/@property\s*(\([^\)]+\))?/, '')
md = decl.match(/(\w+)$/)
next unless md
title = md[1]
type = md.pre_match
code << parse_html_docref(node)
code << " # @return [#{parse_type(type)}]\n"
code << ' ' << (readonly ? "attr_reader" : "attr_accessor") << " :#{title}\n\n"
end
# Methods.
methods = []
methods.concat(doc.xpath("//div[@class='api classMethod']"))
methods.concat(doc.xpath("//div[@class='api instanceMethod']"))
methods.each do |node|
decl = node.xpath(".//div[@class='declaration']").text
types = decl.scan(/\(([^)]+)\)/)
ret_type = types.shift
# Docref.
code << parse_html_docref(node)
# Parameters and return value.
arg_names = node.xpath(".//div[@class='api parameters']//dt")
arg_docs = node.xpath(".//div[@class='api parameters']//dd")
if arg_names.size == arg_docs.size
has_types = types.size == arg_names.size
arg_names.each_with_index do |arg_name, i|
arg_doc = arg_docs[i]
code << " # @param "
code << "[#{parse_type(types[i])}] " if has_types
code << "#{arg_name.text} #{arg_doc.text}\n"
end
end
retdoc = node.xpath(".//div[@class='return_value']/p").text.strip
code << " # @return "
code << "[#{parse_type(ret_type)}] " if ret_type
code << "#{retdoc}" unless retdoc.empty?
code << "\n"
is_class_method = decl.match(/^\s*\+/) != nil
decl.sub!(/^\s*[\+\-]/, '') # Remove method qualifier.
sel_parts = decl.gsub(/\([^)]+\)/, '').split.map { |x| x.split(':') }
head = sel_parts.shift
code << " def #{is_class_method ? 'self.' : ''}#{head[0]}("
code << "#{head[1]}" if head.size > 1
unless sel_parts.empty?
code << ', '
code << sel_parts.map { |part| "#{part[0]}:#{part[1]}" }.join(', ')
end
code << "); end\n\n"
end
code << "end"
return code
end
def parse_html_data(data)
doc = Nokogiri::HTML(data)
title = doc.xpath('/html/head/title')
if title and md = title.text.match(/^(.+)Class Reference$/)
parse_html_class(md[1].strip, doc)
else
nil
end
end
def initialize(paths)
@input_paths = []
paths.each do |path|
if File.directory?(path)
@input_paths.concat(Dir.glob(path + '/**/*.html'))
else
@input_paths << path
end
end
end
def run
rb_files_dir = '/tmp/rb_docset'
FileUtils.rm_rf(rb_files_dir)
FileUtils.mkdir_p(rb_files_dir)
@input_paths.map { |path| parse_html_data(File.read(path)) }.compact.each_with_index do |code, n|
File.open(File.join(rb_files_dir, "t#{n}.rb"), 'w') do |io|
io.write(code)
end
end
sh "yard doc #{rb_files_dir}"
sh "mv doc html"
end
end
task :default => :all
task :all do
# TODO
if !File.exist?('html')
DocGenerator.new(DOCSET_PATHS).run
end
end
task :clean do
# TODO
rm_rf 'html'
end