# # bitclust/rdcompiler.rb # # Copyright (C) 2006-2008 Minero Aoki # # This program is free software. # You can distribute/modify this program under the Ruby License. # require 'bitclust/methodsignature' require 'bitclust/lineinput' require 'bitclust/htmlutils' require 'bitclust/textutils' require 'stringio' module BitClust class RDCompiler include HTMLUtils include TextUtils def initialize(urlmapper, hlevel = 1, opt = {}) @urlmapper = urlmapper @catalog = opt[:catalog] @hlevel = hlevel @type = nil @library = nil @class = nil @method = nil @option = opt.dup end def compile(src) setup(src) { library_file } end # FIXME def compile_method(m, opt = nil) @opt = opt @type = :method @method = m setup(m.source) { method_entry } ensure @opt = nil end private def setup(src) @f = LineInput.new(StringIO.new(src)) @out = StringIO.new yield @out.string end def library_file while @f.next? case @f.peek when /\A---/ method_entry_chunk when /\A=+/ headline @f.gets when /\A\s+\*\s/ ulist when /\A\s+\(\d+\)\s/ olist when %r<\A//emlist\{> emlist when /\A:\s/ dlist when /\A\s+\S/ list else if @f.peek.strip.empty? @f.gets else paragraph end end end end def method_entry while @f.next? method_entry_chunk end end def method_entry_chunk @out.puts '
' if @option[:force] @f.while_match(/\A---/) do |line| method_signature line end props = {} @f.while_match(/\A:/) do |line| k, v = line.sub(/\A:/, '').split(':', 2) props[k.strip] = v.strip end @out.puts '
' while @f.next? case @f.peek when /\A===+/ headline @f.gets when /\A==?/ if @option[:force] break else raise "method entry includes headline: #{@f.peek.inspect}" end when /\A---/ break when /\A\s+\*\s/ ulist when /\A\s+\(\d+\)\s/ olist when /\A:\s/ dlist when %r<\A//emlist\{> emlist when /\A\s+\S/ list when /@see/ see when /@todo/ todo when /\A@[a-z]/ method_info else if @f.peek.strip.empty? @f.gets else method_entry_paragraph end end end @out.puts '
' @out.puts '
' if @option[:force] end def headline(line) level = @hlevel + (line.slice(/\A=+/).size - 3) label = line.sub(/\A=+(\[a:(.*?)\])?/, '').strip frag = $2 if $2 and not $2.empty? line h(level, escape_html(label), frag) end def h(level, label, frag = nil) name = frag ? "id='#{escape_html(frag)}'" : "" "#{label}" end def ulist @out.puts '' end def olist @out.puts '
    ' @f.while_match(/\A\s+\(\d+\)/) do |line| string '
  1. ' string compile_text(line.sub(/\A\s+\(\d+\)/, '').strip) @f.while_match(/\A\s+(?!\(\d+\))\S/) do |cont| string "\n" string compile_text(cont.strip) end line '
  2. ' end line '
' end def dlist line '
' while @f.next? and /\A:/ =~ @f.peek @f.while_match(/\A:/) do |line| line dt(compile_text(line.sub(/\A:/, '').strip)) end dd_with_p end line '
' end # empty lines separate paragraphs. def dd_with_p line '
' while /\A(?:\s|\z)/ =~ @f.peek or %r!\A//emlist\{! =~ @f.peek case @f.peek when /\A$/ @f.gets when /\A[ \t\z]/ line '

' @f.while_match(/\A[ \t\z]/) do |line| line compile_text(line.strip) end line '

' when %r!\A//emlist\{! emlist else raise 'must not happen' end end line '
' end # empty lines do not separate paragraphs. def dd_without_p line '
' while /\A[ \t]/ =~ @f.peek or %r!\A//emlist\{! =~ @f.peek case @f.peek when /\A[ \t\z]/ @f.while_match(/\A[ \t\z]/) do |line| line compile_text(line.strip) end when %r!\A//emlist\{! emlist end end line '
' end def dt(s) "
#{s}
" end def emlist @f.gets # discard "//emlist{" line '
'
      @f.until_terminator(%r<\A//\}>) do |line|
        line escape_html(line.rstrip)
      end
      line '
' end def list lines = unindent_block(canonicalize(@f.break(/\A\S/))) while lines.last.empty? lines.pop end line '
'
      lines.each do |line|
        line escape_html(line)
      end
      line '
' end def canonicalize(lines) lines.map {|line| detab(line.rstrip) } end def paragraph line '

' read_paragraph(@f).each do |line| line compile_text(line.strip) end line '

' end def read_paragraph(f) f.span(%r<\A(?!---|=|//emlist\{)\S>) end def see header = @f.gets cmd = header.slice!(/\A\@\w+/) body = [header] + @f.span(/\A\s+\S/) line '

' line '[SEE_ALSO] ' + compile_text(body.join('').strip) line '

' end def todo header = @f.gets cmd = header.slice!(/\A\@\w+/) body = header line '

' line '[TODO]' + body line '

' end def method_info line '
' while @f.next? and /\A\@(?!see)\w+|\A$/ =~ @f.peek header = @f.gets next if /\A$/ =~ header cmd = header.slice!(/\A\@\w+/) @f.ungets(header) case cmd when '@param', '@arg' name = header.slice!(/\A\s*\w+/) || '?' line "
[PARAM] #{escape_html(name.strip)}:
" when '@raise' ex = header.slice!(/\A\s*[\w:]+/) || '?' line "
[EXCEPTION] #{escape_html(ex.strip)}:
" when '@return' line "
[RETURN]
" else line "
[UNKNOWN_META_INFO] #{escape_html(cmd)}:
" end dd_without_p end line '
' end # FIXME: parse @param, @return, ... def method_entry_paragraph line '

' read_method_entry_paragraph(@f).each do |line| line compile_text(line.strip) end line '

' end def read_method_entry_paragraph(f) f.span(%r<\A(?!---|=|//emlist\{|@[a-z])\S>) end def method_signature(sig_line) # FIXME: check parameters, types, etc. sig = MethodSignature.parse(sig_line) string '
' string @method.klass.name + @method.typemark if @opt string escape_html(sig.friendly_string) string '' if @method and not @method.defined? line %Q( [#{@method.kind} by #{library_link(@method.library.name)}]) end line '
' end BracketLink = /\[\[[\w-]+?:[!-~]+?(?:\[\] )?\]\]/n NeedESC = /[&"<>]/ def compile_text(str) escape_table = HTMLUtils::ESC str.gsub(/(#{NeedESC})|(#{BracketLink})/o) { if char = $1 then escape_table[char] elsif tok = $2 then bracket_link(tok[2..-3]) elsif tok = $3 then seems_code(tok) else raise 'must not happen' end } end def bracket_link(link, label = nil, frag = nil) type, _arg = link.split(':', 2) arg = _arg.rstrip case type when 'lib' then protect(link) { case arg when '/', '_index' label = 'All libraries' when '_builtin' label = 'Builtin libraries' end library_link(arg, label, frag) } when 'c' then protect(link) { class_link(arg, label, frag) } when 'm' then protect(link) { method_link(complete_spec(arg), label || arg, frag) } when 'f' then protect(link) { case arg when '/', '_index' arg, label = '', 'All C API' end function_link(arg, label || arg, frag) } when 'd' then protect(link) { document_link(arg, label, frag) } when 'ref' then protect(link) { reference_link(arg) } when 'url' then direct_url(arg) when 'man' then man_link(arg) when 'rfc', 'RFC' rfc_link(arg) when 'ruby-list', 'ruby-dev', 'ruby-ext', 'ruby-talk', 'ruby-core' blade_link(type, arg) else "[[#{escape_html(link)}]]" end end def protect(src) yield rescue => err %Q([[compile error: #{escape_html(err.message)}: #{escape_html(src)}]]) end def direct_url(url) %Q(#{escape_html(url)}) end def reference_link(arg) case arg when /(\w+):(.*)\#(\w+)\z/ type, name, frag = $1, $2, $3 case type when 'lib' title, t, id = name, LibraryEntry.type_id.to_s, name when 'c' title, t, id = name, ClassEntry.type_id.to_s, name when 'm' title, t, id = name, MethodEntry.type_id.to_s, name when 'd' title, t, id = @option[:database].get_doc(name).title, DocEntry.type_id.to_s, name else raise "must not happen" end label = @option[:database].refs[t, id, frag] label = title + '/' + label if label and name bracket_link("#{type}:#{name}", label, frag) when /\A(\w+)\z/ e = @option[:entry] frag = $1 type = e.type_id.to_s label = @option[:database].refs[type, e.name, frag] || frag a_href('#' + frag, label) else raise "must not happen" end end BLADE_URL = 'http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/%s/%s' def blade_link(ml, num) url = sprintf(BLADE_URL, ml, num) %Q([#{escape_html("#{ml}:#{num}")}]) end RFC_URL = 'http://www.ietf.org/rfc/rfc%s.txt' def rfc_link(num) url = sprintf(RFC_URL, num) %Q([RFC#{escape_html(num)}]) end opengroup_url = 'http://www.opengroup.org/onlinepubs/009695399' MAN_CMD_URL = "#{opengroup_url}/utilities/%s.html" MAN_FCN_URL = "#{opengroup_url}/functions/%s.html" MAN_HEADER_URL = "#{opengroup_url}/basedefs/%s.html" MAN_LINUX_URL = "http://man7.org/linux/man-pages/man%1$s/%2$s.%1$s.html" MAN_FREEBSD_URL = "http://www.freebsd.org/cgi/man.cgi?query=%2$s&sektion=%1$s&manpath=FreeBSD+9.0-RELEASE" def man_url(section, page) case section when "1" sprintf(MAN_CMD_URL, page) when "2", "3" sprintf(MAN_FCN_URL, page) when "header" sprintf(MAN_HEADER_URL, page) when /\A([23457])linux\Z/ sprintf(MAN_LINUX_URL, $1, page) when /\A([1-9])freebsd\Z/ sprintf(MAN_FREEBSD_URL, $1, page) else nil end end def man_link(spec) m = /([\w\.\/]+)\((\w+)\)/.match(spec) or return escape_html(spec) url = man_url(m[2], escape_html(m[1])) or return escape_html(spec) %Q(#{escape_html("#{m[1]}(#{m[2]})")}) end def complete_spec(spec0) case spec0 when /\A\$/ "Kernel#{spec0}" else spec0 end end def seems_code(text) # FIXME escape_html(text) end def string(str) @out.print str end def line(str) @out.puts str end def nl @out.puts end end end