PageRenderTime 65ms CodeModel.GetById 25ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 0ms

/scalate-wikitext/src/main/scala/org/fusesource/scalate/wikitext/Snippets.scala

http://github.com/scalate/scalate
Scala | 252 lines | 153 code | 36 blank | 63 comment | 17 complexity | 09aabe833d4fb67c90ee1e6fe6d00f27 MD5 | raw file
  1/**
  2 * Copyright (C) 2009-2011 the original author or authors.
  3 * See the notice.md file distributed with this work for additional
  4 * information regarding copyright ownership.
  5 *
  6 * Licensed under the Apache License, Version 2.0 (the "License");
  7 * you may not use this file except in compliance with the License.
  8 * You may obtain a copy of the License at
  9 *
 10 *     http://www.apache.org/licenses/LICENSE-2.0
 11 *
 12 * Unless required by applicable law or agreed to in writing, software
 13 * distributed under the License is distributed on an "AS IS" BASIS,
 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15 * See the License for the specific language governing permissions and
 16 * limitations under the License.
 17 */
 18package org.fusesource.scalate.wikitext
 19
 20import collection.mutable.HashMap
 21import org.eclipse.mylyn.wikitext.core.parser.DocumentBuilder.BlockType
 22import org.eclipse.mylyn.internal.wikitext.confluence.core.block.ParameterizedBlock
 23import io.Source
 24import java.io.File
 25import java.util.regex.{ Pattern, Matcher }
 26import org.eclipse.mylyn.wikitext.core.parser.{ DocumentBuilder, Attributes }
 27import org.fusesource.scalate.util.Log
 28
 29/**
 30 * Helper class to access file containing snippets of code:
 31 * - on the local file system
 32 * - using a full URL
 33 * - using a URL that starts with a predefined prefix
 34 */
 35object Snippets {
 36
 37  val log = Log(getClass); import log._
 38  var errorHandler: (SnippetBlock, Throwable) => Unit = logError
 39
 40  var failOnError = false
 41
 42  /**
 43   * By default lets use Pygmentize if its installed to style snippets
 44   * unless its explicitly disabled via user
 45   */
 46  var usePygmentize: Boolean = Pygmentize.isInstalled
 47
 48  val prefixes = HashMap[String, String]()
 49
 50  /**
 51   * Add a prefix definition to make it easier to access snippets.
 52   * E.g. if you add 'activemq' -> 'http://svn.apache.org/repos/asf/activemq/trunk'
 53   *      you can use 'activemq/pom.xml' as a shorthand notation for 'http://svn.apache.org/repos/asf/activemq/trunk/pom.xml"
 54   *
 55   *
 56   * @param prefix the prefix
 57   * @param location the full URL that should be used for the prefix
 58   */
 59  def addPrefix(prefix: String, location: String): Unit = prefixes.put(prefix, location)
 60
 61  private[wikitext] def handlePrefix(location: String): String = {
 62    val prefix = location.substring(0, location.indexOf("/"))
 63    if (prefixes.contains(prefix)) {
 64      location.replaceFirst(prefix, prefixes(prefix))
 65    } else {
 66      location
 67    }
 68  }
 69
 70  /**
 71   * Get the snippet file contents
 72   */
 73  def getSource(location: String): Source = {
 74    val url = handlePrefix(location)
 75
 76    def isUrl = url.indexOf(':') > 0
 77
 78    var file = new File(location)
 79    if (!file.exists) {
 80      file = new File(url)
 81    }
 82    debug("for location: " + location + " using prefix: " + url)
 83    if (file.exists || !isUrl) {
 84      Source.fromFile(file, "UTF-8")
 85    } else {
 86      Source.fromURL(url, "UTF-8")
 87    }
 88  }
 89
 90  protected def logError(snippet: SnippetBlock, e: Throwable): Unit = {
 91    error(e, "Failed to generate snippet: " + snippet.url + ". " + e)
 92    if (failOnError) {
 93      throw e
 94    }
 95  }
 96}
 97import Snippets.log._
 98
 99/**
100 * Represents a {snippet} block in the wiki markup
101 */
102class SnippetBlock extends ParameterizedBlock {
103
104  var pattern: Pattern = Pattern.compile("\\s*\\{snippet(?::([^\\}]*))?\\}(.*)")
105  var matcher: Matcher = null
106  var lang: Option[String] = None
107  var url: String = _
108  var id: Option[String] = None
109  var pygmentize: Boolean = Snippets.usePygmentize
110
111  lazy val handler: SnippetHandler = {
112    if (pygmentize) {
113      val block = new PygmentsBlock
114      block.setState(state)
115      block.setParser(parser)
116      block.setOption("lang", language)
117      PygmentizeSnippetHandler(block)
118    } else {
119      DefaultSnippetHandler(builder, language)
120    }
121  }
122
123  override def canStart(line: String, lineOffset: Int) = {
124    matcher = pattern.matcher(line)
125    if (lineOffset > 0) {
126      matcher.region(lineOffset, line.length)
127    }
128    matcher.matches()
129  }
130
131  override def processLineContent(line: String, offset: Int) = {
132    setOptions(matcher.group(1))
133    val end = matcher.start(2)
134
135    handler.begin
136    try {
137      for (snippetLine <- getSnippet) {
138        handler.addLine(snippetLine)
139      }
140    } catch {
141      case e: Exception => Snippets.errorHandler(this, e)
142    }
143    handler.done
144
145    if (end < line.length) {
146      state.setLineSegmentEndOffset(end)
147    }
148    setClosed(true)
149    if (end == line.length) { -1 } else { end }
150  }
151
152  /**
153   * Extract the snippet from the Source file
154   */
155  def getSnippet: Iterator[String] = {
156    val lines = Snippets.getSource(url).getLines
157    id match {
158      case None => lines
159      case Some(snippet) => lines.dropWhile(!_.contains("START SNIPPET: " + snippet))
160        .takeWhile(!_.contains("END SNIPPET: " + snippet))
161        .drop(1)
162    }
163  }
164
165  override def setOption(key: String, value: String) = {
166    key match {
167      case "id" => id = Some(value)
168      case "url" => url = value
169      case "lang" => lang = Some(value)
170      case "pygmentize" => pygmentize = value.toBoolean
171      case n => warn("Ignored snippet attribute %s on %s", n, this)
172    }
173  }
174
175  /**
176   * Get the configured language or determine the language from the file extension
177   */
178  def language = {
179    lang match {
180      case None => extension(url)
181      case Some(lang) => lang
182    }
183  }
184
185  /**
186   * Extract the file extension from the URL
187   */
188  def extension(url: String) = {
189    if (url.contains(".")) {
190      url.split('.').last
191    } else {
192      ""
193    }
194  }
195
196  override def toString = "{snippet:url=" + url + "}"
197}
198
199/**
200 * Trait to define a {snippet} handler
201 */
202trait SnippetHandler {
203
204  def begin(): Unit
205  def addLine(line: String): Unit
206  def done(): Unit
207
208}
209
210/**
211 * Default handler for the {snippet} code (renders a <div class="snippet"><pre class="<language>">
212 */
213case class DefaultSnippetHandler(
214  val builder: DocumentBuilder,
215  val language: String) extends SnippetHandler {
216
217  def begin() = {
218    builder.beginBlock(BlockType.DIV, cssClass("snippet"))
219    builder.beginBlock(BlockType.PREFORMATTED, cssClass(language))
220    builder.characters("\n")
221  }
222
223  def addLine(line: String) = {
224    builder.characters(line + "\n")
225  }
226
227  def done() = {
228    builder.endBlock(); // </pre>
229    builder.endBlock(); // </div>
230  }
231
232  /**
233   * Create attributes instance containing the CSS class
234   */
235  def cssClass(cssClass: String) = {
236    val attributes = new Attributes
237    attributes.setCssClass(cssClass)
238    attributes
239  }
240
241}
242
243/**
244 * Uses pygmentize to handles syntax coloring for the {snippet}'s code
245 */
246case class PygmentizeSnippetHandler(val block: PygmentsBlock) extends SnippetHandler {
247
248  def begin() = block.beginBlock
249  def addLine(line: String) = block.handleBlockContent(line)
250  def done() = block.endBlock
251
252}