import sbt._ object SiteMap { // represents the configurable aspects of a sitemap entry final case class Entry(changeFreq: String, priority: Double) { assert(priority >= 0.0 && priority <= 1.0, s"Priority must be between 0.0 and 1.0:, was $priority") } def generate(repoBase: File, remoteBase: URI, gzip: Boolean, entry: (File, String) => Option[Entry], log: Logger): (File, Seq[File]) = { def relativize(files: PathFinder): Seq[(File, String)] = files pair relativeTo(repoBase) def entries(files: PathFinder) = relativize(files) flatMap { case (f, path) => entry(f, path).toList map { e => entryXML(e, f, path) } } def entriesXML(entries: Seq[xml.Node]): xml.Elem = { assert(entries.size <= 50000, "A site map cannot contain more than 50,000 entries.") {entries} } def entryXML(e: Entry, f: File, relPath: String) = {remoteBase.resolve(relPath).toString} {lastModifiedString(f)} {e.changeFreq} {e.priority.toString} def singleSiteMap(dir: File, files: PathFinder): Option[File] = { val es = entries(files) if(es.isEmpty) None else Some( writeXMLgz(dir / "sitemap.xml", dir / "sitemap.xml.gz", gzip, entriesXML(es)) ) } def indexEntryXML(sub: File, relPath: String): xml.Elem = {remoteBase.resolve(relPath).toString} {lastModifiedString(sub)} def indexEntriesXML(entries: Seq[xml.Node]): xml.Elem = {entries} def indexEntries(subs: Seq[File]) = relativize(subs) map { case (f, path) => indexEntryXML(f, path) } def siteMapIndex(dir: File, subs: Seq[File]): File = { val xml = indexEntriesXML(indexEntries(subs)) writeXMLgz(dir / "sitemap_index.xml", dir / "sitemap_index.xml.gz", gzip, xml) } def isSymlink(f: File) = f.getCanonicalFile != f.getAbsoluteFile val (symlinks, normal) = (repoBase * DirectoryFilter).get.partition(dir => isSymlink(dir)) log.debug("Detected symlinks: " + symlinks.mkString("\n\t", "\n\t", "")) val subMaps = singleSiteMap(repoBase, (repoBase * "*.html") +++ (symlinks ** "*.html") ).toList ++ normal.flatMap( dir => singleSiteMap(dir, dir ** "*.html").toList ) val index = siteMapIndex(repoBase, subMaps) (index, subMaps) } // generates a string suitable for a sitemap file representing the last modified time of the given File private[this] def lastModifiedString(f: File): String = { val formatter = new java.text.SimpleDateFormat("yyyy-MM-dd") formatter.format(new java.util.Date(f.lastModified)) } // writes the provided XML node to `output` and then gzips it to `gzipped` if `gzip` is true private[this] def writeXMLgz(output: File, gzipped: File, gzip: Boolean, node: xml.Node): File = { writeXML(output, node) if(gzip) { IO.gzip(output, gzipped) gzipped } else output } private[this] def writeXML(output: File, node: xml.Node): Unit = write(output, new xml.PrettyPrinter(1000, 4).format(node)) private[this] def write(output: File, xmlString: String) { // use \n as newline because toString uses PrettyPrinter, which hard codes line endings to be \n IO.write(output, s"\n") IO.append(output, xmlString) } }