Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,19 @@ object ConfigRenderOptions {
def concise = new ConfigRenderOptions(false, false, false, true)
}

case class FormattingOptions(
keepOriginOrder: Boolean = false,
doubleIndent: Boolean = true,
doubleColonAssign: Boolean = false,
newLineAtEnd: Boolean = true
)

final class ConfigRenderOptions private (
val originComments: Boolean,
val comments: Boolean,
val formatted: Boolean,
val json: Boolean
val json: Boolean,
val formattingOptions: FormattingOptions = FormattingOptions()
) {

/**
Expand Down Expand Up @@ -114,6 +122,28 @@ final class ConfigRenderOptions private (
*/
def getFormatted: Boolean = formatted

/**
* Returns options with formatting options set. Formatting is dependant on
* formatted flag.
*
* @param value
* true to enable formatting
* @return
* options with requested setting for formatting
*/
def setFormattingOptions(value: FormattingOptions): ConfigRenderOptions =
if (value == formattingOptions) this
else
new ConfigRenderOptions(originComments, comments, formatted, json, value)

/**
* Returns options used to format the config.
*
* @return
* FormattingOptions
*/
def getFormattingOptions: FormattingOptions = formattingOptions

/**
* Returns options with JSON toggled. JSON means that HOCON extensions
* (omitting commas, quotes for example) won't be used. However, whether to
Expand Down Expand Up @@ -143,7 +173,12 @@ final class ConfigRenderOptions private (
val sb = new StringBuilder("ConfigRenderOptions(")
if (originComments) sb.append("originComments,")
if (comments) sb.append("comments,")
if (formatted) sb.append("formatted,")
if (formatted) {
sb.append("formatted,")
if (formattingOptions.keepOriginOrder) sb.append("keepOriginOrder,")
if (formattingOptions.doubleIndent) sb.append("doubleIndent,")
if (formattingOptions.doubleColonAssign) sb.append("equalsAssign,")
}
if (json) sb.append("json,")
if (sb.charAt(sb.length - 1) == ',') sb.setLength(sb.length - 1)
sb.append(")")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ object AbstractConfigValue {
if (options.getFormatted) {
var remaining = indent
while (remaining > 0) {
sb.append(" ")
sb.append(if (options.formattingOptions.doubleIndent) " " else " ")
remaining -= 1
}
}
Expand Down Expand Up @@ -337,7 +337,9 @@ abstract class AbstractConfigValue private[impl] (val _origin: ConfigOrigin)
if (this.isInstanceOf[ConfigObject]) {
if (options.getFormatted) sb.append(' ')
} else {
sb.append("=")
sb.append(
if (options.formattingOptions.doubleColonAssign) ":" else "="
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@
*/
package org.ekrich.config.impl

import java.{lang => jl}
import java.lang as jl
import java.io.ObjectStreamException
import java.io.Serializable
import java.{util => ju}
import scala.jdk.CollectionConverters._
import scala.util.control.Breaks._
import java.util as ju
import scala.jdk.CollectionConverters.*
import scala.util.control.Breaks.*
import org.ekrich.config.ConfigException
import org.ekrich.config.ConfigObject
import org.ekrich.config.ConfigOrigin
import org.ekrich.config.ConfigRenderOptions
import org.ekrich.config.ConfigValue
import org.ekrich.config.impl.AbstractConfigValue.NotPossibleToResolve

import java.util.{Comparator, Objects}

@SerialVersionUID(2L)
object SimpleConfigObject {
final private[impl] class ResolveModifier private[impl] (
Expand Down Expand Up @@ -79,7 +81,7 @@ object SimpleConfigObject {
}

@SerialVersionUID(1L)
final private class RenderComparator
private class RenderComparator
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure where this guarantee is used.
What rule does my new comparator have to obey?

extends ju.Comparator[String]
with Serializable {
// This is supposed to sort numbers before strings,
Expand All @@ -96,10 +98,29 @@ object SimpleConfigObject {
}
}

@SerialVersionUID(1L)
final private class KeepOriginRenderComparator(
getOriginFor: String => SimpleConfigOrigin
) extends SimpleConfigObject.RenderComparator {
override def compare(a: String, b: String): Int = {
val aOrigin = getOriginFor(a)
val bOrigin = getOriginFor(b)

val aFilename = Option(aOrigin.filename).getOrElse("")
val bFilename = Option(bOrigin.filename).getOrElse("")

val compareFiles = aFilename compareTo bFilename

if (compareFiles != 0) compareFiles
else
aOrigin.lineNumber compareTo bOrigin.lineNumber
}
}

private def mapEquals(
a: ju.Map[String, ConfigValue],
b: ju.Map[String, ConfigValue]
): Boolean =
) =
if (a eq b) true
else if (a.keySet != b.keySet) false
else !a.keySet.asScala.exists(key => a.get(key) != b.get(key))
Expand Down Expand Up @@ -493,11 +514,17 @@ final class SimpleConfigObject(
if (options.getFormatted) sb.append('\n')
} else innerIndent = indentVal
var separatorCount = 0

val keys = new ju.ArrayList[String]
keys.addAll(keySet)
ju.Collections.sort(keys, new SimpleConfigObject.RenderComparator)
// val keys: Array[String] = keySet.toArray(new Array[String](size))
// ju.Arrays.sort(keys, new SimpleConfigObject.RenderComparator)
val ordering =
if (options.formattingOptions.keepOriginOrder)
new SimpleConfigObject.KeepOriginRenderComparator(str =>
value.get(str).origin
)
else new SimpleConfigObject.RenderComparator
ju.Collections.sort(keys, ordering)

for (k <- keys.asScala) {
var v: AbstractConfigValue = null
v = value.get(k)
Expand Down Expand Up @@ -544,7 +571,8 @@ final class SimpleConfigObject(
sb.append("}")
}
}
if (atRoot && options.getFormatted) sb.append('\n')
if (atRoot && options.getFormatted && options.getFormattingOptions.newLineAtEnd)
sb.append('\n')
}

override def get(key: Any): AbstractConfigValue = value.get(key)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.ekrich.config.impl
import org.junit.Assert.*
import org.junit.*
import org.ekrich.config.{
ConfigFactory,
ConfigParseOptions,
ConfigRenderOptions,
FormattingOptions
}

class FormattingOptionsTest extends TestUtilsShared {
val parseOptions = ConfigParseOptions.defaults.setAllowMissing(true)
val myDefaultRenderOptions = ConfigRenderOptions.defaults
.setJson(false)
.setOriginComments(false)
.setComments(true)
.setFormatted(true)

def formatHocon(
str: String
)(implicit formattingOptions: FormattingOptions): String =
ConfigFactory
.parseString(str, parseOptions)
.root
.render(myDefaultRenderOptions.setFormattingOptions(formattingOptions))

@Test
def noNewLineAtTheEnd(): Unit = {
implicit val formattingOptions = FormattingOptions(newLineAtEnd = false)
val in = """r {
|}""".stripMargin
val result = formatHocon(in)
val expected = "r {}"
checkEqualObjects(result, expected)
}

@Test
def keepOriginOrderOfEntries(): Unit = {
Copy link
Copy Markdown
Contributor Author

@kastoestoramadus kastoestoramadus Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekrich
Before, entries always were sorted in alphabetical order.

with

  • ConfigFactory.parseFile
  • render
  • and save to file
    a formatter is ready to go.

implicit val formattingOptions = FormattingOptions(keepOriginOrder = true)

val in = """r {
| p {
| s: ${r.ss}
| }
| f {
| s=t_f
| n="ALA"
| }
|}""".stripMargin
val result = formatHocon(in)

val expected = """r {
| p {
| s=${r.ss}
| }
| f {
| s="t_f"
| n=ALA
| }
|}
|""".stripMargin
checkEqualObjects(result, expected)
}

@Test
def useTwoSpacesIndentation(): Unit = {
implicit val formattingOptions = FormattingOptions(doubleIndent = false)

val in = """r {
| p {
| d {
| s: ${r.ss}
| }
| }
|}""".stripMargin
val result = formatHocon(in)

val expected = """r {
| p {
| d {
| s=${r.ss}
| }
| }
|}
|""".stripMargin
checkEqualObjects(result, expected)
}

@Test
def useDoubleColonAsAssignSign(): Unit = {
implicit val formattingOptions = FormattingOptions(doubleColonAssign = true)

val in = """r {
| s=t_f
| n-m=1
| n:"ALA"
|}""".stripMargin
val result = formatHocon(in)

val expected = """r {
| n:ALA
| "n-m":1
| s:"t_f"
|}
|""".stripMargin
checkEqualObjects(result, expected)
}
}