logo

class

docCompiler::ApiToHtmlGenerator

sys::Obj
  fandoc::HtmlDocWriter
    docCompiler::HtmlGenerator
      docCompiler::ApiToHtmlGenerator
//
// Copyright (c) 2007, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   5 May 07  Brian Frank  Creation
//

using compiler
using fandoc

**
** ApiToHtmlGenerator generates an HTML file for a Type's API
**
class ApiToHtmlGenerator : HtmlGenerator
{

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

  new make(DocCompiler compiler, Location loc, OutStream out, Type t)
    : super(compiler, loc, out)
  {
    this.t = t
    sorter := |Slot a, Slot b -> Int| { return a.name <=> b.name }
    filter := |Slot s -> Bool| { return showSlot(t, s) }
    this.slots = t.slots.rw.sort(sorter).findAll(filter)
  }

//////////////////////////////////////////////////////////////////////////
// Generator
//////////////////////////////////////////////////////////////////////////

  override Str title()
  {
    return t.qname
  }

  override Void header()
  {
    out.print("<ul>\n")
    out.print("  <li><a href='../index.html'>$docHome</a></li>\n")
    out.print("  <li><a href='index.html'>$t.pod.name</a></li>\n")
    out.print("  <li>$t.name</li>\n")
    out.print("</ul>\n")
  }

  override Void content()
  {
    out.print("<div class='type'>\n")
    typeOverview
    typeDetail
    out.print("</div>\n")
    slotsDetail
  }

  override Void sidebar()
  {
    actions
    slotsOverview
  }

//////////////////////////////////////////////////////////////////////////
// Methods
//////////////////////////////////////////////////////////////////////////

  **
  ** Generate the actions.
  **
  Void actions()
  {
    out.print("<h2>More Info</h2>\n")
    out.print("<ul>\n")
    out.print("  <li><a href='${t.name}_src.html'>View Source</a></li>\n")
    out.print("  <li><a href='#' onclick='ShowSlots.toggle(event); return false;'>")
    out.print("Show All Slots</a></li>\n")
    out.print("</ul>\n")
  }

  **
  ** Generate the type overview documentation.
  **
  Void typeOverview()
  {
    out.print("<div class='overview'>\n")
    out.print("<h2>")
    if (t.isMixin) out.print("mixin")
    else
    {
      if (t.isAbstract) out.print("abstract ")
      if (t.isConst) out.print("const ")
      if (t.isFinal) out.print("final ")
      if (t.isEnum)
        out.print("enum")
      else
        out.print("class")
    }
    out.print("</h2>\n")
    out.print("<h1>$t.qname</h1>\n")
    if (t.base != null) inheritance
    out.print("</div>\n")
  }

  **
  ** Generate the type detail documentation.
  **
  Void typeDetail()
  {
    if (t.doc == null) return
    out.print("<div class='detail'>\n")
    fandoc(t.doc)
    out.print("</div>\n")
  }

  **
  ** Generate the type inheritance.
  **
  Void inheritance()
  {
    chain := Type[t]
    base  := t.base
    while (base != null)
    {
      chain.insert(0, base)
      base = base.base
    }
    out.print("<pre>")
    chain.each |Type t, Int i|
    {
      if (i > 0) out.print("\n${Str.spaces(i*2)}")
      if (i == chain.size-1) out.print(t.qname)
      else out.print("<a href='${compiler.uriMapper.map(t.qname, loc)}'>$t.qname</a>")
    }
    t.mixins.each |Type t, Int i|
    {
      //if (i == 0) out.print("\n\nMixin: ")
      if (i == 0) out.print(" : ")
      else out.print(", ")
      out.print("<a href='${compiler.uriMapper.map(t.qname, loc)}'>$t.qname</a>")
    }
    out.print("</pre>")
  }

  **
  ** Generate the slot overview documentation.
  **
  Void slotsOverview(Bool hideByDefault := true)
  {
    out.print("<div class='slots'>\n")
    out.print("<div class='overview'>\n")
    out.print("<h2>Slots</h2>\n")
    out.print("<ul>\n")
    slots.each |Slot slot|
    {
      if (!showSlot(t, slot)) return
      out.print("  <li")
      if (!showByDefault(t, slot)) out.print(" class='hidden'")
      if (!hideByDefault) out.print(" style='display: block;'")
      out.print("><a href='#$slot.name'>$slot.name</a></li>\n")
    }
    out.print("</ul>\n")
    out.print("</div>\n")
    out.print("</div>\n")
  }

  **
  ** Generate the slot detail documentation.
  **
  Void slotsDetail()
  {
    out.print("<div class='slots'>\n")
    out.print("<div class='detail'>\n")
    out.print("<h2>Slots</h2>\n")
    out.print("<dl>\n")
    slots.each |Slot slot| { slotDetail(slot) }
    out.print("</dl>\n")
    out.print("</div>\n")
    out.print("</div>\n")
  }

  **
  ** Generate the documentation for the given slot.
  **
  Void slotDetail(Slot slot)
  {
    if (!showSlot(t, slot)) return

    oldfile := loc.file
    loc.file = slot.qname

    hidden := !showByDefault(t, slot)
    cls := (slot.isField) ? "field" : "method"
    if (hidden) cls += " hidden"
    out.print("<dt id='$slot.name' class='$cls'>$slot.name</dt>\n")
    out.print("<dd")
    if (hidden) out.print(" class='hidden'")
    out.print(">\n")

    // Slot spec
    out.print("<p><code>")
    if (slot.isField)
    {
      f := (Field)slot
      slotModifiers(f)
      typeLink(f.of)
      out.print(" $f.name")
      setter(f)
    }
    else
    {
      m := (Method)slot
      if (m.isCtor) out.print("new")
      else
      {
        slotModifiers(m)
        typeLink(m.returns)
      }
      out.print(" $m.name")
      out.print("(")
      defs := parseDefs(slot.doc)
      m.params.each |Param p, Int i|
      {
        if (i > 0) out.print(", ")
        typeLink(p.of)
        out.print(" $p.name")
        if (p.hasDefault)
        {
          out.print(" := ")
          out.print(defs.get(p.name, "def"))
        }
      }
      out.print(")")
    }
    out.print("</code></p>\n")

    // inherited
    if (isInnherited(t, slot))
    {
      out.print("<p>Inherited from ")
      typeLink(slot.parent)
      out.print("</p>\n")
    }

    // Slot comment
    if (slot.doc != null)
      fandoc(slot.doc)

    out.print("</dd>\n")

    loc.file = oldfile
  }

  **
  ** Write a slot's modifiers.
  **
  Void slotModifiers(Slot s)
  {
    if (s.isVirtual)
    {
      if (s.isAbstract) out.print("abstract ")
      else if (s.isOverride) out.print("override ")
      else out.print("virtual ")
    }

    if (s.isStatic) out.print("static ")
    else if (s.isConst) out.print("const ")

    if (s.isProtected) out.print("protected ")
    else if (s.isPrivate)   out.print("private ")
    else if (s.isInternal)  out.print("internal ")

    if (s.isField)
    {
      Method z := s->setter
      if (z != null && !s.isPrivate && z.isPrivate) out.print("readonly ")
    }
  }

  **
  ** Write a field's setter proctection level if its different
  ** from the getter's level.
  **
  Void setter(Field f)
  {
    Method s := f->setter
    if (s == null) return
    if (f.isPublic    && s.isPublic)    return
    if (f.isProtected && s.isProtected) return
    if (f.isPrivate   && s.isPrivate)   return
    if (f.isInternal  && s.isInternal)  return

    // this case handled already in slotModifers() by writing 'readonly'
    if (!f.isPrivate && s.isPrivate) return

    // if we made this far, they must be different
    out.print(" { ")
    slotModifiers(s)
    out.print("set }")
  }

  **
  ** Write a type link out in the form <a href='type.uri'>type.name</a>.
  **
  Void typeLink(Type t)
  {
    display := typeToDisplay(t)

    // TODO - not really sure what the right thing to
    // do here is - these are the generic types, like
    // sys::A, sys::V, etc
    if (t.pod.name == "sys" && t.name.size == 1)
      t = Obj.type

    out.print("<a href='${compiler.uriMapper.map(t.qname, loc)}'>$display</a>")
  }

  **
  ** Convert the Type name to a display string by stripping
  ** the pod names from the signature.
  **
  static Str typeToDisplay(Type t)
  {
    if (!t.isGeneric)
    {
      p := t.params
      if (p["L"] != null) return typeToDisplay(p["V"]) + "[]"
      if (p["M"] != null) return typeToDisplay(p["K"]) + ":" + typeToDisplay(p["V"])
      if (p["R"] != null)
      {
        buf := StrBuf.make.addChar('|')

        keys := p.keys.rw.sort |Str a, Str b -> Int| { return a <=> b }
        keys.each |Str k, Int i|
        {
          if (k == "R") return
          if (i > 0) buf.add(", ")
          buf.add(typeToDisplay(p[k]))
        }

        if (p["R"] != Void.type)
          buf.add(" -> ").add(typeToDisplay(p["R"]))

        buf.addChar('|')
        return buf.toStr
      }
    }

    // Force sys::A,B,C,etc to be Obj
    //if (t.pod.name == "sys" && t.name.size == 1) t = Obj.type
    return t.name
  }

  **
  ** Parse def parameters.
  **
  Str:Str parseDefs(Str text)
  {
    defs := Str:Str[:]
    if (text == null || !text.startsWith("@"))
      return defs

    InStream.makeForStr(text).eachLine |Str line|
    {
      if (!line.startsWith("@")) return
      i := line.index(".def=")
      if (i == null) return
      defs[line[1...i]] = line[i+5..-1].trim
    }

    return defs
  }

  **
  ** Write out the fandoc for this text - if an exception
  ** is thrown, write the original text.
  **
  Void fandoc(Str text)
  {
    try
    {
      // TODO - we could save some cycles here by reusing
      // the stream we used for parseDefs

      in := InStream.makeForStr(text)
      while (in.peek == '@') in.readLine // eat def args if they exist

      doc := FandocParser.make.parse("API for $t", in)
      doc.children.each |DocNode child| { child.write(this) }
    }
    catch { out.print("<p>$text<p>\n") }
  }

//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////

  Type t        // type to documenting
  Slot[] slots  // slots to document

}