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