mirror of
https://github.com/zhigang1992/RubyMotion.git
synced 2026-04-05 22:40:25 +08:00
The iOS SDK references might have several structures. In such reference, the description of the members in structure was wrong.
430 lines
12 KiB
Ruby
430 lines
12 KiB
Ruby
class DocsetGenerator
|
|
require 'rubygems'
|
|
require 'nokogiri'
|
|
require 'fileutils'
|
|
|
|
def sanitize(str)
|
|
str.to_s.gsub(/\n/, ' ')
|
|
end
|
|
|
|
def parse_html_docref(node)
|
|
code = ''
|
|
code << node.xpath(".//p[@class='abstract']").text
|
|
code << "\n"
|
|
|
|
node_discussion = node.xpath(".//div[@class='api discussion']")
|
|
node_cdesample = node_discussion.xpath(".//div[@class='codesample clear']")
|
|
node_cdesample.unlink
|
|
|
|
code << node_discussion.text.sub(/^Discussion/, '')
|
|
code.strip!
|
|
code.gsub!(/^/m, ' # ')
|
|
code << "\n"
|
|
return code
|
|
end
|
|
|
|
def parse_type(type)
|
|
if type.kind_of?(Array)
|
|
type = type.first
|
|
end
|
|
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(?:\s*<\w+>)?/
|
|
'Object'
|
|
when 'void'
|
|
'nil'
|
|
when 'SEL'
|
|
'Symbol'
|
|
when 'bool', 'BOOL'
|
|
'Boolean'
|
|
when 'float', 'double', 'CGFloat'
|
|
'Float'
|
|
when /^(?:const\s+)?u?int(?:\d+_t)?/, 'char', 'unichar', 'short', 'long', 'long long', 'unsigned char', 'unsigned short', 'unsigned long', 'unsigned long long', 'NSInteger', 'NSUInteger'
|
|
'Integer'
|
|
when 'NSString', 'NSMutableString'
|
|
'String'
|
|
when 'NSArray', 'NSMutableArray'
|
|
'Array'
|
|
when 'NSDictionary', 'NSMutableDictionary'
|
|
'Hash'
|
|
else
|
|
type
|
|
end
|
|
end
|
|
|
|
def parse_html_property(doc, code = "")
|
|
# Properties.
|
|
doc.xpath("//div[@class='api propertyObjC']").each do |node|
|
|
decl = node.xpath(".//div[@class='declaration']/div[@class='declaration']").text
|
|
if decl.length == 0
|
|
decl = node.xpath(".//div[@class='declaration']").text
|
|
end
|
|
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
|
|
|
|
return code
|
|
end
|
|
|
|
def parse_html_method(doc, code = "")
|
|
# 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} #{sanitize(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 << "#{sanitize(retdoc)}" unless retdoc.empty?
|
|
code << "\n"
|
|
|
|
is_class_method = decl.match(/^\s*\+/) != nil
|
|
code << " # @scope class\n" if is_class_method
|
|
|
|
decl.sub!(/^\s*[\+\-]/, '') # Remove method qualifier.
|
|
decl.sub!(/;\s*$/, '')
|
|
|
|
no_break_space = [0x00A0].pack("U*")
|
|
decl.gsub!(no_break_space, '')
|
|
|
|
sel_parts = decl.gsub(/\([^)]+\)+/, '').split.map { |x| x.split(':') }
|
|
head = sel_parts.shift
|
|
code << " def #{head[0]}("
|
|
code << "#{head[1]}" if head.size > 1
|
|
unless sel_parts.empty?
|
|
code << ', '
|
|
code << sel_parts.map { |part|
|
|
if part[1]
|
|
"#{part[0]}:#{part[1]}"
|
|
else
|
|
part[0]
|
|
end
|
|
}.join(', ')
|
|
end
|
|
code << "); end\n\n"
|
|
end
|
|
|
|
return code
|
|
end
|
|
|
|
def parse_html_constant(doc, code_const = "", code_struct = "")
|
|
doc.xpath("//div[@id='Constants_section']").each do |node|
|
|
node_abstract = node.xpath("./p[@class='abstract']")
|
|
node_declaration = node.xpath("./pre[@class='declaration']")
|
|
node_termdef = node.xpath("./dl[@class='termdef']")
|
|
|
|
node_termdef.size.times do |i|
|
|
decl = node_declaration[i].text.strip
|
|
if decl =~ /^(typedef\s+)?struct/
|
|
parse_html_struct(node.child, code_struct)
|
|
next
|
|
end
|
|
|
|
enum_name = (decl.match(/\}\s*([^\s]+);$/m).to_a)[1]
|
|
is_enum = true if enum_name.to_s.length > 0
|
|
|
|
if is_enum
|
|
code_const << "# #{sanitize(node_abstract[i].text)}\n"
|
|
code_const << "module #{enum_name} # Enumeration\n\n"
|
|
end
|
|
node_name = node_termdef[i].xpath("./dt")
|
|
node_description = node_termdef[i].xpath("./dd")
|
|
node_name.size.times do |i|
|
|
code_const << " # #{sanitize(node_description[i].text.capitalize)}\n"
|
|
code_const << " #{node_name[i].text} = nil\n"
|
|
end
|
|
code_const << "end\n" if is_enum
|
|
end
|
|
end
|
|
|
|
return code_const
|
|
end
|
|
|
|
def find_framework_path(doc)
|
|
elem = doc.xpath(".//span[@class='FrameworkPath']")
|
|
if elem.size > 0
|
|
elem[0].parent.parent.parent.children[1].text
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def parse_html_class_property_common(doc, code)
|
|
code_const = ''
|
|
code_struct = ''
|
|
parse_html_property(doc, code)
|
|
parse_html_method(doc, code)
|
|
parse_html_constant(doc, code_const, code_struct)
|
|
|
|
code << "end\n"
|
|
code << code_const
|
|
code << code_struct
|
|
return code
|
|
end
|
|
|
|
def parse_html_class(name, doc, code)
|
|
# 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
|
|
|
|
# Class abstract.
|
|
code << doc.xpath(".//p[@class='abstract']")[0].text.gsub(/^/m, '# ')
|
|
if sclass == "none"
|
|
code << "\nclass #{name}\n\n"
|
|
else
|
|
code << "\nclass #{name} < #{sclass}\n\n"
|
|
end
|
|
|
|
parse_html_class_property_common(doc, code)
|
|
return code
|
|
end
|
|
|
|
def parse_html_protocol(name, doc, code)
|
|
# Class abstract.
|
|
node = doc.xpath(".//p[@class='abstract']")
|
|
return nil if node.empty?
|
|
|
|
code << node.text.gsub(/^/m, '# ')
|
|
code << "\nmodule #{name} # Protocol\n\n"
|
|
|
|
parse_html_class_property_common(doc, code)
|
|
return code
|
|
end
|
|
|
|
def parse_html_function(doc, code = "")
|
|
node_name = doc.xpath("../h3[@class='tight jump function']")
|
|
node_abstract = doc.xpath("../p[@class='abstract']")
|
|
node_declaration = doc.xpath("../pre[@class='declaration']")
|
|
node_termdef = doc.xpath("../div[@class='api parameters']/dl[@class='termdef']")
|
|
node_return_val = doc.xpath("../div[@class='return_value']/p")
|
|
|
|
node_name.size.times do |i|
|
|
name = node_name[i].text
|
|
abstract = node_abstract[i].text
|
|
declaration = node_declaration[i].text.strip
|
|
|
|
|
|
declaration =~ /([^\s]+)\s+.+/
|
|
return_type = $1
|
|
|
|
declaration =~ /\((.+)\);/mx
|
|
args = $1
|
|
next unless args
|
|
args = args.split(",")
|
|
next unless args.size > 0
|
|
|
|
return_type.strip!
|
|
code << "# #{sanitize(abstract)}\n"
|
|
|
|
node_param_description = node_termdef.xpath("dd")
|
|
params = []
|
|
args.each_with_index do |arg, index|
|
|
arg.strip!
|
|
arg =~ /(.+)\s+([^\s]+),?$/
|
|
type = $1
|
|
param = $2
|
|
next unless param
|
|
|
|
param.sub!(/\*+/, '')
|
|
type << Regexp.last_match.to_s
|
|
params << param
|
|
|
|
description = node_param_description[index].text if node_param_description[index]
|
|
code << "# @param [#{parse_type(type)}] #{param} #{sanitize(description)}\n"
|
|
end
|
|
|
|
if node_return_val[i]
|
|
code << "# @return [#{parse_type(return_type)}] #{sanitize(node_return_val[i].text)}\n"
|
|
elsif return_type != "void"
|
|
code << "# @return [#{parse_type(return_type)}]\n"
|
|
else
|
|
code << "# @return [nil]\n"
|
|
end
|
|
code << "def #{name}("
|
|
if params.size > 0
|
|
params.each do |param|
|
|
code << "#{param}, "
|
|
end
|
|
code.slice!(-2, 2) # remove last ", "
|
|
end
|
|
code << "); end\n\n"
|
|
|
|
end
|
|
|
|
return code
|
|
end
|
|
|
|
def parse_html_struct(doc, code = "")
|
|
node_name = doc.xpath("../h3[@class='tight jump struct']|../h3[@class='tight jump typeDef']")
|
|
node_abstract = doc.xpath("../p[@class='abstract']")
|
|
node_declaration = doc.xpath("../pre[@class='declaration']|../table[@class='zDeclaration']")
|
|
node_termdef = doc.xpath("../dl[@class='termdef']")
|
|
current_member_position = 0
|
|
|
|
node_name.size.times do |i|
|
|
name = node_name[i].text
|
|
abstract = node_abstract[i].text
|
|
declaration = node_declaration[i].text.strip
|
|
if node_name[i].values[0].include?("typeDef") &&
|
|
!(declaration =~ /^typedef struct/)
|
|
next
|
|
end
|
|
|
|
members = declaration.scan(/\{([^\}]+)\}/)
|
|
members = members[0][0].strip.split(/;/) if members.size > 0
|
|
unless members.empty?
|
|
code << "# #{sanitize(abstract)}\n"
|
|
code << "class #{name} < Boxed\n"
|
|
|
|
members = members.inject([]) { |ary, item|
|
|
# split 'double x, y, z, w;' to each line
|
|
item.strip =~ /([^\s]+)\s+(.+)/
|
|
type = $1
|
|
member = $2
|
|
if type && member
|
|
member.split(",").each do |m|
|
|
ary << "#{type} #{m}"
|
|
end
|
|
end
|
|
ary
|
|
}
|
|
|
|
node_field_description = node_termdef.xpath("dd")
|
|
members.each do |item|
|
|
item.strip =~ /(.+)\s+(.+)/
|
|
type = $1
|
|
member = $2
|
|
desc = node_field_description[current_member_position]
|
|
code << " # @return [#{parse_type(type)}] #{desc ? sanitize(desc.text) : ''}\n"
|
|
code << " attr_accessor :#{member}\n"
|
|
current_member_position += 1
|
|
end
|
|
code << "end\n\n"
|
|
end
|
|
end
|
|
|
|
node_name.remove
|
|
return code
|
|
end
|
|
|
|
def parse_html_reference(name, doc, code)
|
|
if node = doc.xpath("//section/a[@title='Functions']")
|
|
parse_html_function(node, code)
|
|
end
|
|
if node = doc.xpath("//section/a[@title='Data Types']")
|
|
parse_html_struct(node, code)
|
|
end
|
|
|
|
return code
|
|
end
|
|
|
|
def parse_html_data(data)
|
|
doc = Nokogiri::HTML(data)
|
|
title = doc.xpath('/html/head/title')
|
|
if title
|
|
code = ''
|
|
if framework_path = find_framework_path(doc)
|
|
code << "# -*- framework: #{framework_path} -*-\n\n"
|
|
else
|
|
#$stderr.puts "Can't determine framework path for: #{name}"
|
|
code << "\n\n"
|
|
end
|
|
|
|
if md = title.text.match(/^(.+)Class Reference$/)
|
|
parse_html_class(md[1].strip, doc, code)
|
|
elsif md = title.text.match(/^(.+)Protocol Reference$/)
|
|
parse_html_protocol(md[1].strip, doc, code)
|
|
elsif md = title.text.match(/^(.+) Reference$/)
|
|
parse_html_reference(md[1].strip, doc, code)
|
|
end
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def self.modify_document_title(path, new_title)
|
|
unless File.exists?(path)
|
|
warn "File not exists : #{path}"
|
|
return nil
|
|
end
|
|
data = File.read(path)
|
|
data.gsub!(/\s*Module:/, new_title + ':')
|
|
|
|
File.open(path, "w") { |io| io.print data }
|
|
end
|
|
|
|
def initialize(outpath, paths)
|
|
@input_paths = []
|
|
paths.each do |path|
|
|
path = File.expand_path(path)
|
|
if File.directory?(path)
|
|
@input_paths.concat(Dir.glob(path + '/**/*.html'))
|
|
else
|
|
@input_paths << path
|
|
end
|
|
end
|
|
@outpath = outpath
|
|
@rb_files_dir = '/tmp/rb_docset'
|
|
end
|
|
|
|
def generate_ruby_code
|
|
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.puts "# -*- coding: utf-8 -*-"
|
|
io.write(code)
|
|
end
|
|
end
|
|
end
|
|
|
|
def generate_html
|
|
sh "yard doc #{@rb_files_dir}"
|
|
sh "mv doc \"#{@outpath}\""
|
|
end
|
|
|
|
def run
|
|
generate_ruby_code()
|
|
generate_html()
|
|
end
|
|
end
|