From 1f5efb9873dcaa05104d84f60111ed9c52f86b89 Mon Sep 17 00:00:00 2001 From: drdozer Date: Sat, 2 Jul 2016 22:51:03 +0100 Subject: [PATCH 1/6] Implemented pretty-printing for the text back-end. --- .../src/main/scala/scalatags/Text.scala | 65 +++++++++++++++---- .../main/scala/scalatags/text/Builder.scala | 5 +- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/scalatags/shared/src/main/scala/scalatags/Text.scala b/scalatags/shared/src/main/scala/scalatags/Text.scala index cdba63b0..b0846779 100644 --- a/scalatags/shared/src/main/scala/scalatags/Text.scala +++ b/scalatags/shared/src/main/scala/scalatags/Text.scala @@ -98,17 +98,17 @@ object Text Objects.requireNonNull(v) def render = { val strb = new StringBuilder() - writeTo(strb) + writeTo(strb, 0, 0) strb.toString() } - def writeTo(strb: StringBuilder) = Escaping.escape(v, strb) + def writeTo(strb: StringBuilder, indentBy: Int, depth: Int) = Escaping.escape(v, strb) } object StringFrag extends Companion[StringFrag] object RawFrag extends Companion[RawFrag] case class RawFrag(v: String) extends text.Frag { Objects.requireNonNull(v) def render = v - def writeTo(strb: StringBuilder) = strb ++= v + def writeTo(strb: StringBuilder, indentBy: Int, depth: Int) = strb ++= v } class GenericAttr[T] extends AttrValue[T]{ @@ -157,15 +157,27 @@ object Text * because I've inlined a whole lot of things to improve the performance of this code * ~4x from what it originally was, which is a pretty nice speedup */ - def writeTo(strb: StringBuilder): Unit = { + override def writeTo(strb: StringBuilder, indentBy: Int, depth: Int): Unit = { val builder = new text.Builder() build(builder) + val ind = indentBy * depth + val indenting = indentBy > 0 + val inlineContent = builder.childIndex == 1 && !builder.children(0).isInstanceOf[TypedTag[Output]] + + println(s"scalatags: inlineContent=$inlineContent children=${builder.children.length} ${ + if(builder.children.length == 1) !builder.children(0).isInstanceOf[TypedTag[Output]] else builder.children.to[List]}") + // tag + var i = 0 + while (i < ind) { + strb += ' ' + i+=1 + } strb += '<' ++= tag // attributes - var i = 0 + i = 0 while (i < builder.attrIndex){ val pair = builder.attrs(i) strb += ' ' ++= pair._1 ++= "=\"" @@ -174,20 +186,41 @@ object Text i += 1 } - if (builder.childIndex == 0 && void) { - // No children - close tag - strb ++= " />" + if (builder.childIndex == 0) { + if (void) { + // No children - self-closing tag + strb ++= " />" + } else { + // no children - close tag + strb ++= ">' + } + if (indenting) strb += '\n' + } else if (inlineContent) { + strb += '>' + builder.children(0).writeTo(strb, indentBy, 0) + strb ++= "' + if (indenting) strb += '\n' } else { strb += '>' + if (indenting) strb += '\n' // Childrens + val d = depth + 1 var i = 0 while(i < builder.childIndex){ - builder.children(i).writeTo(strb) + builder.children(i).writeTo(strb, indentBy, d) i += 1 } // Closing tag + + val ind = indentBy * depth + i = 0 + while (i < ind) { + strb += ' ' + i+=1 + } strb ++= "' + if (indenting) strb += '\n' } } @@ -198,12 +231,20 @@ object Text /** * Converts an ScalaTag fragment into an html string */ - override def toString = { + final def toString(indentBy: Int) = { val strb = new StringBuilder - writeTo(strb) + writeTo(strb, indentBy, 0) strb.toString() } - def render: Output = this.toString.asInstanceOf[Output] + + /** + * Converts an ScalaTag fragment into an html string + */ + override final def toString = toString(0) + + def render(indentBy: Int): Output = this.toString(indentBy).asInstanceOf[Output] + + def render: Output = render(0) } diff --git a/scalatags/shared/src/main/scala/scalatags/text/Builder.scala b/scalatags/shared/src/main/scala/scalatags/text/Builder.scala index cc865649..da8b1202 100644 --- a/scalatags/shared/src/main/scala/scalatags/text/Builder.scala +++ b/scalatags/shared/src/main/scala/scalatags/text/Builder.scala @@ -67,7 +67,6 @@ class Builder(var children: Array[Frag] = new Array(4), } } trait Frag extends generic.Frag[Builder, String]{ - def writeTo(strb: StringBuilder): Unit - def render: String - def applyTo(b: Builder) = b.addChild(this) + def writeTo(strb: StringBuilder, indentBy: Int, depth: Int): Unit + override def applyTo(b: Builder) = b.addChild(this) } From 5c0567d1c7d67379376e0bd2eec6146fb94dc776 Mon Sep 17 00:00:00 2001 From: drdozer Date: Sat, 2 Jul 2016 23:12:58 +0100 Subject: [PATCH 2/6] Removed debug statements --- scalatags/shared/src/main/scala/scalatags/Text.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/scalatags/shared/src/main/scala/scalatags/Text.scala b/scalatags/shared/src/main/scala/scalatags/Text.scala index b0846779..88a9db2a 100644 --- a/scalatags/shared/src/main/scala/scalatags/Text.scala +++ b/scalatags/shared/src/main/scala/scalatags/Text.scala @@ -165,9 +165,6 @@ object Text val indenting = indentBy > 0 val inlineContent = builder.childIndex == 1 && !builder.children(0).isInstanceOf[TypedTag[Output]] - println(s"scalatags: inlineContent=$inlineContent children=${builder.children.length} ${ - if(builder.children.length == 1) !builder.children(0).isInstanceOf[TypedTag[Output]] else builder.children.to[List]}") - // tag var i = 0 while (i < ind) { From 05f456033f91df4aa69c7af64bc3b7cd3e4324c6 Mon Sep 17 00:00:00 2001 From: drdozer Date: Wed, 6 Jul 2016 23:08:25 +0100 Subject: [PATCH 3/6] Added unit test cases for pretty-printing --- .../test/scala/scalatags/text/TextTests.scala | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala b/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala index 4bab9305..274210a6 100644 --- a/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala +++ b/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala @@ -10,6 +10,51 @@ object TextTests extends TestSuite{ val sample = div("omg") assert(sample.toString == "
omg
") } + + 'helloWorld_indent{ + val sample = div("omg").render(2) + val expected = "
omg
\n" + assert(sample == expected) + } + + 'nested_indent{ + val sample = div(span("omg")).render(2) + val expected = + """
+ | omg + |
+ |""".stripMargin + assert(sample == expected) + } + + 'voidTag_indent{ + val sample = "void".voidTag[String].render(2) + val expected = "\n" + assert(sample == expected) + } + + 'tag_indent{ + val sample = "tag".tag[String].render(2) + val expected = "\n" + assert(sample == expected) + } + + 'aList_indent{ + val sample = ol( + li("one"), + li(), + li("three") + ).render(2) + val expected = + """
    + |
  1. one
  2. + |
  3. + |
  4. three
  5. + |
+ |""".stripMargin + assert(sample == expected) + } + /** * Tests the usage of the pre-defined tags, as well as creating * the tags on the fly from Strings @@ -41,5 +86,6 @@ object TextTests extends TestSuite{ import scalatags.Text.all._ val thing: Tag = div } + } } From c9716aa373647a61b7f413faf87283a54b4969a6 Mon Sep 17 00:00:00 2001 From: drdozer Date: Wed, 6 Jul 2016 23:54:00 +0100 Subject: [PATCH 4/6] Handles multi-child string content now. Added a richer integration test. --- .../src/main/scala/scalatags/Text.scala | 22 +++++++++---------- .../test/scala/scalatags/text/TextTests.scala | 21 +++++++++++++----- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/scalatags/shared/src/main/scala/scalatags/Text.scala b/scalatags/shared/src/main/scala/scalatags/Text.scala index 88a9db2a..58104454 100644 --- a/scalatags/shared/src/main/scala/scalatags/Text.scala +++ b/scalatags/shared/src/main/scala/scalatags/Text.scala @@ -163,7 +163,8 @@ object Text val ind = indentBy * depth val indenting = indentBy > 0 - val inlineContent = builder.childIndex == 1 && !builder.children(0).isInstanceOf[TypedTag[Output]] + val startsWithString = builder.childIndex > 0 && !builder.children(0).isInstanceOf[TypedTag[Output]] + val endsWithString = builder.childIndex > 0 && !builder.children(builder.childIndex - 1).isInstanceOf[TypedTag[Output]] // tag var i = 0 @@ -192,14 +193,9 @@ object Text strb ++= ">' } if (indenting) strb += '\n' - } else if (inlineContent) { - strb += '>' - builder.children(0).writeTo(strb, indentBy, 0) - strb ++= "' - if (indenting) strb += '\n' } else { strb += '>' - if (indenting) strb += '\n' + if (indenting & !startsWithString) strb += '\n' // Childrens val d = depth + 1 var i = 0 @@ -210,11 +206,13 @@ object Text // Closing tag - val ind = indentBy * depth - i = 0 - while (i < ind) { - strb += ' ' - i+=1 + if(!endsWithString) { + val ind = indentBy * depth + i = 0 + while (i < ind) { + strb += ' ' + i += 1 + } } strb ++= "' if (indenting) strb += '\n' diff --git a/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala b/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala index 274210a6..43eb3ec5 100644 --- a/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala +++ b/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala @@ -41,20 +41,31 @@ object TextTests extends TestSuite{ 'aList_indent{ val sample = ol( + `class` := "myList", li("one"), - li(), - li("three") + li(float.right), + li( + span(3) + ) ).render(2) val expected = - """
    + """
      |
    1. one
    2. - |
    3. - |
    4. three
    5. + |
    6. + |
    7. + | 3 + |
    8. |
    |""".stripMargin assert(sample == expected) } + 'multiSeq_indent{ + val sample = span("one", "two", "three").render(2) + val expected = "onetwothree\n" + assert(sample == expected) + } + /** * Tests the usage of the pre-defined tags, as well as creating * the tags on the fly from Strings From 00239085ad3299d4e8cb340ef56d07ea3eafc0f4 Mon Sep 17 00:00:00 2001 From: drdozer Date: Thu, 7 Jul 2016 14:01:57 +0100 Subject: [PATCH 5/6] Fairly major overhaul of the logic of pretty-print indentation, to handle mixed content gracefully. --- .../src/main/scala/scalatags/Text.scala | 73 ++++++++++--------- .../main/scala/scalatags/text/Builder.scala | 11 ++- .../test/scala/scalatags/text/TextTests.scala | 23 ++++-- 3 files changed, 63 insertions(+), 44 deletions(-) diff --git a/scalatags/shared/src/main/scala/scalatags/Text.scala b/scalatags/shared/src/main/scala/scalatags/Text.scala index 58104454..b6dade22 100644 --- a/scalatags/shared/src/main/scala/scalatags/Text.scala +++ b/scalatags/shared/src/main/scala/scalatags/Text.scala @@ -98,17 +98,23 @@ object Text Objects.requireNonNull(v) def render = { val strb = new StringBuilder() - writeTo(strb, 0, 0) + writeTo(strb, 0, 0, false) strb.toString() } - def writeTo(strb: StringBuilder, indentBy: Int, depth: Int) = Escaping.escape(v, strb) + def writeTo(strb: StringBuilder, indentBy: Int, depth: Int, fromTag: Boolean) = { + Escaping.escape(v, strb) + false + } } object StringFrag extends Companion[StringFrag] object RawFrag extends Companion[RawFrag] case class RawFrag(v: String) extends text.Frag { Objects.requireNonNull(v) def render = v - def writeTo(strb: StringBuilder, indentBy: Int, depth: Int) = strb ++= v + def writeTo(strb: StringBuilder, indentBy: Int, depth: Int, fromTag: Boolean) = { + strb ++= v + false + } } class GenericAttr[T] extends AttrValue[T]{ @@ -157,31 +163,30 @@ object Text * because I've inlined a whole lot of things to improve the performance of this code * ~4x from what it originally was, which is a pretty nice speedup */ - override def writeTo(strb: StringBuilder, indentBy: Int, depth: Int): Unit = { + override def writeTo(strb: StringBuilder, indentBy: Int, depth: Int, fromTag: Boolean) = { val builder = new text.Builder() build(builder) - val ind = indentBy * depth val indenting = indentBy > 0 - val startsWithString = builder.childIndex > 0 && !builder.children(0).isInstanceOf[TypedTag[Output]] - val endsWithString = builder.childIndex > 0 && !builder.children(builder.childIndex - 1).isInstanceOf[TypedTag[Output]] + val indent: String = if(indenting) " " * (indentBy * depth) else null // tag - var i = 0 - while (i < ind) { - strb += ' ' - i+=1 + if(indenting && fromTag) { + strb += '\n' + strb ++= indent } strb += '<' ++= tag // attributes - i = 0 - while (i < builder.attrIndex){ - val pair = builder.attrs(i) - strb += ' ' ++= pair._1 ++= "=\"" - Escaping.escape(pair._2, strb) - strb += '\"' - i += 1 + { + var i = 0 + while (i < builder.attrIndex){ + val pair = builder.attrs(i) + strb += ' ' ++= pair._1 ++= "=\"" + Escaping.escape(pair._2, strb) + strb += '\"' + i += 1 + } } if (builder.childIndex == 0) { @@ -192,31 +197,29 @@ object Text // no children - close tag strb ++= ">' } - if (indenting) strb += '\n' } else { strb += '>' - if (indenting & !startsWithString) strb += '\n' + var ft = true + // Childrens - val d = depth + 1 - var i = 0 - while(i < builder.childIndex){ - builder.children(i).writeTo(strb, indentBy, d) - i += 1 + { + val d = depth + 1 + var i = 0 + while (i < builder.childIndex) { + ft = builder.children(i).writeTo(strb, indentBy, d, ft) + i += 1 + } } // Closing tag - - if(!endsWithString) { - val ind = indentBy * depth - i = 0 - while (i < ind) { - strb += ' ' - i += 1 - } + if(indenting && ft) { + strb += '\n' + strb ++= indent } strb ++= "' - if (indenting) strb += '\n' } + + true } def apply(xs: Modifier*): TypedTag[Output] = { @@ -228,7 +231,7 @@ object Text */ final def toString(indentBy: Int) = { val strb = new StringBuilder - writeTo(strb, indentBy, 0) + writeTo(strb, indentBy, 0, false) strb.toString() } diff --git a/scalatags/shared/src/main/scala/scalatags/text/Builder.scala b/scalatags/shared/src/main/scala/scalatags/text/Builder.scala index da8b1202..a0b7f414 100644 --- a/scalatags/shared/src/main/scala/scalatags/text/Builder.scala +++ b/scalatags/shared/src/main/scala/scalatags/text/Builder.scala @@ -67,6 +67,15 @@ class Builder(var children: Array[Frag] = new Array(4), } } trait Frag extends generic.Frag[Builder, String]{ - def writeTo(strb: StringBuilder, indentBy: Int, depth: Int): Unit + /** + * Write into a string buffer. + * + * @param strb buffer to write into + * @param indentBy the amount to indent per unit depth + * @param depth depth of this node + * @param fromTag true if the previous node to be written to strb is a tag + * @return true if the last thing to be written to strb by this method was a tag + */ + def writeTo(strb: StringBuilder, indentBy: Int, depth: Int, fromTag: Boolean): Boolean override def applyTo(b: Builder) = b.addChild(this) } diff --git a/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala b/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala index 43eb3ec5..d684bf85 100644 --- a/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala +++ b/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala @@ -13,7 +13,7 @@ object TextTests extends TestSuite{ 'helloWorld_indent{ val sample = div("omg").render(2) - val expected = "
    omg
    \n" + val expected = "
    omg
    " assert(sample == expected) } @@ -22,20 +22,19 @@ object TextTests extends TestSuite{ val expected = """
    | omg - |
    - |""".stripMargin + |""".stripMargin assert(sample == expected) } 'voidTag_indent{ val sample = "void".voidTag[String].render(2) - val expected = "\n" + val expected = "" assert(sample == expected) } 'tag_indent{ val sample = "tag".tag[String].render(2) - val expected = "\n" + val expected = "" assert(sample == expected) } @@ -55,14 +54,22 @@ object TextTests extends TestSuite{ |
  1. | 3 |
  2. - |
- |""".stripMargin + |""".stripMargin assert(sample == expected) } 'multiSeq_indent{ val sample = span("one", "two", "three").render(2) - val expected = "onetwothree\n" + val expected = "onetwothree" + assert(sample == expected) + } + + 'mixedContent_indent{ + val sample = div("one", span("two"), "three", span(span("four")), "five").render(2) + val expected = + """
onetwothree + | four + | five
""".stripMargin assert(sample == expected) } From 18afccb8cc45f7111495254287373eb9a1918f90 Mon Sep 17 00:00:00 2001 From: drdozer Date: Thu, 7 Jul 2016 14:05:38 +0100 Subject: [PATCH 6/6] Added a test with whitespace pretty printing. --- .../shared/src/test/scala/scalatags/text/TextTests.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala b/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala index d684bf85..1c20f3f0 100644 --- a/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala +++ b/scalatags/shared/src/test/scala/scalatags/text/TextTests.scala @@ -73,6 +73,14 @@ object TextTests extends TestSuite{ assert(sample == expected) } + 'whitespaceContent_indent{ + val sample = span("one", " and ", "two\n").render(2) + val expected = + """one and two + |""".stripMargin + assert(sample == expected) + } + /** * Tests the usage of the pre-defined tags, as well as creating * the tags on the fly from Strings