mirror of
https://github.com/zhigang1992/RubyMotion.git
synced 2026-04-07 09:37:36 +08:00
427 lines
10 KiB
Ruby
427 lines
10 KiB
Ruby
verbose(true)
|
|
|
|
class DocsetGenerator
|
|
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(outpath, paths)
|
|
@input_paths = []
|
|
paths.each do |path|
|
|
if File.directory?(path)
|
|
@input_paths.concat(Dir.glob(path + '/**/*.html'))
|
|
else
|
|
@input_paths << path
|
|
end
|
|
end
|
|
@outpath = outpath
|
|
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 \"#{@outpath}\""
|
|
end
|
|
end
|
|
|
|
require 'redcloth'
|
|
class GuideGenerator < RedCloth
|
|
def initialize(src, dest, no_header_footer)
|
|
base = File.basename(src, '.textile')
|
|
@title = base.gsub(/([a-z])([A-Z])/, '\1 \2')
|
|
@dest = dest
|
|
@no_header_footer = no_header_footer
|
|
data = File.read(src)
|
|
super(fix_anchors(data))
|
|
end
|
|
|
|
def run
|
|
File.open(@dest, 'w') do |io|
|
|
unless @no_header_footer
|
|
io.puts html_header
|
|
#io.puts toc
|
|
io.puts '<div class="content">'
|
|
end
|
|
io.puts to_html(:inline_plus_annotations, :textile)
|
|
unless @no_header_footer
|
|
io.puts '</div>'
|
|
io.puts html_footer
|
|
end
|
|
end
|
|
end
|
|
|
|
def toc
|
|
toggle_func = <<EOS
|
|
if (document.getElementById('toc-content').style.display == 'none') {
|
|
document.getElementById('toc-content').style.display = 'block';
|
|
document.getElementById('toc-toggle-link').text = 'hide';
|
|
}
|
|
else {
|
|
document.getElementById('toc-content').style.display = 'none';
|
|
document.getElementById('toc-toggle-link').text = 'show';
|
|
}
|
|
EOS
|
|
|
|
text = "<div class=\"toc\"><h3>Table of Contents (<a id=\"toc-toggle-link\" href=\"javascript:void(0)\" onClick=\"#{toggle_func}\">hide</a>)</h3>"
|
|
current_level = 1
|
|
text << "<div id=\"toc-content\">"
|
|
self.to_s.scan(/^h([2-3])\((#[^)]+)\)\.([^\n]+)\n/).each do |level, anchor, title|
|
|
level = level.to_i
|
|
if level > current_level
|
|
text << '<ol>'
|
|
current_level = level
|
|
elsif level < current_level
|
|
text << '</ol>'
|
|
current_level = level
|
|
end
|
|
text << "<li class=\"toc-level#{level}\"><a href=\"#{anchor}\">#{title}</a></li>"
|
|
end
|
|
while current_level > 1
|
|
text << '</ol>'
|
|
current_level -= 1
|
|
end
|
|
text << '</div></div>'
|
|
return text
|
|
end
|
|
|
|
# Transform +foo+ into <tt>foo</tt>.
|
|
def inline_plus_annotations(text)
|
|
text.gsub!(/\+(.*?)\+/m) { |m| rip_offtags("<tt>#{$~[1]}</tt>") }
|
|
end
|
|
|
|
# Auto-define anchors for h[1-4] elements.
|
|
def fix_anchors(text)
|
|
text.gsub(/^(h[1-4])\.([^\n]+)/m) do |m|
|
|
"#{$1}(##{anchor_name($2)}). #{$2}"
|
|
end
|
|
end
|
|
|
|
def anchor_name(title)
|
|
title.strip.gsub(/\s/, '-').downcase
|
|
end
|
|
|
|
def css
|
|
<<EOS
|
|
body {
|
|
font-family: "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif;
|
|
font-size: 15px;
|
|
text-align: left
|
|
}
|
|
|
|
.content {
|
|
margin-bottom: 20px;
|
|
margin-left: 280px;
|
|
margin-right: 280px;
|
|
margin-top: 0px;
|
|
position: relative;
|
|
text-align: left;
|
|
min-width: 700px;
|
|
max-width: 900px;
|
|
}
|
|
|
|
h1 {
|
|
padding-top: 10px;
|
|
font-size: 28px;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 1.4em;
|
|
border-bottom: 1px #aaa solid;
|
|
padding-top: 30px;
|
|
padding-bottom: 3px;
|
|
}
|
|
|
|
.content li {
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
a:link, a:active, a:visited {
|
|
color: #33a;
|
|
text-decoration: none;
|
|
}
|
|
|
|
a:hover, a:focus {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
pre, code {
|
|
overflow: auto;
|
|
color: #222;
|
|
background: #EEE;
|
|
padding-left: 8px;
|
|
padding-right: 8px;
|
|
padding-top: 6px;
|
|
padding-bottom: 6px;
|
|
border: 1px solid #CCC;
|
|
}
|
|
|
|
tt {
|
|
color: #222;
|
|
/*background: #EEE;*/
|
|
}
|
|
|
|
pre, code, tt {
|
|
font-size: 14px;
|
|
font-family: "Anonymous Pro", "Inconsolata", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
table {
|
|
border: 1px solid #CCC;
|
|
background: #FFF;
|
|
border-collapse: collapse;
|
|
width: 100%;
|
|
}
|
|
|
|
table th, table td {
|
|
font-size: 15px;
|
|
padding: 0.25em 1em;
|
|
border: 1px solid #CCC;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
table th {
|
|
font-size: 15px;
|
|
background: #EEE;
|
|
font-weight: bold;
|
|
padding: 0.5em 1em;
|
|
}
|
|
|
|
.toc {
|
|
margin-left: 20px;
|
|
padding-left: 15px;
|
|
padding-right: 20px;
|
|
float: left;
|
|
border: 1px solid #ddd;
|
|
max-width: 195px;
|
|
-webkit-box-shadow: -2px 2px 6px #bbb;
|
|
-moz-box-shadow: -2px 2px 6px #bbb;
|
|
position: fixed;
|
|
}
|
|
|
|
.toc h3, .toc-level2 {
|
|
font-size: 15px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.toc-level3 {
|
|
font-size: 15px;
|
|
}
|
|
|
|
.toc-level4 {
|
|
font-size: 15px;
|
|
}
|
|
|
|
.toc ol {
|
|
padding-left: 20px;
|
|
}
|
|
|
|
.toc li {
|
|
padding-bottom: 5px;
|
|
}
|
|
|
|
EOS
|
|
end
|
|
|
|
def javascript
|
|
<<EOS
|
|
EOS
|
|
end
|
|
|
|
def html_header
|
|
<<EOS
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
|
<html lang="en-US">
|
|
<head>
|
|
<title>#{@title}</title>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
<style type="text/css">
|
|
#{css}
|
|
</style>
|
|
<script type="text/javascript">
|
|
#{javascript}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
EOS
|
|
end
|
|
|
|
def html_footer
|
|
<<EOS
|
|
</body>
|
|
</html>
|
|
EOS
|
|
end
|
|
|
|
end
|
|
|
|
task :default => :all
|
|
#task :all => [:guides, :docset]
|
|
task :all => [:guides] # no docset in the main distribution...
|
|
|
|
task :guides do
|
|
devcenter = ENV['devcenter_destdir']
|
|
Dir.glob('*.textile').each do |src|
|
|
dest = (File.basename(src, '.textile') + '.html')
|
|
dest = dest.downcase.gsub(/ /, '-') if devcenter
|
|
dest = File.join(devcenter, dest) if devcenter
|
|
if devcenter or !File.exist?(dest) or File.mtime(src) > File.mtime(dest) or File.mtime(__FILE__) > File.mtime(dest)
|
|
GuideGenerator.new(src, dest, devcenter != nil).run
|
|
end
|
|
end
|
|
end
|
|
|
|
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
|
|
}
|
|
task :docset do
|
|
if !File.exist?('docset')
|
|
DocsetGenerator.new('docset', DOCSET_PATHS).run
|
|
end
|
|
end
|
|
|
|
task :clean do
|
|
rm_rf 'docset'
|
|
Dir.glob('*.html').each { |x| rm_rf x }
|
|
end
|