Skip to content

Commit

Permalink
clean up logger, add colors
Browse files Browse the repository at this point in the history
  • Loading branch information
arainko committed Oct 15, 2023
1 parent 4c09c6d commit 8b66a5f
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 85 deletions.
6 changes: 4 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ lazy val ducktapeNext =
project
.in(file("ducktapeNext"))
.settings(
scalacOptions ++= List("-Xcheck-macros", "-no-indent", "-old-syntax", "-Xfatal-warnings", "-deprecation"),
scalacOptions ++= List("-Xcheck-macros", "-Xfatal-warnings", "-deprecation"),
)
.dependsOn(ducktape.jvm)
.dependsOn(ducktape.jvm, tooling)

lazy val tooling = project.in(file("tooling"))

lazy val docs =
project
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,30 +78,7 @@ object Configuration {
TypeApply(Select(Ident("Field2"), "allMatching"), a :: b :: destFieldTpe :: fieldSourceTpe :: Nil),
PathSelector(path) :: fieldSource :: Nil
) =>
val sourceExpr = fieldSource.asExpr
val result =
Structure.fromTypeRepr(destFieldTpe.tpe).as[Structure.Product]
.zip(Structure.fromTypeRepr(fieldSourceTpe.tpe).as[Structure.Product])
.map { (destStruct, fieldSourceStruct) =>
destStruct.fields.collect {
case (fieldName @ fieldSourceStruct.fields(source), dest) if source.tpe.repr <:< dest.tpe.repr =>
println(s"marched $fieldName")
val c = Configuration.At.Successful(
path.appended(Path.Segment.Field(source.tpe, fieldName)),
Target.Dest,
Configuration.FieldReplacement(sourceExpr, fieldName, source.tpe)
)
println(Debug.show(c))
c
}
.toList
}
.getOrElse(Configuration.At.Failed(path, Target.Dest, "Field.allMatching only works when targeting and supplying a product") :: Nil)

result match {
case Nil => Configuration.At.Failed(path, Target.Dest, "No matching fields found") :: Nil // TODO: Better error message
case configs => configs
}
parseAllMatching(fieldSource.asExpr, path, destFieldTpe.tpe, fieldSourceTpe.tpe)

case Apply(
TypeApply(Select(Ident("Case2"), "const"), a :: b :: sourceTpe :: constTpe :: Nil),
Expand All @@ -123,9 +100,47 @@ object Configuration {
Configuration.Const(value.asExpr, value.tpe.asType)
) :: Nil

case oopsie => report.errorAndAbort(oopsie.show(using Printer.TreeStructure))
case oopsie => report.errorAndAbort(oopsie.show(using Printer.TreeStructure), oopsie.pos)
}
.toList
}

private def parseAllMatching(using Quotes)(
sourceExpr: Expr[Any],
path: Path,
destFieldTpe: quotes.reflect.TypeRepr,
fieldSourceTpe: quotes.reflect.TypeRepr
) = {
val result =
Structure
.fromTypeRepr(destFieldTpe)
.as[Structure.Product | Structure.Function]
.zip(Structure.fromTypeRepr(fieldSourceTpe).as[Structure.Product])
.map { (destStruct, fieldSourceStruct) =>
val fields = destStruct match {
case p: Structure.Product => p.fields
case f: Structure.Function => f.args
}

fields.collect {
case (fieldName @ fieldSourceStruct.fields(source), dest) if source.tpe.repr <:< dest.tpe.repr =>
Configuration.At.Successful(
path.appended(Path.Segment.Field(source.tpe, fieldName)),
Target.Dest,
Configuration.FieldReplacement(sourceExpr, fieldName, source.tpe)
)
}.toList
}
.getOrElse(
Configuration.At
.Failed(path, Target.Dest, "Field.allMatching only works when targeting a product or a function and supplying a product") :: Nil
)

result match {
case Nil =>
Configuration.At.Failed(path, Target.Dest, "No matching fields found") :: Nil // TODO: Better error message
case configs => configs
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import io.github.arainko.ducktape.internal.macros.DebugMacros
import scala.annotation.nowarn
import scala.deriving.Mirror
import io.github.arainko.ducktape.Transformer.Debug
import scala.reflect.TypeTest
import scala.reflect.ClassTag

final case class Value(int: Int) extends AnyVal
final case class ValueGen[A](int: A) extends AnyVal
Expand Down Expand Up @@ -64,6 +66,12 @@ final case class ProdTest2(test: Test2)

val p = Person1(1, "asd", Nested1(1))

val test: Any = Nil

val cos = summon[TypeTest[Any, Int | String]]

println(cos.unapply(test))

/*
Use cases that I need to support:
- override a field for which a transformation exists
Expand Down Expand Up @@ -99,7 +107,6 @@ final case class ProdTest2(test: Test2)
}



def costam(int: Int, str: String): Int = ???

// val aaa: AppliedViaBuilder[Person1, Int, FunctionArguments{val int: Int; val str: String}] =
Expand Down
115 changes: 58 additions & 57 deletions ducktapeNext/src/main/scala/io/github/arainko/ducktape/Structure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import io.github.arainko.ducktape.Structure.Singleton
import io.github.arainko.ducktape.Structure.Ordinary
import io.github.arainko.ducktape.Structure.Lazy
import io.github.arainko.ducktape.internal.modules.*
import io.github.arainko.ducktape.internal.Debug
import io.github.arainko.ducktape.internal.*
import scala.collection.immutable.ListMap
import io.github.arainko.ducktape.function.FunctionMirror
import scala.reflect.TypeTest
Expand Down Expand Up @@ -60,62 +60,63 @@ object Structure {
import quotes.reflect.*
given Printer[TypeRepr] = Printer.TypeReprShortCode

Expr.summon[Mirror.Of[A]] match {
case None =>
summon[Type[A]].repr match {
case valueClassRepr if valueClassRepr <:< TypeRepr.of[AnyVal] && valueClassRepr.typeSymbol.flags.is(Flags.Case) =>
val param = valueClassRepr.typeSymbol.caseFields.head
val paramTpe = valueClassRepr.memberType(param)
Structure.ValueClass(summon[Type[A]], valueClassRepr.show, paramTpe.asType, param.name)
case other =>
Structure.Ordinary(summon[Type[A]], TypeRepr.of[A].show)
}
case Some(value) =>
value match {
case '{
type label <: String
$m: Mirror.Singleton {
type MirroredLabel = `label`
}
} =>
val value = materializeSingleton[A]
Structure.Singleton(summon[Type[A]], constantString[label], value.asExpr)
case '{
type label <: String
$m: Mirror.SingletonProxy {
type MirroredLabel = `label`
}
} =>
val value = materializeSingleton[A]
Structure.Singleton(summon[Type[A]], constantString[label], value.asExpr)
case '{
type label <: String
$m: Mirror.Product {
type MirroredLabel = `label`
type MirroredElemLabels = labels
type MirroredElemTypes = types
}
} =>
val structures =
tupleTypeElements(TypeRepr.of[types]).map(tpe => tpe.asType match { case '[tpe] => Lazy(() => Structure.of[tpe]) })
val names = constStringTuple(TypeRepr.of[labels])
Structure.Product(summon[Type[A]], constantString[label], names.zip(structures).toMap)
case '{
type label <: String
$m: Mirror.Sum {
type MirroredLabel = `label`
type MirroredElemLabels = labels
type MirroredElemTypes = types
}
} =>
val names = constStringTuple(TypeRepr.of[labels])
val structures =
tupleTypeElements(TypeRepr.of[types]).map(tpe => tpe.asType match { case '[tpe] => Lazy(() => Structure.of[tpe]) })

Structure.Coproduct(summon[Type[A]], constantString[label], names.zip(structures).toMap)

}
}
Logger.loggedInfo("Structure"):
Expr.summon[Mirror.Of[A]] match {
case None =>
summon[Type[A]].repr match {
case valueClassRepr if valueClassRepr <:< TypeRepr.of[AnyVal] && valueClassRepr.typeSymbol.flags.is(Flags.Case) =>
val param = valueClassRepr.typeSymbol.caseFields.head
val paramTpe = valueClassRepr.memberType(param)
Structure.ValueClass(summon[Type[A]], valueClassRepr.show, paramTpe.asType, param.name)
case other =>
Structure.Ordinary(summon[Type[A]], TypeRepr.of[A].show)
}
case Some(value) =>
value match {
case '{
type label <: String
$m: Mirror.Singleton {
type MirroredLabel = `label`
}
} =>
val value = materializeSingleton[A]
Structure.Singleton(summon[Type[A]], constantString[label], value.asExpr)
case '{
type label <: String
$m: Mirror.SingletonProxy {
type MirroredLabel = `label`
}
} =>
val value = materializeSingleton[A]
Structure.Singleton(summon[Type[A]], constantString[label], value.asExpr)
case '{
type label <: String
$m: Mirror.Product {
type MirroredLabel = `label`
type MirroredElemLabels = labels
type MirroredElemTypes = types
}
} =>
val structures =
tupleTypeElements(TypeRepr.of[types]).map(tpe => tpe.asType match { case '[tpe] => Lazy(() => Structure.of[tpe]) })
val names = constStringTuple(TypeRepr.of[labels])
Structure.Product(summon[Type[A]], constantString[label], names.zip(structures).toMap)
case '{
type label <: String
$m: Mirror.Sum {
type MirroredLabel = `label`
type MirroredElemLabels = labels
type MirroredElemTypes = types
}
} =>
val names = constStringTuple(TypeRepr.of[labels])
val structures =
tupleTypeElements(TypeRepr.of[types]).map(tpe => tpe.asType match { case '[tpe] => Lazy(() => Structure.of[tpe]) })

Structure.Coproduct(summon[Type[A]], constantString[label], names.zip(structures).toMap)

}
}
}

private def materializeSingleton[A: Type](using Quotes) = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package io.github.arainko.ducktape.internal

import scala.compiletime.*
import scala.quoted.*
import io.github.arainko.ducktape.Transformer
import io.github.arainko.tooling.FullName

private[ducktape] object Logger {

transparent inline given Level = Level.Info
given Output = Output.StdOut

enum Level {
case Debug, Info, Off
}

enum Output {
case StdOut, Report

final def print(msg: String)(using Quotes) =
this match {
case StdOut => println(msg)
case Report => quotes.reflect.report.info(msg)
}
}

private val infoTag = s"${Console.GREEN}[INFO]${Console.RESET}"
private val debugTag = s"${Console.GREEN}[DEBUG]${Console.RESET}"
private def blue(msg: String) = s"${Console.BLUE}$msg${Console.RESET}"

inline def loggedInfo[A](using Level, Output, FullName, Debug[A], Quotes)(
inline msg: String
)(value: A) = {
info(msg, value)
value
}

inline def info(inline msg: String)(using level: Level, output: Output, name: FullName, quotes: Quotes): Unit =
inline level match {
case Level.Debug => ()
case Level.Info => output.print(s"$infoTag $msg ${blue(s"[$name]")}")
case Level.Off => ()
}

inline def info[A](
inline msg: String,
value: A
)(using Level, Output, FullName, Debug[A], Quotes): Unit =
info(s"$msg: ${Debug.show(value)}")

inline def loggedDebug[A](using Level, Output, FullName, Debug[A], Quotes)(
inline msg: String
)(value: A) = {
debug(msg, value)
value
}

inline def debug(inline msg: String)(using level: Level, output: Output, name: FullName, quotes: Quotes): Unit =
inline level match {
case Level.Debug => output.print(s"$debugTag $msg ${blue(s"[$name]")}")
case Level.Info => output.print(s"$infoTag $msg ${blue(s"[$name]")}")
case Level.Off => ()
}

inline def debug[A](inline msg: String, value: A)(using level: Level, name: FullName, _debug: Debug[A], quotes: Quotes): Unit =
debug(s"$msg: ${Debug.show(value)}")
}
30 changes: 30 additions & 0 deletions tooling/src/main/scala/io/github/arainko/tooling/FullName.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.github.arainko.tooling

import scala.quoted.*

private[arainko] opaque type FullName <: String = String

private[arainko] object FullName {

inline given derived(using DummyImplicit): FullName = ${ ownerMacro }

private def ownerMacro(using Quotes) = {
import quotes.reflect.*

val pos = Position.ofMacroExpansion

val sourceFile = s"${pos.sourceFile.name}:${pos.startLine + 1}"

val rendered =
List
.unfold(Symbol.spliceOwner)(sym => Option.when(!sym.isNoSymbol)(sym -> sym.maybeOwner))
.collect {
case sym if !sym.flags.is(Flags.Synthetic) && !sym.flags.is(Flags.Package) && !sym.isLocalDummy => sym.name
}
.reverse
.mkString(".")
.replace("$", "")

'{ ${ Expr(s"$rendered @ $sourceFile") }: FullName }
}
}

0 comments on commit 8b66a5f

Please sign in to comment.