mirror of
https://github.com/zhigang1992/RubyMotion.git
synced 2026-03-29 22:43:28 +08:00
433 lines
12 KiB
Ruby
433 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?
|
|
|
|
# FIXME : To avoid overwriting NSObject class reference by NSObject protocol reference
|
|
return nil if name == "NSObject"
|
|
|
|
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
|