Skip to content

Commit

Permalink
More efficient intersperse, add scaladoc
Browse files Browse the repository at this point in the history
  • Loading branch information
keynmol committed Oct 29, 2022
1 parent af27cdb commit e3dafca
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 28 deletions.
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ lazy val core = projectMatrix
.nativePlatform(Versions.scalaVersions, disableDependencyChecks)
.enablePlugins(BuildInfoPlugin)
.settings(
buildInfoPackage := "com.indoorvivants.library.internal",
buildInfoPackage := "rendition",
buildInfoOptions += BuildInfoOption.PackagePrivate,
buildInfoKeys := Seq[BuildInfoKey](
version,
scalaVersion,
Expand Down
149 changes: 122 additions & 27 deletions modules/core/src/main/scala/Rendering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,54 +23,149 @@ enum Separator:
case Append(s: String)

case class Rendering(to: LineBuilder, c: Config):
/** This opaque type alias is used to associate current rendering config with
* a particular instance of Rendering - to make sure that two different
* renderings don't overlap
*/
opaque type Context = Config

/** Returns the indentation whitespace at the current nesting level. This
* method does not modify the contents of the LineBuilder
*
* @param c
* @return
*/
inline def indent(using c: Context): String =
(" " * (c.indentSize.value * c.indents.value))

inline def nest(f: Context ?=> Unit)(using context: Context = c) =
/** Increases indentation level for all the rendering calls done inside the
* anonymous function `f`
*
* @param f
* block with rendering calls which will have their indentation increased
* @param context
* current rendering context
* @return
*/
inline def nest(f: Context ?=> Unit)(using context: Context = c): Rendering =
f(using context.copy(indents = context.indents.map(_ + 1)))

this
end nest

/** A shorter alternative to `nest { nest { nest {...` to increase indentation
* level by necessary value
*
* @param count
* number of indentation levels to add to the current one
* @param f
* block with rendering calls which will have their indentation increased
* @param context
* @return
*/
inline def deep(count: Int)(f: Context ?=> Unit)(using
context: Context = c
) =
): Rendering =
f(using context.copy(indents = context.indents.map(_ + count)))

inline def line(n: String)(using context: Context = c) =
to.appendLine(indent(using context) + n)

inline def emptyLine()(using context: Context = c) =
line("")

inline def forkRendering(using context: Context = c) = Rendering(to, context)

this
end deep

/** Writes a line to the builder indented to the correct number of spaces
* (depending on config and nesting)
*
* @param str
* text line to write
* @param context
* current rendering context
* @return
*/
inline def line(str: String)(using context: Context = c): Rendering =
to.appendLine(indent(using context) + str)
this
end line

/** Adds empty line to the builder
*
* @return
*/
inline def emptyLine(): Rendering =
to.appendLine("")
this

/** Creates an instance of Rendering at the current nesting level.
*
* Useful if you want to extract some rendering logic into a separate
* function/method and want it to maintain the nesting level regardless of
* where it's called
*
* @param context
* @return
*/
inline def forkRendering(using context: Context = c): Rendering =
Rendering(to, context)

/** Creates a block, where `start` and `end` will be rendered as separate
* lines at current indentation level, and the rendering block `f` will have
* its indentation increased
*
* @param start
* block header
* @param end
* block footer
* @param f
* rendering block with increased indentation
* @param context
* @return
*/
inline def block(start: String, end: String)(f: Context ?=> Unit)(using
context: Context = c
) =
): Rendering =
line(start)
nest { f }
line(end)

this
end block

/** Renders `items` as separate lines, with separators placed between them.
*
* If you use `Separator.Newline(...)`, then separator will be placed on a
* separate line.
*
* If you use `Separator.Append(...)`, then separator will be added to the
* end of the line
*
* @param separator
* @param items
* @param ctx
* @return
*/
inline def intersperse(
separator: Separator
)(iterator: Seq[String])(using ctx: Context = c) =
if iterator.size < 2 then iterator.foreach(line(_))
)(items: Seq[String])(using ctx: Context = c): Rendering =
if items.size < 2 then items.foreach(line(_))
else
var separators = iterator.size - 1
iterator.foreach { str =>
var separators = items.size - 1
val onEach =
separator match
case Separator.Newline(s) =>
line(str)
if separators > 0 then
line(s)
separators -= 1
case Separator.Append(s) =>
if separators > 0 then
line(str + s)
separators -= 1
else line(str)
(str: String) =>
line(str)
if separators > 0 then
line(s)
separators -= 1

case Separator.Append(s) =>
(str: String) =>
if separators > 0 then
line(str + s)
separators -= 1
else line(str)

items.foreach { str =>
onEach(str)
}
end if
this
end intersperse
end Rendering

object Rendering:
Expand Down

0 comments on commit e3dafca

Please sign in to comment.