Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
add FencePlugin
  • Loading branch information
eed3si9n committed Jan 28, 2015
1 parent 95b00fd commit 161743f
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 32 deletions.
7 changes: 4 additions & 3 deletions app/src/main/scala/app.scala
Expand Up @@ -12,11 +12,11 @@ object Pamflet {
def main(args: Array[String]) {
System.exit(run(args))
}
private def storage(dir: File) = CachedFileStorage(dir)
private def storage(dir: File, ps: List[FencePlugin]) = CachedFileStorage(dir, ps)
def run(args: Array[String]) = {
args match {
case Array(Dir(input), Dir(output)) =>
Produce(storage(input).globalized, output)
Produce(storage(input, fencePlugins).globalized, output)
println("Wrote pamflet to " + output)
0
case Array(Dir(dir)) => preview(dir)
Expand All @@ -34,8 +34,9 @@ object Pamflet {
1
}
}
def fencePlugins: List[FencePlugin] = Nil
def preview(dir: File): Int = {
Preview(storage(dir).globalized).run { server =>
Preview(storage(dir, fencePlugins).globalized).run { server =>
unfiltered.util.Browser.open(
"http://127.0.0.1:%d/".format(server.portBindings.head.port)
)
Expand Down
84 changes: 73 additions & 11 deletions knockoff/src/main/scala/fenced.scala
Expand Up @@ -2,19 +2,46 @@ package pamflet

import com.tristanhunt.knockoff._
import scala.util.parsing.input.{ CharSequenceReader, Position, Reader }
import collection.mutable.ListBuffer

trait FencedDiscounter extends Discounter {
/** List of FencePlugin */
def fencePlugins: List[FencePlugin]

override def newChunkParser : ChunkParser =
new ChunkParser with FencedChunkParser
override def blockToXHTML: Block => xml.Node = block => block match {
case FencedCodeBlock(text, _, language) =>
fencedChunkToXHTML(text, language)
case _ => super.blockToXHTML(block)
override def blockToXHTML: Block => xml.Node = {
val fallback: PartialFunction[Block, xml.Node] = { case x => super.blockToXHTML(x) }
val fs: List[PartialFunction[Block, xml.Node]] =
(fencePlugins map {_.blockToXHTML}) ++ List(FencePlugin.Plain.blockToXHTML, fallback)
fs.reduceLeft(_ orElse _)
}

def notifyBeginLanguage(): Unit =
fencePlugins foreach {_.onBeginLanguage()}
def notifyBeginPage(): Unit =
fencePlugins foreach {_.onBeginPage()}

def fencedChunkToBlock(language: Option[String], content: String, position: Position,
list: ListBuffer[Block]): Block = {
val processors: List[PartialFunction[(Option[String], String, Position, ListBuffer[Block]), Block]] =
fencePlugins ++ List(FencePlugin.Plain)
val f = processors.reduceLeft(_ orElse _)
f((language, content, position, list))
}
def fencedChunkToXHTML(text: Text, language: Option[String]) =
<pre><code class={
language.map { "prettyprint lang-" + _ }.getOrElse("")
}>{ text.content }</code></pre>
}

trait MutableFencedDiscounter extends FencedDiscounter {
private[this] val fencePluginBuffer: ListBuffer[FencePlugin] = ListBuffer()
def registerFencedPlugin(p: FencePlugin): Unit = fencePluginBuffer.append(p)
def fencePlugins = fencePluginBuffer.toList
def clearFencePlugins(): Unit = fencePluginBuffer.clear()
def knockoffWithPlugins(source: java.lang.CharSequence, ps: List[FencePlugin]): Seq[Block] =
{
clearFencePlugins()
ps foreach registerFencedPlugin
super.knockoff(source)
}
}

trait FencedChunkParser extends ChunkParser {
Expand Down Expand Up @@ -46,11 +73,46 @@ trait FencedChunkParser extends ChunkParser {

case class FencedChunk(val content: String, language: Option[String])
extends Chunk {
def appendNewBlock( list : collection.mutable.ListBuffer[Block],
def appendNewBlock( list : ListBuffer[Block],
remaining : List[ (Chunk, Seq[Span], Position) ],
spans : Seq[Span], position : Position,
discounter : Discounter ) {
list += FencedCodeBlock(Text(content), position, language)
discounter : Discounter ): Unit = discounter match {
case fd: FencedDiscounter => list += fd.fencedChunkToBlock(language, content, position, list)
case _ => sys.error("Expected FencedDiscounter")
}
}

/** A FencePlugin must implement the following methods:
* 1. def isDefinedAt(language: Option[String]): Boolean
* 2. def toBlock(language: Option[String], content: String, position: Position, list: ListBuffer[Block]): Block
* 3. def blockToXHTML: PartialFunction[Block, xml.Node]
*
* First, you have to declare what "language" your FencePlugin supports with `isDefinedAt`.
* Next, in `toBlock` evaluate the incoming content and store them in a custom case class that extends `Block`.
* Finally, in `blockToXHTML` turn your custom case class into an xml `Node`.
*/
trait FencePlugin extends PartialFunction[(Option[String], String, Position, ListBuffer[Block]), Block] {
def isDefinedAt(language: Option[String]): Boolean
def toBlock(language: Option[String], content: String, position: Position, list: ListBuffer[Block]): Block
def blockToXHTML: PartialFunction[Block, xml.Node]

override def isDefinedAt(x: (Option[String], String, Position, ListBuffer[Block])): Boolean = isDefinedAt(x._1)
override def apply(x: (Option[String], String, Position, ListBuffer[Block])): Block = toBlock(x._1, x._2, x._3, x._4)
def onBeginLanguage(): Unit = ()
def onBeginPage(): Unit = ()
}
object FencePlugin {
val Plain: FencePlugin = new FencePlugin {
override def isDefinedAt(language: Option[String]): Boolean = true
override def toBlock(language: Option[String], content: String, position: Position, list: ListBuffer[Block]): Block =
FencedCodeBlock(Text(content), position, language)
override def blockToXHTML = {
case FencedCodeBlock(text, _, language) => fencedChunkToXHTML(text, language)
}
def fencedChunkToXHTML(text: Text, language: Option[String]) =
<pre><code class={
language.map { "prettyprint lang-" + _ }.getOrElse("")
}>{ text.content }</code></pre>
}
}

Expand Down
2 changes: 1 addition & 1 deletion knockoff/src/main/scala/parsers.scala
Expand Up @@ -5,7 +5,7 @@ import scala.util.parsing.input.{ CharSequenceReader, Position, Reader }

object PamfletDiscounter
extends Discounter
with FencedDiscounter
with MutableFencedDiscounter
with SmartyDiscounter
with IdentifiedHeaders
with Html5Imgs
23 changes: 11 additions & 12 deletions library/src/main/scala/knock.scala
Expand Up @@ -5,26 +5,25 @@ import com.tristanhunt.knockoff._
import collection.immutable.Map

object Knock {
def knockEither(value: String, propFiles: Seq[File]): Either[Throwable, (String, Seq[Block], Template)] = {
val frontin = Frontin(value)
val template = StringTemplate(propFiles, frontin header, Map())
val raw = template(frontin body)
try {
Right((raw.toString, PamfletDiscounter.knockoff(raw), template))
} catch {
case e: Throwable => Left(e)
}
}
lazy val discounter = PamfletDiscounter
def notifyBeginLanguage(): Unit = discounter.notifyBeginLanguage()
def notifyBeginPage(): Unit = discounter.notifyBeginPage()

def knockEither(value: String, propFiles: Seq[File], ps: List[FencePlugin]): Either[Throwable, (String, Seq[Block], Template)] =
knockEither(value, StringTemplate(propFiles, None, Map()), ps)

def knockEither(value: String, template0: Template): Either[Throwable, (String, Seq[Block], Template)] =
knockEither(value, template0, List())

def knockEither(value: String, template0: Template): Either[Throwable, (String, Seq[Block], Template)] = {
def knockEither(value: String, template0: Template, ps: List[FencePlugin]): Either[Throwable, (String, Seq[Block], Template)] = {
val frontin = Frontin(value)
val template = frontin.header match {
case None => template0
case Some(h) => template0.updated(h)
}
val raw = template(frontin body)
try {
Right((raw.toString, PamfletDiscounter.knockoff(raw), template))
Right((raw.toString, discounter.knockoffWithPlugins(raw, ps), template))
} catch {
case e: Throwable => Left(e)
}
Expand Down
3 changes: 2 additions & 1 deletion library/src/main/scala/printer.scala
@@ -1,5 +1,5 @@
package pamflet
import PamfletDiscounter.toXHTML
import Knock.discounter.toXHTML
import collection.immutable.Map

object Printer {
Expand Down Expand Up @@ -160,6 +160,7 @@ case class Printer(contents: Contents, globalized: Globalized, manifest: Option[
case Right(x) => x
case Left(x) =>
Console.err.println("Error while processing " + x)
// x.printStackTrace()
throw x
}
toXHTML(blocks)
Expand Down
10 changes: 6 additions & 4 deletions library/src/main/scala/storage.scala
Expand Up @@ -13,7 +13,7 @@ trait Storage {
/** Cache FileStorage based on the last modified time.
* This should make previewing much faster on large pamflets.
*/
case class CachedFileStorage(base: File) extends Storage {
case class CachedFileStorage(base: File, ps: List[FencePlugin]) extends Storage {
def allFiles(f0: File): Seq[File] =
f0.listFiles.toVector flatMap {
case dir if dir.isDirectory => allFiles(dir)
Expand All @@ -27,7 +27,7 @@ case class CachedFileStorage(base: File) extends Storage {
CachedFileStorage.cache.get(base) match {
case Some((lm0, gl0)) if lm == lm0 => gl0
case _ =>
val st = FileStorage(base)
val st = FileStorage(base, ps)
val gl = st.globalized
CachedFileStorage.cache(base) = (lm, gl)
gl
Expand All @@ -39,7 +39,7 @@ object CachedFileStorage {
val cache: TrieMap[File, (Long, Globalized)] = TrieMap()
}

case class FileStorage(base: File) extends Storage {
case class FileStorage(base: File, ps: List[FencePlugin]) extends Storage {
def propFile(dir: File): Option[File] =
new File(dir, "template.properties") match {
case file if file.exists => Some(file)
Expand Down Expand Up @@ -69,6 +69,7 @@ case class FileStorage(base: File) extends Storage {
def isSpecialDir(dir: File): Boolean =
dir.isDirectory && ((dir.getName == "layouts") || (dir.getName == "files"))
def rootSection(dir: File, propFiles: Seq[File]): Section = {
Knock.notifyBeginLanguage()
def emptySection = Section("", "", Seq.empty, Nil, defaultTemplate)
if (dir.exists) section("", dir, propFiles).headOption getOrElse emptySection
else emptySection
Expand Down Expand Up @@ -98,10 +99,11 @@ case class FileStorage(base: File) extends Storage {
source.mkString("")
}
def knock(file: File, propFiles: Seq[File]): (String, Seq[Block], Template) =
Knock.knockEither(read(file, defaultTemplate.defaultEncoding), propFiles) match {
Knock.knockEither(read(file, defaultTemplate.defaultEncoding), propFiles, ps) match {
case Right(x) => x
case Left(x) =>
Console.err.println("Error while processing " + file.toString)
// x.printStackTrace()
throw x
}
def isMarkdown(f: File) = {
Expand Down

0 comments on commit 161743f

Please sign in to comment.