Files
RubyMotion/doc/Rakefile
Laurent Sansonetti 6a7e900080 some misc stuff
2012-03-03 02:24:07 +01:00

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