Merge pull request #1671 from ajozwik/0.13

Improve xml handling
This commit is contained in:
Josh Suereth 2014-10-20 20:21:09 -04:00
commit 577c555d81
13 changed files with 163 additions and 117 deletions

View File

@ -23,10 +23,13 @@ private[sbt] object SbtParser {
sealed trait ParsedSbtFileExpressions {
/** The set of parsed import expressions. */
def imports: Seq[(String, Int)]
/** The set of parsed defintions and/or sbt build settings. */
def settings: Seq[(String, LineRange)]
/** The set of scala tree's for parsed definitions/settings and the underlying string representation.. */
def settingsTrees: Seq[(String, Tree)]
/** Represents the changes we had to perform to the sbt file so that XML will parse correctly. */
def modifiedContent: String
}
@ -200,9 +203,9 @@ private[sbt] object XmlContent {
private val DOUBLE_SLASH = "//"
private val OPEN_BRACKET = s" $OPEN_CURLY_BRACKET "
private val OPEN_BRACKET = s"$OPEN_CURLY_BRACKET"
private val CLOSE_BRACKET = " ) "
private val CLOSE_BRACKET = ")"
/**
*
@ -222,24 +225,25 @@ private[sbt] object XmlContent {
* Cut file for normal text - xml - normal text - xml ....
* @param content - content
* @param ts - import/statements
* @return (text,true) - is xml (text,false) - if normal text
* @return Seq - Right(xml,whiteSpaces) - for xml, Left(statement) - for text
*/
private def splitFile(content: String, ts: Seq[(String, Int, Int)]): Seq[(String, Boolean)] = {
val (statements, index) = ts.foldLeft((Seq.empty[(String, Boolean)], 0)) {
(accSeqIndex, el) =>
val (statement, startIndex, endIndex) = el
val (accSeq, index) = accSeqIndex
val textStatementOption = if (index >= startIndex) {
None
private def splitFile(content: String, ts: Seq[(String, Int, Int)]): Seq[Either[(String), (String, String)]] = {
val (statements, lastIndex) = ts.foldLeft((Seq.empty[Either[(String), (String, String)]], 0)) {
case ((accSeq, index), (statement, startIndex, endIndex)) =>
val toAdd = if (index >= startIndex) {
Seq(Right((statement, "")))
} else {
val s = content.substring(index, startIndex)
Some((s, false))
if (s.trim.isEmpty) {
Seq(Right((statement, s)))
} else {
Seq(Right((statement, "")), Left(s))
}
}
val newAccSeq = (statement, true) +: addOptionToCollection(accSeq, textStatementOption)
(newAccSeq, endIndex)
(toAdd ++ accSeq, endIndex)
}
val endOfFile = content.substring(index, content.length)
val withEndOfFile = (endOfFile, false) +: statements
val endOfFile = content.substring(lastIndex, content.length)
val withEndOfFile = if (endOfFile.isEmpty) statements else Left(endOfFile) +: statements
withEndOfFile.reverse
}
@ -265,6 +269,8 @@ private[sbt] object XmlContent {
}
}
private val CLOSE_XML_TAG = "/>"
/**
* Modified Opening Tag - <aaa/>
* @param offsetIndex - index
@ -273,7 +279,7 @@ private[sbt] object XmlContent {
*/
@tailrec
private def findModifiedOpeningTags(content: String, offsetIndex: Int, acc: Seq[(String, Int, Int)]): Seq[(String, Int, Int)] = {
val endIndex = content.indexOf("/>", offsetIndex)
val endIndex = content.indexOf(CLOSE_XML_TAG, offsetIndex)
if (endIndex == NOT_FOUND_INDEX) {
acc
} else {
@ -352,46 +358,40 @@ private[sbt] object XmlContent {
* @return content with xml with brackets
*/
private def addExplicitXmlContent(content: String, xmlParts: Seq[(String, Int, Int)]): String = {
val statements: Seq[(String, Boolean)] = splitFile(content, xmlParts)
val (correctedStmt, shouldAddCloseBrackets, wasXml, _) = addBracketsIfNecessary(statements)
val closeIfNecessaryCorrectedStmt =
if (shouldAddCloseBrackets && wasXml) {
correctedStmt.head +: CLOSE_BRACKET +: correctedStmt.tail
} else {
correctedStmt
}
closeIfNecessaryCorrectedStmt.reverse.mkString
}
val statements = splitFile(content, xmlParts)
def addBracketsIfNecessary(statements: Seq[(String, Boolean)]): (Seq[String], Boolean, Boolean, String) = {
statements.foldLeft((Seq.empty[String], false, false, "")) {
case ((accStmt, shouldAddCloseBracket, prvWasXml, prvStmt), (stmt, isXml)) =>
if (stmt.trim.isEmpty) {
(stmt +: accStmt, shouldAddCloseBracket, prvWasXml, stmt)
} else {
val (newShouldAddCloseBracket, newStmtAcc) = if (isXml) {
addOpenBracketIfNecessary(accStmt, shouldAddCloseBracket, prvWasXml, prvStmt)
} else if (shouldAddCloseBracket) {
(false, CLOSE_BRACKET +: accStmt)
} else {
(false, accStmt)
}
(stmt +: newStmtAcc, newShouldAddCloseBracket, isXml, stmt)
val (_, seq, lastAdd) = statements.foldLeft[(Option[Either[(String), (String, String)]], Seq[String], Boolean)]((None, Seq.empty[String], false)) {
case ((previousOption, acc, add), element) =>
val (newAcc, newAdd) = (element, previousOption) match {
case (Left(text), _) =>
val accWithClose = if (add) {
addCloseBracket(acc)
} else {
acc
}
(text +: accWithClose, false)
case (Right((xml, nonXml)), Some(Left(text))) =>
val (accWithOpen, added) = if (areBracketsNecessary(text)) {
(OPEN_BRACKET +: acc, true)
} else {
(acc, false)
}
(xml +: (nonXml +: accWithOpen), added)
case (Right((xml, nonXml)), _) =>
(xml +: (nonXml +: acc), add)
}
(Some(element), newAcc, newAdd)
}
val correctedSeq = if (lastAdd) {
addCloseBracket(seq)
} else {
seq
}
correctedSeq.reverse.mkString
}
private def addOpenBracketIfNecessary(stmtAcc: Seq[String], shouldAddCloseBracket: Boolean, prvWasXml: Boolean, prvStatement: String) =
if (prvWasXml) {
(shouldAddCloseBracket, stmtAcc)
} else {
if (areBracketsNecessary(prvStatement)) {
(true, OPEN_BRACKET +: stmtAcc)
} else {
(false, stmtAcc)
}
}
private def addCloseBracket(statements: Seq[String]) = CLOSE_BRACKET +: statements
/**
* Add to head if option is not empty
@ -439,8 +439,15 @@ private[sbt] object XmlContent {
*/
private def areBracketsNecessary(statement: String): Boolean = {
val doubleSlash = statement.indexOf(DOUBLE_SLASH)
val endOfLine = statement.indexOf(END_OF_LINE)
if (doubleSlash == NOT_FOUND_INDEX || (doubleSlash < endOfLine)) {
if (doubleSlash != NOT_FOUND_INDEX) {
val endOfLine = statement.indexOf(END_OF_LINE, doubleSlash)
if (endOfLine == NOT_FOUND_INDEX) {
false
} else {
areBracketsNecessary(statement.substring(endOfLine))
}
} else {
val roundBrackets = statement.lastIndexOf(OPEN_CURLY_BRACKET)
val braces = statement.lastIndexOf(OPEN_PARENTHESIS)
val max = roundBrackets.max(braces)
@ -450,8 +457,7 @@ private[sbt] object XmlContent {
val trimmed = statement.substring(max + 1).trim
trimmed.nonEmpty
}
} else {
false
}
}
}

View File

@ -0,0 +1 @@
*

View File

@ -0,0 +1,72 @@
name := "scala-stm"
organization := "org.scala-stm"
version := "0.8-SNAPSHOT"
scalaVersion := "2.11.2"
crossScalaVersions := Seq("2.11.2", "2.10.4", "2.9.3")
libraryDependencies += ("org.scalatest" %% "scalatest" % "[1.5,)" % "test")
libraryDependencies += ("junit" % "junit" % "4.5" % "test")
// skip exhaustive tests
testOptions += Tests.Argument("-l", "slow")
// test of TxnExecutor.transformDefault must be run by itself
parallelExecution in Test := false
////////////////////
// publishing
pomExtra :=
<url>http://nbronson.github.com/scala-stm/</url>
<licenses>
<license>
<name>BSD</name>
<url>https://github.com/nbronson/scala-stm/blob/master/LICENSE.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:git@github.com:nbronson/scala-stm.git</connection>
<url>git@github.com:nbronson/scala-stm.git</url>
</scm>
<developers>
<developer>
<id>nbronson</id>
<name>Nathan Bronson</name>
<email>ngbronson@gmail.com</email>
</developer>
</developers>
publishMavenStyle := true
publishTo <<= (version) { v: String =>
val base = "https://oss.sonatype.org/"
if (v.trim.endsWith("SNAPSHOT"))
Some("snapshots" at base + "content/repositories/snapshots/")
else
Some("releases" at base + "service/local/staging/deploy/maven2/")
}
// exclude scalatest from the Maven POM
pomPostProcess := { xi: scala.xml.Node =>
import scala.xml._
val badDeps = (xi \\ "dependency") filter {
x => (x \ "artifactId").text != "scala-library"
} toSet
def filt(root: Node): Node = root match {
case x: Elem => {
val ch = x.child filter { !badDeps(_) } map { filt(_) }
Elem(x.prefix, x.label, x.attributes, x.scope, ch: _*)
}
case x => x
}
filt(xi)
}
credentials += Credentials(Path.userHome / ".ivy2" / ".credentials")

View File

@ -0,0 +1,16 @@
import sbt._
val scmpom = taskKey[xml.NodeBuffer]("Node buffer")
scmpom := <scm>
<url>git@github.com:mohiva/play-html-compressor.git</url>
<connection>scm:git:git@github.com:mohiva/play-html-compressor.git</connection>
</scm>
<developers>
<developer>
<id>akkie</id>
<name>Christian Kaps</name>
<url>http://mohiva.com</url>
</developer>
</developers>
<a></a>

View File

@ -0,0 +1 @@
scmpom := <a/><b a="rt">OK</b>

View File

@ -0,0 +1,5 @@
import sbt._
val scmpom = taskKey[xml.NodeBuffer]("Node buffer")
scmpom := (<a/><b a="rt">OK</b>)

View File

@ -1,17 +0,0 @@
k1 := {}
k2 := {}
k3 := {
val x = "hi"
()
}
k4 := { }; k5 := ()
k1 <<= k1 map {_ => sys.error("k1")}
k4 := { val x = k4.value; () }

View File

@ -1,15 +0,0 @@
k1 := {}
k2 := {}
k3 := {
val x = "hi"
()
}
k4 := (); k5 := ()
k1 <<= k1 map {_ => sys.error("k1")}

View File

@ -2,4 +2,4 @@ import sbt._
val scmpom = taskKey[xml.NodeBuffer]("Node buffer")
scmpom := ( <a/><b a="rt">OK</b> )
scmpom := <a/><b a="rt">OK</b>;

View File

@ -2,40 +2,16 @@ package sbt.internals.parser
import java.io.File
import sbt.LineRange
import sbt.{ EvaluateConfigurations, LineRange }
import scala.annotation.tailrec
@deprecated("This class is be removed. Only for test backward compatibility", "1.0")
object EvaluateConfigurationsOriginal {
private[this] def isSpace = (c: Char) => Character isWhitespace c
private[this] def fstS(f: String => Boolean): ((String, Int)) => Boolean = { case (s, i) => f(s) }
private[this] def firstNonSpaceIs(lit: String) = (_: String).view.dropWhile(isSpace).startsWith(lit)
private[this] def or[A](a: A => Boolean, b: A => Boolean): A => Boolean = in => a(in) || b(in)
def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) =
{
val blank = (_: String).forall(isSpace)
val isImport = firstNonSpaceIs("import ")
val comment = firstNonSpaceIs("//")
val blankOrComment = or(blank, comment)
val importOrBlank = fstS(or(blankOrComment, isImport))
val (imports, settings) = lines.zipWithIndex span importOrBlank
(imports filterNot fstS(blankOrComment), groupedLines(settings, blank, blankOrComment))
EvaluateConfigurations.splitExpressions(lines)
}
def groupedLines(lines: Seq[(String, Int)], delimiter: String => Boolean, skipInitial: String => Boolean): Seq[(String, LineRange)] =
{
val fdelim = fstS(delimiter)
@tailrec def group0(lines: Seq[(String, Int)], accum: Seq[(String, LineRange)]): Seq[(String, LineRange)] =
if (lines.isEmpty) accum.reverse
else {
val start = lines dropWhile fstS(skipInitial)
val (next, tail) = start.span { case (s, _) => !delimiter(s) }
val grouped = if (next.isEmpty) accum else (next.map(_._1).mkString("\n"), LineRange(next.head._2, next.last._2 + 1)) +: accum
group0(tail, grouped)
}
group0(lines, Nil)
}
}

View File

@ -70,4 +70,4 @@ abstract class AbstractSessionSettingsSpec(folder: String) extends AbstractSpec
class SessionSettingsSpec extends AbstractSessionSettingsSpec("session-settings")
//class SessionSettingsQuickSpec extends AbstractSessionSettingsSpec("session-settings-quick", true)
class SessionSettingsQuickSpec extends AbstractSessionSettingsSpec("session-settings-quick")

View File

@ -12,6 +12,8 @@ import scala.tools.reflect.ToolBoxError
class SplitExpressionsFilesTest extends AbstractSplitExpressionsFilesTest("/old-format/")
//class SplitExpressionsFilesFailedTest extends AbstractSplitExpressionsFilesTest("/fail-format/")
abstract class AbstractSplitExpressionsFilesTest(pathName: String) extends Specification {
case class SplitterComparison(oldSplitterResult: util.Try[(Seq[(String, Int)], Seq[LineRange])], newSplitterResult: util.Try[(Seq[(String, Int)], Seq[LineRange])])