/
Renderable.scala
168 lines (133 loc) · 5.63 KB
/
Renderable.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package org.http4s.util
import java.nio.charset.{ Charset, StandardCharsets }
import java.time.{ZoneId, Instant}
import java.time.format.DateTimeFormatter
import java.util.Locale
import scodec.bits.ByteVector
import scala.annotation.tailrec
import scala.collection.immutable.BitSet
/** A type class that describes how to efficiently render a type
* @tparam T the type which will be rendered
*/
trait Renderer[T] {
/** Renders the object to the writer
* @param writer [[Writer]] to render to
* @param t object to render
* @return the same [[Writer]] provided
*/
def render(writer: Writer, t: T): writer.type
}
object Renderer {
def renderString[T: Renderer](t: T): String = new StringWriter().append(t).result
implicit val RFC7231InstantRenderer: Renderer[Instant] = new Renderer[Instant] {
private val dateFormat =
DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz")
.withLocale(Locale.US)
.withZone(ZoneId.of("GMT"))
override def render(writer: Writer, t: Instant): writer.type =
writer << dateFormat.format(t)
}
}
/** Mixin that makes a type writable by a [[Writer]] without needing a [[Renderer]] instance */
trait Renderable extends Any {
/** Base method for rendering this object efficiently */
def render(writer: Writer): writer.type
/** Generates a String rendering of this object */
def renderString: String = Renderer.renderString(this)
override def toString: String = renderString
}
object Renderable {
implicit def renderableInst[T <: Renderable]: Renderer[T] =
genericInstance.asInstanceOf[Renderer[T]]
// Cache the Renderable because GC pauses suck
private val genericInstance = new Renderer[Renderable] {
override def render(writer: Writer, t: Renderable): writer.type =
t.render(writer)
}
}
object Writer {
val HeaderValueDQuote = BitSet("\\\"".map(_.toInt):_*)
}
/** Efficiently accumulate [[Renderable]] representations */
trait Writer {
def append(s: String): this.type
def append(ci: CaseInsensitiveString): this.type = append(ci.toString)
def append(char: Char): this.type = append(char.toString)
def append(float: Float): this.type = append(float.toString)
def append(double: Double): this.type = append(double.toString)
def append(int: Int): this.type = append(int.toString)
def append(long: Long): this.type = append(long.toString)
def append[T](r: T)(implicit R: Renderer[T]): this.type = R.render(this, r)
def quote(s: String, escapedChars: BitSet = Writer.HeaderValueDQuote, escapeChar: Char = '\\'): this.type = {
this << '"'
@tailrec
def go(i: Int): Unit = if (i < s.length) {
val c = s.charAt(i)
if (escapedChars.contains(c.toInt)) this << escapeChar
this << c
go(i + 1)
}
go(0)
this << '"'
}
def addStrings(s: Seq[String], sep: String = "", start: String = "", end: String = ""): this.type = {
append(start)
if (s.nonEmpty) {
append(s.head)
s.tail.foreach(s => append(sep).append(s))
}
append(end)
}
def addStringNel(s: NonEmptyList[String], sep: String = "", start: String = "", end: String = ""): this.type = {
append(start)
append(s.head)
s.tail.foreach(s => append(sep).append(s))
append(end)
}
def addSeq[T: Renderer](s: Seq[T], sep: String = "", start: String = "", end: String = ""): this.type = {
append(start)
if (s.nonEmpty) {
append(s.head)
s.tail.foreach(s => append(s).append(sep))
}
append(end)
}
final def <<(s: String): this.type = append(s)
final def <<#(s: String): this.type = quote(s)
final def <<(s: CaseInsensitiveString): this.type = append(s)
final def <<(char: Char): this.type = append(char)
final def <<(float: Float): this.type = append(float)
final def <<(double: Double): this.type = append(double)
final def <<(int: Int): this.type = append(int)
final def <<(long: Long): this.type = append(long)
final def <<[T: Renderer](r: T): this.type = append(r)
}
/** [[Writer]] that will result in a `String`
* @param size initial buffer size of the underlying `StringBuilder`
*/
class StringWriter(size: Int = StringWriter.InitialCapacity) extends Writer {
private val sb = new java.lang.StringBuilder(size)
def append(s: String): this.type = { sb.append(s); this }
override def append(char: Char): this.type = { sb.append(char); this }
override def append(float: Float): this.type = { sb.append(float); this }
override def append(double: Double): this.type = { sb.append(double); this }
override def append(int: Int): this.type = { sb.append(int); this }
override def append(long: Long): this.type = { sb.append(long); this }
def result: String = sb.toString
}
object StringWriter {
private val InitialCapacity = 64
}
/** [[Writer]] that will result in a `ByteVector`
* @param bv initial ByteVector`
*/
final case class ByteVectorWriter(private var bv: ByteVector = ByteVector.empty,
charset: Charset = StandardCharsets.UTF_8) extends Writer {
override def append(s: String): this.type = { bv = bv ++ ByteVector(s.getBytes(charset)); this}
override def append(char: Char): this.type = append(char.toString)
override def append(float: Float): this.type = append(float.toString)
override def append(double: Double): this.type = append(double.toString)
override def append(int: Int): this.type = append(int.toString)
override def append(long: Long): this.type = append(long.toString)
def toByteVector: ByteVector = bv
}