Last active
August 28, 2024 08:41
-
-
Save Baccata/a990c526374784b70535b307b43ce88c to your computer and use it in GitHub Desktop.
Revisions
-
Baccata revised this gist
Aug 28, 2024 . 1 changed file with 27 additions and 24 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -60,17 +60,16 @@ object ConfigLeftovers { * This is a poor man's solution, in the absence of any known library that would do this in a rather principled * fashion. */ def using[A](config: Config, ignoreKeys: Set[String] = Set.empty)(f: ConfigValue => A): (Option[ConfigValue], A) = { val trackingConfig = transformObject(config.root()) val result = f(trackingConfig) (collapseUnused(trackingConfig, ignoreKeys), result) } private class ConfigObjectWrapper(map: ju.Map[String, ConfigValue], configOrigin: ConfigOrigin) extends ConfigObject { val usedKeys: scala.collection.mutable.Set[String] = scala.collection.mutable.Set.empty[String] /// Whenever a key is accessed, we add it to the list of keys that were queried. override def get(key: Object): ConfigValue = { usedKeys += key.asInstanceOf[String] map.get(key) @@ -156,25 +155,29 @@ object ConfigLeftovers { private def transformList(cl: ConfigList): ConfigListWrapper = new ConfigListWrapper(cl.asScala.map(transform).asJava, cl.origin()) private def collapseUnused(configValue: ConfigValue, ignore: Set[String] = Set.empty): Option[ConfigValue] = configValue match { case co: ConfigObjectWrapper => val map = co.asScala.view .map { case (key, value) => if (co.usedKeys(key)) { val valueType = value.valueType() if (valueType == ConfigValueType.OBJECT || valueType == ConfigValueType.LIST) (key, collapseUnused(value)) else (key, None) } else { (key, Some(value)) } } .collect { case (key, Some(value)) if !(ignore(key)) => (key, value) } .toMap if (map.isEmpty) None else Some(ConfigValueFactory.fromMap(map.asJava, co.origin().description())) case cl: ConfigList => val values = cl.asScala.toList.map(collapseUnused(_)).collect { case Some(value) => value } if (values.forall(v => v.valueType() != ConfigValueType.OBJECT)) None else Some(ConfigValueFactory.fromIterable(values.asJava, cl.origin().description())) case other => Some(other) } } -
Baccata revised this gist
Aug 27, 2024 . 1 changed file with 7 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -20,7 +20,7 @@ object Main { implicit val reader: ConfigReader[C] = deriveReader } case class ABC(a: Int, b: String, c: List[C], d: List[String]) object ABC { implicit val reader: ConfigReader[ABC] = deriveReader } @@ -37,6 +37,8 @@ object Main { | value = 1 | } | ] | d = ["foo", "bar"] | e = ["foo", "bar"] | unused2 = 33 |} |""".stripMargin) @@ -61,13 +63,14 @@ object ConfigLeftovers { def using[A](config: Config)(f: ConfigValue => A): (Option[ConfigValue], A) = { val trackingConfig = transformObject(config.root()) val result = f(trackingConfig) trackingConfig.toConfig() (collapseUnused(trackingConfig), result) } private class ConfigObjectWrapper(map: ju.Map[String, ConfigValue], configOrigin: ConfigOrigin) extends ConfigObject { val usedKeys: scala.collection.mutable.Set[String] = scala.collection.mutable.Set.empty[String] /// Whenever a key is accessed, we add it to the list of keys that were queried. This override def get(key: Object): ConfigValue = { usedKeys += key.asInstanceOf[String] map.get(key) @@ -169,7 +172,8 @@ object ConfigLeftovers { if (map.isEmpty) None else Some(ConfigValueFactory.fromMap(map.asJava, co.origin().description())) case cl: ConfigList => val values = cl.asScala.toList.map(collapseUnused).collect { case Some(value) => value } if (values.forall(v => v.valueType() != ConfigValueType.OBJECT)) None else Some(ConfigValueFactory.fromIterable(values.asJava, cl.origin().description())) case other => Some(other) } -
Baccata revised this gist
Jun 6, 2024 . 1 changed file with 0 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -61,7 +61,6 @@ object ConfigLeftovers { def using[A](config: Config)(f: ConfigValue => A): (Option[ConfigValue], A) = { val trackingConfig = transformObject(config.root()) val result = f(trackingConfig) (collapseUnused(trackingConfig), result) } -
Baccata revised this gist
Jun 6, 2024 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -68,7 +68,7 @@ object ConfigLeftovers { private class ConfigObjectWrapper(map: ju.Map[String, ConfigValue], configOrigin: ConfigOrigin) extends ConfigObject { val usedKeys: scala.collection.mutable.Set[String] = scala.collection.mutable.Set.empty[String] /// Whenever a key is accessed, we add it to the list of keys that were queried. override def get(key: Object): ConfigValue = { usedKeys += key.asInstanceOf[String] map.get(key) -
Baccata created this gist
Jun 6, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,177 @@ //> using dep "com.typesafe:config:1.4.3" //> using dep "com.github.pureconfig::pureconfig:0.17.6" //> using scala "2.13.14" import com.typesafe.config._ import pureconfig.generic.semiauto._ import pureconfig.ConfigReader import pureconfig.ConfigSource import java.{util => ju} import java.util.Collection import java.util.Map.Entry import scala.jdk.CollectionConverters._ object Main { def main(args: Array[String]): Unit = { case class C(value: Int) object C { implicit val reader: ConfigReader[C] = deriveReader } case class ABC(a: Int, b: String, c: List[C]) object ABC { implicit val reader: ConfigReader[ABC] = deriveReader } val config = ConfigFactory.parseString("""|{ | a = 1 | b = "b" | c = [ | { | value = 3 | unused1 = {} | }, | { | value = 1 | } | ] | unused2 = 33 |} |""".stripMargin) val (maybeRemainder, result) = ConfigLeftovers.using(config)(ABC.reader.from(_)) println(maybeRemainder.map(_.render(ConfigRenderOptions.defaults()))) } } // scalafmt: {maxColumn = 120} object ConfigLeftovers { /** Provided a Config and a function that produces a value from a ConfigValue, this will attempt to return a * ConfigValue stripped of all the configuration keys that will have been queried during the execution of the * function. * * This is a poor man's solution, in the absence of any known library that would do this in a rather principled * fashion. */ def using[A](config: Config)(f: ConfigValue => A): (Option[ConfigValue], A) = { val trackingConfig = transformObject(config.root()) val result = f(trackingConfig) trackingConfig.toConfig() (collapseUnused(trackingConfig), result) } private class ConfigObjectWrapper(map: ju.Map[String, ConfigValue], configOrigin: ConfigOrigin) extends ConfigObject { val usedKeys: scala.collection.mutable.Set[String] = scala.collection.mutable.Set.empty[String] /// Whenever a key is accessed, we add it to the list of keys that were queried. This override def get(key: Object): ConfigValue = { usedKeys += key.asInstanceOf[String] map.get(key) } override def origin(): ConfigOrigin = configOrigin override def valueType(): ConfigValueType = ConfigValueType.OBJECT override def render(): String = ConfigValueFactory.fromMap(map, configOrigin.description()).render() override def render(options: ConfigRenderOptions): String = ConfigValueFactory.fromMap(map, configOrigin.description()).render(options) override def containsKey(key: Object): Boolean = map.containsKey(key) override def size(): Int = map.size() override def isEmpty(): Boolean = map.isEmpty() override def unwrapped(): ju.Map[String, Object] = map.asInstanceOf[ju.Map[String, Object]] override def containsValue(value: Object): Boolean = map.containsValue(value) override def keySet(): ju.Set[String] = map.keySet() override def values(): Collection[ConfigValue] = map.values() override def entrySet(): ju.Set[Entry[String, ConfigValue]] = map.entrySet() override def toConfig(): Config = ConfigFactory.parseMap(map) // SHOULD NOT USE DURING DECODING override def withFallback(other: ConfigMergeable): ConfigObject = ??? override def put(key: String, value: ConfigValue): ConfigValue = ??? override def remove(key: Object): ConfigValue = ??? override def putAll(m: ju.Map[_ <: String, _ <: ConfigValue]): Unit = ??? override def clear(): Unit = ??? override def withOnlyKey(key: String): ConfigObject = ??? override def withoutKey(key: String): ConfigObject = ??? override def withValue(key: String, value: ConfigValue): ConfigObject = ??? override def withOrigin(origin: ConfigOrigin): ConfigObject = ??? override def atPath(path: String): Config = ??? override def atKey(key: String): Config = ??? } private class ConfigListWrapper(list: ju.List[ConfigValue], configOrigin: ConfigOrigin) extends ConfigList { def unwrapped(): ju.List[Object] = list.asInstanceOf[ju.List[Object]] def size(): Int = list.size() def isEmpty(): Boolean = list.isEmpty() def contains(o: Object): Boolean = list.contains(o) def iterator(): ju.Iterator[ConfigValue] = list.iterator() def toArray(): Array[Object] = list.toArray() def toArray[T <: Object](x: Array[T with Object]): Array[T with Object] = list.toArray[T](x) def containsAll(c: Collection[_ <: Object]): Boolean = list.containsAll(c) def get(index: Int): ConfigValue = list.get(index) def indexOf(o: Object): Int = list.indexOf(o) def lastIndexOf(o: Object): Int = list.lastIndexOf(o) def listIterator(): ju.ListIterator[ConfigValue] = list.listIterator() def listIterator(x: Int): ju.ListIterator[ConfigValue] = list.listIterator(x) def origin(): ConfigOrigin = configOrigin def valueType(): ConfigValueType = ConfigValueType.LIST def render(): String = render(ConfigRenderOptions.defaults()) def render(options: ConfigRenderOptions): String = ConfigValueFactory.fromIterable(list, configOrigin.description()).render(options) // SHOULD NOT USE DURING DECODING def subList(fromIndex: Int, toIndex: Int): ju.List[ConfigValue] = ??? def withFallback(other: ConfigMergeable): ConfigValue = ??? def add(x: ConfigValue): Boolean = ??? def remove(x: Object): Boolean = ??? def addAll(x: Collection[_ <: ConfigValue]): Boolean = ??? def addAll(x: Int, col: Collection[_ <: ConfigValue]): Boolean = ??? def set(index: Int, element: ConfigValue): ConfigValue = ??? def add(x: Int, cv: ConfigValue): Unit = ??? def remove(x: Int): ConfigValue = ??? def removeAll(c: Collection[_ <: Object]): Boolean = ??? def retainAll(c: Collection[_ <: Object]): Boolean = ??? def atPath(path: String): Config = ??? def clear(): Unit = ??? def atKey(key: String): Config = ??? def withOrigin(origin: ConfigOrigin): ConfigList = ??? } private def transform(configValue: ConfigValue): ConfigValue = configValue match { case co: ConfigObject => transformObject(co) case cl: ConfigList => transformList(cl) case other => other } private def transformObject(co: ConfigObject): ConfigObjectWrapper = new ConfigObjectWrapper(co.asScala.view.mapValues(transform).toMap.asJava, co.origin()) private def transformList(cl: ConfigList): ConfigListWrapper = new ConfigListWrapper(cl.asScala.map(transform).asJava, cl.origin()) private def collapseUnused(configValue: ConfigValue): Option[ConfigValue] = configValue match { case co: ConfigObjectWrapper => val map = co.asScala.view .map { case (key, value) => if (co.usedKeys(key)) { val valueType = value.valueType() if (valueType == ConfigValueType.OBJECT || valueType == ConfigValueType.LIST) (key, collapseUnused(value)) else (key, None) } else (key, Some(value)) } .collect { case (key, Some(value)) => (key, value) } .toMap if (map.isEmpty) None else Some(ConfigValueFactory.fromMap(map.asJava, co.origin().description())) case cl: ConfigList => val values = cl.asScala.toList.map(collapseUnused).collect { case Some(value) => value } if (values.isEmpty) None else Some(ConfigValueFactory.fromIterable(values.asJava, cl.origin().description())) case other => Some(other) } }