Fan

 

class

compiler::WritePod

sys::Obj
  compiler::CompilerSupport
    compiler::CompilerStep
      compiler::WritePod
//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   3 Sep 05  Brian Frank  Creation
//   7 Oct 06  Brian Frank  Port from Java to Fan
//

**
** WritePod writes the FPod to a zip file.
**
class WritePod : CompilerStep
{

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

  new make(Compiler compiler)
    : super(compiler)
  {
  }

//////////////////////////////////////////////////////////////////////////
// Run
//////////////////////////////////////////////////////////////////////////

  **
  ** Not used, use write instead
  **
  override Void run() { throw UnsupportedErr.make }

  **
  ** Run the step and return pod file written
  **
  File write()
  {
    dir  := compiler.input.outDir
    fpod := compiler.fpod
    podFile := dir + "${fpod.name}.pod".toUri
    location = Location.makeFile(podFile)

    log.info("WritePod [${podFile.toStr}]")

    // create output directory
    dir.create

    Zip? zip := null
    try
    {
      // open zip store
      zip = Zip.write(podFile.out)

      // write fpod data structures into zip file
      fpod.write(zip)

      // write type db indices
      if (!compiler.input.isScript)
        writeTypeDb(zip)

      // write resource files
      compiler.resFiles.each |File f| { writeRes(zip, f) }

      // if including fandoc write it out too
      if (compiler.input.includeDoc) writeTypeDocs(zip)

      // if including source write it out too
      if (compiler.input.includeSrc) writeSrc(zip)
    }
    catch (CompilerErr e)
    {
      throw e
    }
    catch (Err e)
    {
      throw errReport(CompilerErr.make("Cannot write", location, e))
    }

    // close file
    if (zip != null) zip.close
    return podFile
  }

//////////////////////////////////////////////////////////////////////////
// Resource
//////////////////////////////////////////////////////////////////////////

  private Void writeRes(Zip zip, File file, Uri? path := null)
  {
    input := compiler.input
    if (path == null)
    {
      path = file.uri
      path = path.relTo(input.homeDir.uri)
    }

    try
    {
      out := zip.writeNext(path, file.modified)
      file.in.pipe(out)
      out.close
    }
    catch (Err e)
    {
      throw errReport(CompilerErr.make("Cannot write resource file '$path'", location, e))
    }
  }

//////////////////////////////////////////////////////////////////////////
// Doc
//////////////////////////////////////////////////////////////////////////

  private Void writeTypeDocs(Zip zip)
  {
    compiler.types.each |TypeDef t|
    {
      if (!t.isSynthetic) writeTypeDoc(zip, t)
    }
  }

  **
  ** FDoc is used to read/write a fandoc text file.  The fandoc file
  ** format is an extremely simple plan text format with left justified
  ** type/slot qnames, followed by the fandoc content indented two spaces.
  ** Addiontal type/slot meta-data is prefixed as "@name=value" lines.
  **
  private Void writeTypeDoc(Zip zip, TypeDef t)
  {
    try
    {
      out := zip.writeNext("doc/${t.name}.apidoc".toUri)
      writeDoc(out, t.qname, t.doc, t.docMeta)
      t.slotDefs.each |SlotDef s|
      {
        writeDoc(out, s.qname, s.doc, s.docMeta)
      }
      out.close
    }
    catch (Err e)
    {
      throw errReport(CompilerErr.make("Cannot write fandoc '$t.name'", t.location, e))
    }
  }

  private static Void writeDoc(OutStream out, Str key, Str[]? doc, [Str:Str]? meta)
  {
    if (doc == null && (meta == null || meta.isEmpty)) return
    out.printLine(key)
    if (meta != null)
    {
      meta.each|Str val, Str name|
      {
        val = val.replace("\n", " ").replace("\r", " ")
        out.printLine("  @$name=$val")
      }
    }
    if (doc != null)
    {
      doc.each |Str line| { out.print("  ").printLine(line) }
    }
    out.printLine
  }

//////////////////////////////////////////////////////////////////////////
// Src
//////////////////////////////////////////////////////////////////////////

  private Void writeSrc(Zip zip)
  {
    compiler.srcFiles.each |File f|
    {
      writeRes(zip, f, "src/$f.name".toUri)
    }
  }

//////////////////////////////////////////////////////////////////////////
// TypeDb
//////////////////////////////////////////////////////////////////////////

  private Void writeTypeDb(Zip zip)
  {
    out := zip.writeNext(`/typedb.def`)

    // pod meta-data
    out.writeI4(FConst.TypeDbMagic)
    out.writeI4(FConst.TypeDbVersion)
    out.writeUtf(pod.name)
    out.writeUtf(compiler.fpod.version.toStr)

    // filter types
    types := pod.typeDefs.findAll |TypeDef t->Bool|
    {
      return !t.isSynthetic
    }

    // compute list of all indexed facets
    facetNameList := Str[,]
    facetNameMap  := Str:Int[:]
    podFacets := compiler.input.podFacets
    podFacets.each |Obj v, Str k|
    {
      facetNameMap[k] = facetNameList.size
      facetNameList.add(k)
    }
    types.each |TypeDef t|
    {
      t.indexedFacets = computeIndexedFacets(t.facets, facetNameList, facetNameMap)
    }

    // write facet names
    out.writeI2(facetNameList.size)
    facetNameList.each |Str n| { out.writeUtf(n) }

    // write pod level facets
    out.writeI2(podFacets.size)
    podFacets.each |Obj v, Str k|
    {
      out.writeI2(facetNameMap[k])
      out.writeUtf(Buf.make.writeObj(v).flip.readAllStr)
    }

    // write types
    out.writeI2(types.size)
    types.each |TypeDef t| { writeTypeDbType(out, t, facetNameMap) }

    out.close
  }

  private FacetDef[] computeIndexedFacets([Str:FacetDef]? all, Str[] list, Str:Int map)
  {
    // if no facets defined, this is easy
    if (all == null || all.size == 0)
      return noFacets

    indexed := all.values

    /* filter just specific values?
    indexed := all.values.findAll |FacetDef f->Bool|
    {
      ft := f.value.ctype
      if (ft.isStr) return true
      if (ft is ListType && ((ListType)ft).v.isStr) return true
      return false
    }
    */

    // map facet names into interned list/map
    indexed.each |FacetDef f|
    {
      name := f.name
      if (map[name] == null)
      {
        map[name] = list.size
        list.add(name)
      }
    }

    return indexed
  }

  private Void writeTypeDbType(OutStream out, TypeDef t, Str:Int facetNames)
  {
    out.writeUtf(t.name)
    out.writeUtf(t.base == null ? "" : t.base.qname)
    out.writeI2(t.mixins.size)
    t.mixins.each |CType m| { out.writeUtf(m.qname) }
    out.writeI4(t.flags)
    out.writeI2(t.indexedFacets.size)
    t.indexedFacets.each |FacetDef f|
    {
      out.writeI2(facetNames[f.name])
      out.writeUtf(f.value.serialize)
    }
  }

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

  private Location location
  private FacetDef[] noFacets := FacetDef[,].ro
}