Files
RubyMotion/doc/docset.rb

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