PageRenderTime 30ms CodeModel.GetById 11ms app.highlight 15ms RepoModel.GetById 2ms app.codeStats 0ms

/scalate-core/src/main/scala/org/fusesource/scalate/console/ConsoleHelper.scala

http://github.com/scalate/scalate
Scala | 282 lines | 166 code | 43 blank | 73 comment | 31 complexity | 3c2f7ee6069597638f98312e94a739a0 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.console
 19
 20import _root_.java.util.regex.Pattern
 21import java.io.File
 22
 23import _root_.javax.servlet.ServletContext
 24import _root_.org.fusesource.scalate.RenderContext
 25import _root_.org.fusesource.scalate.servlet.ServletRenderContext
 26import org.fusesource.scalate.util.{ Log, SourceMap, SourceMapInstaller }
 27
 28import scala.collection.JavaConverters._
 29import scala.collection.immutable.SortedMap
 30import scala.collection.mutable.{ ArrayBuffer, ListBuffer }
 31import scala.io.Source
 32import scala.util.parsing.input.{ OffsetPosition, Position }
 33import scala.xml.NodeSeq
 34
 35case class SourceLine(
 36  line: Int,
 37  source: String) {
 38
 39  def style(errorLine: Int): String = if (line == errorLine) "line error" else "line"
 40
 41  def nonBlank = source != null && source.length > 0
 42
 43  /**
 44   * Return a tuple of the prefix, the error character and the postfix of this source line
 45   * to highlight the error at the given column
 46   */
 47  def splitOnCharacter(col: Int): (String, String, String) = {
 48    val length = source.length
 49    if (col >= length) {
 50      (source, "", "")
 51    } else {
 52      val next = col + 1
 53      val prefix = source.substring(0, col)
 54      val ch = if (col < length) source.substring(col, next) else ""
 55      val postfix = if (next < length) source.substring(next, length) else ""
 56      (prefix, ch, postfix)
 57    }
 58  }
 59}
 60
 61object ConsoleHelper extends Log
 62
 63/**
 64 * Helper snippets for creating the console
 65 *
 66 * @version $Revision : 1.1 $
 67 */
 68class ConsoleHelper(
 69  context: RenderContext) extends ConsoleSnippets {
 70  import context._
 71
 72  val consoleParameter = "_scalate"
 73
 74  def servletContext: ServletContext = context.asInstanceOf[ServletRenderContext].servletContext
 75  def renderContext = context
 76
 77  // TODO figure out the viewName from the current template?
 78  def viewName = "index"
 79
 80  /**
 81   * Returns the class name of the current resource
 82   */
 83  def resourceClassName: Option[String] = attributes.get("it") match {
 84    case Some(it: AnyRef) => Some(it.getClass.getName)
 85    case _ => None
 86  }
 87
 88  def isDevelopmentMode = context.engine.isDevelopmentMode
 89
 90  /**
 91   * Returns an attempt at finding the source file for the current resource.
 92   *
 93   * TODO use bytecode swizzling to find the accurate name from the debug info in
 94   * the class file!
 95   */
 96  def resourceSourceFile: Option[File] = resourceClassName match {
 97    case Some(name: String) =>
 98      val fileName = name.replace('.', '/')
 99      val prefixes = List("src/main/scala/", "src/main/java/")
100      val postfixes = List(".scala", ".java")
101
102      val names = for (prefix <- prefixes; postfix <- postfixes) yield new File(prefix + fileName + postfix)
103      names.find(_.exists)
104
105    case _ => None
106  }
107
108  /**
109   * Returns all the available archetypes for the current view name
110   */
111  def archetypes: Array[Archetype] = {
112    val dir = "/WEB-INF/archetypes/" + viewName
113    var files: Array[File] = Array()
114    val fileName = realPath(dir)
115    if (fileName != null) {
116      val file = new File(fileName)
117      if (file.exists && file.isDirectory) {
118        files = file.listFiles
119      }
120    }
121    files.map(f => new Archetype(new File(dir, f.getName)))
122  }
123
124  /**
125   * Creates the newly created template name if there can be one for the current resource
126   */
127  def newTemplateName(): Option[String] = resourceClassName match {
128    case Some(resource) =>
129      val prefix = "/" + resource.replace('.', '/') + "."
130
131      if (templates.exists(_.startsWith(prefix)) == false) {
132        Some(prefix + viewName)
133      } else {
134        None
135      }
136    case _ => None
137  }
138
139  /**
140   * Returns the current template names used in the current context
141   */
142  def templates: List[String] = attributes.get("scalateTemplates") match {
143    case Some(list: List[_]) =>
144      list.map(_.asInstanceOf[String]).distinct.sortWith(_ < _)
145    case _ => Nil
146  }
147
148  /**
149   * Returns the current layouts used in the current context
150   */
151  def layouts: List[String] = attributes.get("scalateLayouts") match {
152    case Some(list: List[_]) =>
153      list.map(_.asInstanceOf[String]).distinct.sortWith(_ < _)
154    case _ => Nil
155  }
156
157  /**
158   * Returns true if the option is enabled
159   */
160  def optionEnabled(name: String): Boolean = context.asInstanceOf[ServletRenderContext].parameterValues(consoleParameter).contains(name)
161
162  /**
163   * Link to the current page with the option enabled
164   */
165  def enableLink(name: String): String = context.asInstanceOf[ServletRenderContext].currentUriPlus(consoleParameter + "=" + name)
166
167  /**
168   * Link to the current page with the option disabled
169   */
170  def disableLink(name: String): String = context.asInstanceOf[ServletRenderContext] currentUriMinus (consoleParameter + "=" + name)
171
172  /**
173   * Retrieves a chunk of lines either side of the given error line
174   */
175  def lines(template: String, errorLine: Int, chunk: Int): scala.collection.Seq[SourceLine] = {
176    val file = realPath(template)
177    if (file != null) {
178      val source = Source.fromFile(file)
179      val start = (errorLine - chunk).min(0)
180      val end = start + chunk
181
182      val list = new ListBuffer[SourceLine]
183      val lines = source.getLines().toIndexedSeq
184      for (i <- 1.to(end)) {
185        val code = lines(i)
186        if (i >= start) {
187          list += SourceLine(i, code)
188        }
189      }
190      list
191    } else {
192      Nil
193    }
194  }
195
196  /**
197   * Retrieves a chunk of lines either side of the given error line
198   */
199  def lines(template: String, pos: Position, chunk: Int = 5): scala.collection.Seq[SourceLine] = {
200    pos match {
201      case op: OffsetPosition =>
202
203        // OffsetPosition's already are holding onto the file contents
204        val index: Array[String] = {
205          val source = op.source
206          val rc = new ArrayBuffer[String]
207          var start = 0
208          for (i <- 0 until source.length) {
209            if (source.charAt(i) == '\n') {
210              rc += source.subSequence(start, i).toString.stripLineEnd
211              start = i + 1
212            }
213          }
214          rc.toArray
215        }
216
217        val start = (pos.line - chunk).max(1)
218        val end = (pos.line + chunk).min(index.length)
219
220        val list = new ListBuffer[SourceLine]
221        for (i <- start to end) {
222          list += SourceLine(i, index(i - 1))
223        }
224        list
225
226      case _ =>
227
228        // We need to manually load the file..
229        lines(template, pos.line, chunk)
230    }
231
232  }
233
234  def systemProperties: SortedMap[String, String] = {
235    // TODO is there a better way?
236    val m: Map[String, String] = System.getProperties.asScala.toMap
237    SortedMap(m.iterator.toSeq: _*)
238  }
239
240  // Error Handling helper methods
241  //-------------------------------------------------------------------------
242  def exception = attributes("javax.servlet.error.exception")
243
244  def errorMessage = attributeOrElse("javax.servlet.error.message", "")
245
246  def errorRequestUri = attributeOrElse("javax.servlet.error.request_uri", "")
247
248  def errorCode = attributeOrElse("javax.servlet.error.status_code", 500)
249
250  def renderStackTraceElement(stack: StackTraceElement): NodeSeq = {
251    var rc: NodeSeq = null
252
253    // Does it look like a scalate template class??
254    val className = stack.getClassName.split(Pattern.quote(".")).last
255    if (className.startsWith("$_scalate_$")) {
256      // Then try to load it's smap info..
257      var file = RenderContext().engine.bytecodeDirectory
258      file = new File(file, stack.getClassName.replace('.', '/') + ".class")
259      try {
260        val smap = SourceMap.parse(SourceMapInstaller.load(file))
261        // And then render a link to the original template file.
262        smap.mapToStratum(stack.getLineNumber) match {
263          case None =>
264          case Some((file, line)) =>
265            rc = editLink(file, Some(line), Some(1)) {
266              RenderContext() << <pre class="stacktrace">at ({ file }:{ line })</pre>
267            }
268        }
269      } catch {
270        // ignore errors trying to load the smap... we can fallback
271        // to rendering a plain stack line.
272        case e: Throwable =>
273      }
274    }
275
276    if (rc == null)
277      <pre class="stacktrace">at { stack.getClassName }.{ stack.getMethodName }({ stack.getFileName }:{ stack.getLineNumber })</pre>
278    else
279      rc
280  }
281
282}