logo

class

docCompiler::UriMapper

sys::Obj
  docCompiler::DocCompilerSupport
    docCompiler::UriMapper
   1  //
   2  // Copyright (c) 2007, Brian Frank and Andy Frank
   3  // Licensed under the Academic Free License version 3.0
   4  //
   5  // History:
   6  //   11 May 07  Brian Frank  Creation
   7  //
   8  
   9  using compiler
  10  using fandoc
  11  
  12  **
  13  ** UriMapper is used to normalize fandoc URIs into hrefs to
  14  ** their HTML file representation using relative URLs.
  15  **
  16  class UriMapper : DocCompilerSupport
  17  {
  18  
  19  //////////////////////////////////////////////////////////////////////////
  20  // Constructor
  21  //////////////////////////////////////////////////////////////////////////
  22  
  23    new make(DocCompiler compiler)
  24      : super(compiler)
  25    {
  26    }
  27  
  28  //////////////////////////////////////////////////////////////////////////
  29  // Access
  30  //////////////////////////////////////////////////////////////////////////
  31  
  32    **
  33    ** Given a fandoc uri string, map it to a relative URL
  34    ** to the resource's HTML.
  35    **
  36    Uri map(Str fandocUri, Location loc)
  37    {
  38      this.fandocUri    = fandocUri
  39      this.loc          = loc
  40      this.frag         = null
  41      this.targetUri    = null
  42      this.targetIsCode = false
  43      this.targetIsSlot = false
  44  
  45      // if document internal fragment identifer then
  46      // bail now before we do any work or use our cache
  47      // which spans all the documents in the current pod
  48      // reset working fields
  49      if (fandocUri.startsWith("#"))
  50        return targetUri = Uri.fromStr(fandocUri)
  51  
  52      // if absolute then bail
  53      if (fandocUri.startsWith("http:")  ||
  54          fandocUri.startsWith("https:") ||
  55          fandocUri.startsWith("ftp:")   ||
  56          fandocUri.startsWith("mailto:"))
  57        return targetUri = Uri.fromStr(fandocUri)
  58  
  59      // check the cache
  60      cached := cache[fandocUri]
  61      if (cached != null)
  62      {
  63        targetUri    = cached.targetUri
  64        targetIsCode = cached.targetIsCode
  65        return targetUri
  66      }
  67  
  68      // strip off fragment if specified
  69      pound := fandocUri.index("#")
  70      if (pound != null)
  71      {
  72        // if URI is to frag within doc we are done
  73        if (pound == 0) return targetUri = Uri.fromStr(fandocUri)
  74  
  75        // split off fragment identifier
  76        this.fandocUri = fandocUri[0...pound]
  77        this.frag = fandocUri[pound+1..-1]
  78      }
  79  
  80      // map
  81      try
  82      {
  83        if (fandocUri.contains("::"))
  84          mapPod
  85        else
  86          mapTypeOrFile(compiler.pod, this.fandocUri)
  87  
  88        // cache for next time
  89        if (!targetIsSlot)
  90          cache[fandocUri] = CachedUri.make(targetUri, targetIsCode)
  91      }
  92      catch (CompilerErr e)
  93      {
  94        targetUri = fandocUri.toUri
  95      }
  96      catch (Err e)
  97      {
  98        errReport(CompilerErr.make("Cannot map uri '$fandocUri'", loc, e))
  99        targetUri = fandocUri.toUri
 100      }
 101  
 102      // return result
 103      return targetUri
 104    }
 105  
 106  //////////////////////////////////////////////////////////////////////////
 107  // Map
 108  //////////////////////////////////////////////////////////////////////////
 109  
 110    private Void mapPod()
 111    {
 112      // lookup pod
 113      colons := fandocUri.index("::")
 114      podName := fandocUri[0...colons]
 115      pod := Pod.find(podName, false)
 116      if (pod == null) throw err("Unknown pod '$podName'", loc)
 117  
 118      rest := fandocUri[colons+2..-1]
 119      mapTypeOrFile(pod, rest)
 120    }
 121  
 122    private Void mapTypeOrFile(Pod pod, Str s)
 123    {
 124      typeName := s
 125      Str rest := null
 126  
 127      if (s == "index")
 128      {
 129        targetUri = toUri(pod, "index.html", frag)
 130        return
 131      }
 132  
 133      dot := s.index(".")
 134      if (dot != null)
 135      {
 136        typeName = s[0...dot]
 137        rest = s[dot+1..-1]
 138      }
 139      else if (compiler.curType != null)
 140      {
 141        // if this string maps to a slot in the current
 142        // type being processes - then slot name trumps
 143        t := compiler.curType
 144        slot := t.slot(s, false)
 145        if (slot != null)
 146        {
 147          targetIsSlot = true
 148          targetUri = toUri(slot.parent.pod, "${slot.parent.name}.html", slot.name)
 149          return
 150        }
 151      }
 152  
 153      // first try type in pod
 154      t := pod.findType(typeName, false)
 155      if (t != null)
 156      {
 157        mapSlot(t, rest)
 158        return
 159      }
 160  
 161      // if dot ext, this must be a filename which we don't do yet
 162      // TODO: eventually we need we need a standard mechanism
 163      // to determine which resources to copy to the doc directory
 164      // probably under some specific dir, which we can map
 165      if (rest != null)
 166      {
 167        if (fandocUri.endsWith(".html"))
 168        {
 169          echo("WARNING: Need to fix unresolved fandoc uri bug: $fandocUri")
 170          targetUri = fandocUri.toUri
 171        }
 172        else
 173        {
 174          throw err("Unresolved fandoc uri '$fandocUri'", loc)
 175        }
 176      }
 177  
 178      // try to find fandoc file in pod
 179      fandocFile := pod.files.find |File f->Bool|
 180      {
 181        return f.basename == typeName && f.ext == "fandoc"
 182      }
 183  
 184      if (fandocFile == null)
 185        throw err("Unresolved fandoc uri '$fandocUri'", loc)
 186  
 187      // if this a fragment identifier within the
 188      // fandoc file then check that it exists
 189      if (frag != null)
 190      {
 191        // ensure we've parsed the fandoc file into memory
 192        // to create a table of all the fragment identifiers
 193        if (fandocFrags[fandocUri] == null)
 194          fandocFrags[fandocUri] = parseFandocFrags(fandocFile)
 195  
 196        // check that the fragment exists
 197        if (!fandocFrags[fandocUri].frags.containsKey(frag))
 198          throw err("Unknown fragment '#$frag' in '$fandocUri'", loc)
 199      }
 200  
 201      // we can now build our uri
 202      targetUri = toUri(pod, "${fandocFile.basename}.html", frag)
 203    }
 204  
 205    private Void mapSlot(Type t, Str rest)
 206    {
 207      targetIsCode = true
 208      Str slotFrag := null
 209  
 210      // if rest it must be a slot name
 211      if (rest != null)
 212      {
 213        slot := t.slot(rest, false)
 214        if (slot == null)
 215          throw err("Unresolved uri to slot '${t.qname}.$rest'", loc)
 216        if (slot.parent != t)
 217          throw err("Uri to inherited slot '${t.qname}.$rest' is declared on '$slot.parent'", loc)
 218        slotFrag = slot.name
 219        targetIsSlot = true
 220      }
 221  
 222      targetUri = toUri(t.pod, "${t.name}.html", slotFrag)
 223    }
 224  
 225    private Uri toUri(Pod pod, Str uri, Str frag)
 226    {
 227      if (pod != compiler.pod) uri = "../$pod.name/" + uri
 228      if (frag != null) uri = uri + "#" + frag
 229      return Uri.fromStr(uri)
 230    }
 231  
 232  //////////////////////////////////////////////////////////////////////////
 233  // FandocFrag Parse
 234  //////////////////////////////////////////////////////////////////////////
 235  
 236    private FandocFrags parseFandocFrags(File f)
 237    {
 238      try
 239      {
 240        log.debug("    Parse fandoc frag [$fandocUri]")
 241        docFrags := FandocFrags.make
 242        f.readAllLines.each |Str line|
 243        {
 244          x := line.index("[#")
 245          if (x != null)
 246          {
 247            y := line.index("]", x)
 248            if (y != null)
 249            {
 250              frag := line[x+2...y]
 251              docFrags.frags[frag] = frag
 252            }
 253          }
 254        }
 255        return docFrags
 256      }
 257      catch (Err e)
 258      {
 259        e.trace
 260        throw err("Cannot parse fandoc file for fragments: $f", loc)
 261      }
 262    }
 263  
 264  //////////////////////////////////////////////////////////////////////////
 265  // Fields
 266  //////////////////////////////////////////////////////////////////////////
 267  
 268    Str fandocUri
 269    Str frag
 270    Location loc
 271    Uri targetUri
 272    Bool targetIsCode
 273    Bool targetIsSlot
 274    Str:CachedUri cache := Str:CachedUri[:]
 275    Str:FandocFrags fandocFrags := Str:FandocFrags[:]
 276  }
 277  
 278  internal class CachedUri
 279  {
 280    new make(Uri uri, Bool isCode)
 281    {
 282      targetUri    = uri
 283      targetIsCode = isCode
 284    }
 285  
 286    Uri targetUri
 287    Bool targetIsCode
 288  }
 289  
 290  internal class FandocFrags
 291  {
 292    Str:Str frags := Str:Str[:]
 293  }
 294