Last active
January 10, 2025 01:38
-
-
Save ghostdogpr/3b5bd33dd3356e16434db42595924bf4 to your computer and use it in GitHub Desktop.
Code generation to create lenses
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 characters
Compile / sourceGenerators / fileInputs ++= Seq( | |
baseDirectory.value.toGlob / "inputDir" | |
), | |
Compile / sourceGenerators ++= Seq( | |
Def.task { | |
val source: File = baseDirectory.value / "inputDir" | |
val lenses = CodeGenerator.generateLenses(source.toPath) | |
val file = (Compile / sourceManaged).value / "codegen" / "Lenses.scala" | |
IO.write(file, lenses) | |
Seq(file) | |
}.taskValue | |
) |
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 characters
import java.nio.file.Path | |
import scala.meta.* | |
object CodeGenerator { | |
import dialects.Scala213Source3 | |
private val emptyTree = "".parse[Source].get | |
def generateLenses(path: Path): String = { | |
val files = os.walk(os.Path(path)).toList.filterNot(os.isDir) | |
val lenses: List[Defn.Object] = | |
files.flatMap { file => | |
val input = Input.VirtualFile(path.toString, os.read(file)) | |
val tree = input.parse[Source].getOrElse(emptyTree) | |
def getCaseClasses(tree: Tree, parentObject: Option[Term.Ref]): List[(Type, List[(Name, Type)])] = | |
tree match { | |
case i: Source => i.stats.flatMap(stat => getCaseClasses(stat, parentObject)) | |
case i: Pkg => i.stats.flatMap(stat => getCaseClasses(stat, parentObject)) | |
case i: Defn.Class if i.mods.exists(_.syntax.contains("@lenses")) => | |
val className = Type.Name(i.name.value) | |
List( | |
( | |
parentObject.fold[Type](className)(parent => Type.Select(parent, className)), | |
i.ctor.paramClauses.toList.flatMap(_.values.flatMap(param => param.decltpe.map(param.name -> _))) | |
) | |
) | |
case i: Defn.Object => | |
val objectName = Term.Name(i.name.value) | |
i.templ.stats.flatMap(stat => | |
getCaseClasses(stat, Some(parentObject.fold[Term.Ref](objectName)(parent => Term.Select(parent, objectName)))) | |
) | |
case _ => Nil | |
} | |
val caseClasses: List[(Type, List[(Name, Type)])] = getCaseClasses(tree, None) | |
caseClasses.map { case (parentType, params) => | |
val paramLenses = params.map { case (paramName, paramType) => | |
val lensName = Term.Name(paramName.value) | |
q"val ${Pat.Var(lensName)}: Lens[$parentType, $paramType] = GenLens[$parentType](_.$lensName)" | |
} | |
val name = parentType.toString().split('.').mkString | |
q""" | |
object ${Term.Name(s"${name}Lens")} { | |
..$paramLenses | |
} | |
""" | |
} | |
} | |
val output = q""" | |
package com.example | |
// add needed imports here | |
import monocle.macros.GenLens | |
import monocle.Lens | |
..$lenses | |
""" | |
attachComment(output.syntax, path.toString) | |
} | |
def attachComment(syntax: String, source: String): String = | |
s"""// Generated by project/CodeGenerator.scala. | |
|// Source: $source | |
|// Do not edit! | |
| | |
|$syntax""".stripMargin | |
} |
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 characters
class lenses extends scala.annotation.StaticAnnotation |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment