diff --git a/build.sbt b/build.sbt index aca98f76a..9564433c7 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ libraryDependencies ++= Seq( "dev.optics" %% "monocle-macro" % "3.1.0" ) -val circeVersion = "0.14.9" +val circeVersion = "0.14.10" libraryDependencies ++= Seq( "io.circe" %% "circe-core", diff --git a/src/main/scala/io/viash/config/package.scala b/src/main/scala/io/viash/config/package.scala index e89736506..5c3c4302d 100644 --- a/src/main/scala/io/viash/config/package.scala +++ b/src/main/scala/io/viash/config/package.scala @@ -272,7 +272,7 @@ package object config { "Could not convert json to Config." ) - implicit val encodeBuildInfo: Encoder[BuildInfo] = deriveConfiguredEncoder + implicit val encodeBuildInfo: Encoder.AsObject[BuildInfo] = deriveConfiguredEncoder implicit val decodeBuildInfo: Decoder[BuildInfo] = deriveConfiguredDecoderFullChecks // encoder and decoder for Author diff --git a/src/main/scala/io/viash/helpers/Mirroring.scala b/src/main/scala/io/viash/helpers/Mirroring.scala index 7131224e0..fdd0d68ee 100644 --- a/src/main/scala/io/viash/helpers/Mirroring.scala +++ b/src/main/scala/io/viash/helpers/Mirroring.scala @@ -28,6 +28,7 @@ inline def fieldsOf[T]: List[String] = ${ fieldsOfImpl[T] } inline def internalFunctionalityFieldsOf[T]: List[String] = ${ internalFunctionalityFieldsOfImpl[T] } inline def deprecatedFieldsOf[T]: Vector[(String, String, String, String)] = ${ deprecatedFieldsOfImpl[T] } inline def removedFieldsOf[T]: Vector[(String, String, String, String)] = ${ removedFieldsOfImpl[T] } +inline def membersOf[T]: List[String] = ${ membersOfImpl[T] } def typeOfImpl[T: Type](using Quotes): Expr[String] = import quotes.reflect.* @@ -125,3 +126,10 @@ def removedFieldsOfImpl[T: Type](using Quotes): Expr[Vector[(String, String, Str '{ ($fieldNameExpr, $annotExpr.message, $annotExpr.deprecatedSince, $annotExpr.since) } val seq: Expr[Seq[(String, String, String, String)]] = Expr.ofSeq(tuples) '{ $seq.toVector } + +def membersOfImpl[T: Type](using Quotes): Expr[List[String]] = { + import quotes.reflect.* + val tpe = TypeRepr.of[T].typeSymbol + val memberSymbols = tpe.fieldMembers.map(_.name) + Expr(memberSymbols) +} diff --git a/src/main/scala/io/viash/schemas/CollectedSchemas.scala b/src/main/scala/io/viash/schemas/CollectedSchemas.scala index 6a6001df0..3f632d984 100644 --- a/src/main/scala/io/viash/schemas/CollectedSchemas.scala +++ b/src/main/scala/io/viash/schemas/CollectedSchemas.scala @@ -44,6 +44,7 @@ import io.viash.config.Links import io.viash.config.References import scala.deriving.Mirror import scala.compiletime.{ codeOf, constValue, erasedValue, error, summonFrom, summonInline } +import scala.annotation.Annotation final case class CollectedSchemas ( config: Map[String, List[ParameterSchema]], @@ -88,15 +89,17 @@ object CollectedSchemas { case _: EmptyTuple => Nil case _: (t *: ts) => constValue[t].asInstanceOf[String] :: summonLabels[ts] - private def getMembers[T /*TypeTag*/]/*(using mirror: Mirror.Of[T])*/(): (Map[String,List[MemberInfo]], List[Symbol]) = { + private inline def getMembers[T /*TypeTag*/]/*(using mirror: Mirror.Of[T])*/(): (Map[String,List[MemberInfo]], List[Symbol]) = { // val name: String = constValue[mirror.MirroredLabel] + val name: String = typeOf[T] // Get all members and filter for constructors, first one should be the best (most complete) one // Traits don't have constructors // Get all parameters and store their short name // val constructorMembers = typeOf[T].members.filter(_.isConstructor).headOption.map(_.asMethod.paramLists.head.map(_.shortName)).getOrElse(List.empty[String]) // val constructorMembers = summonLabels[mirror.MirroredElemLabels] + val constructorMembers = fieldsOf[T] // val baseClasses = typeOf[T].baseClasses // .filter(_.fullName.startsWith("io.viash")) @@ -111,6 +114,7 @@ object CollectedSchemas { // .filter(!_.isMethod || documentFully) // .map(_.shortName) // .toSeq + val memberNames = membersOf[T] // val allMembers = baseClasses // .zipWithIndex @@ -219,7 +223,7 @@ object CollectedSchemas { .replaceAll("""(\w*)\[[\w\.]*?(\w*),[\w\.]*?(\w*)\]""", "$1[$2,$3]") } - // private def annotationsOf(members: (Map[String,List[MemberInfo]]), classes: List[Symbol]): List[(String, String, List[String], List[Annotation])] = { + private def annotationsOf(members: (Map[String,List[MemberInfo]]), classes: List[Symbol]): List[(String, String, List[String], List[Annotation])] = { // val annMembers = members // .map{ case (memberName, memberInfo) => { // val h = memberInfo.head @@ -231,19 +235,20 @@ object CollectedSchemas { // val allAnnotations = annThis :: annMembers.toList // allAnnotations // .map({case (name, tpe, annotations, d, e, hierarchy) => (name, trimTypeName(tpe), hierarchy, annotations)}) // TODO this ignores where the annotation was defined, ie. top level class or super class - // } + Nil + } - // private val getSchema: ((Map[String,List[MemberInfo]], List[Symbol])) => List[ParameterSchema] = (t: (Map[String,List[MemberInfo]], List[Symbol])) => t match { - // case (members, classes) => { - // annotationsOf(members, classes).map{ case (name, tpe, hierarchy, annotations) => ParameterSchema(name, tpe, hierarchy, annotations) } - // } - // } + private val getSchema: ((Map[String,List[MemberInfo]], List[Symbol])) => List[ParameterSchema] = (t: (Map[String,List[MemberInfo]], List[Symbol])) => t match { + case (members, classes) => { + annotationsOf(members, classes).map{ case (name, tpe, hierarchy, annotations) => ParameterSchema(name, tpe, hierarchy, annotations) } + } + } // get all parameters for a given type, including parent class annotations // def getParameters[T: TypeTag](): List[ParameterSchema] = getSchema(getMembers[T]()) // Main call for documentation output - lazy val fullData: List[List[ParameterSchema]] = Nil//schemaClasses.map{ v => getSchema(v)} + lazy val fullData: List[List[ParameterSchema]] = schemaClasses.map{ v => getSchema(v)} lazy val data: List[List[ParameterSchema]] = fullData.map(_.filter(p => !p.hasUndocumented && !p.hasInternalFunctionality)) def getKeyFromParamList(data: List[ParameterSchema]): String = data.find(p => p.name == "__this__").get.`type` diff --git a/src/main/scala/io/viash/schemas/ParameterSchema.scala b/src/main/scala/io/viash/schemas/ParameterSchema.scala index f0585d43b..1804ec1ba 100644 --- a/src/main/scala/io/viash/schemas/ParameterSchema.scala +++ b/src/main/scala/io/viash/schemas/ParameterSchema.scala @@ -19,6 +19,7 @@ package io.viash.schemas // import scala.reflect.runtime.universe._ import io.viash.schemas.internalFunctionality +import scala.annotation.Annotation final case class ParameterSchema( name: String, @@ -80,89 +81,92 @@ object ParameterSchema { // (name, values) // } - // def apply(name: String, `type`: String, hierarchy: List[String], annotations: List[Annotation]): ParameterSchema = { + def apply(name: String, `type`: String, hierarchy: List[String], annotations: List[Annotation]): ParameterSchema = { + println(s"ParameterSchema: $name, ${`type`}, $hierarchy, $annotations") - // def beautifyTypeName(s: String): String = { + def beautifyTypeName(s: String): String = { - // // "tpe[a]" -> "(\w*)\[(\w*)\]" - // def regexify(s: String) = s.replace("[", "\\[").replace("]", "\\]").replaceAll("\\w+", "(\\\\w+)").r + // "tpe[a]" -> "(\w*)\[(\w*)\]" + def regexify(s: String) = s.replace("[", "\\[").replace("]", "\\]").replaceAll("\\w+", "(\\\\w+)").r - // val regex0 = regexify("tpe") - // val regex1 = regexify("tpe[a]") - // val regex2 = regexify("tpe[a,b]") - // val regexNested1 = regexify("tpe[a[b,c]]") - // val regexNested2 = regexify("tpe[a[b,c[d,e]]]") - // val regexNested3 = regexify("tpe[a,b[c,d]]") - // val regexNested4 = regexify("tpe[a[b[c,d],e]]") - // val regexNested5 = regexify("tpe[a[b,c[d]]]") - // val regexNested6 = regexify("tpe[a[b,c],d]") - // val regexNested7 = regexify("tpe[a,b[c]]") + val regex0 = regexify("tpe") + val regex1 = regexify("tpe[a]") + val regex2 = regexify("tpe[a,b]") + val regexNested1 = regexify("tpe[a[b,c]]") + val regexNested2 = regexify("tpe[a[b,c[d,e]]]") + val regexNested3 = regexify("tpe[a,b[c,d]]") + val regexNested4 = regexify("tpe[a[b[c,d],e]]") + val regexNested5 = regexify("tpe[a[b,c[d]]]") + val regexNested6 = regexify("tpe[a[b,c],d]") + val regexNested7 = regexify("tpe[a,b[c]]") - // def map(a: String, b: String): String = s"Map of $a to $b" - // def either(a: String, b: String): String = s"""Either $a or $b""" + def map(a: String, b: String): String = s"Map of $a to $b" + def either(a: String, b: String): String = s"""Either $a or $b""" - // s match { - // case regex0(tpe) => s"$tpe" - // case regex1(tpe, subtpe) => s"$tpe of $subtpe" - // case regex2("Map", subtpe1, subtpe2) => map(subtpe1,subtpe2) - // case regex2("ListMap", subtpe1, subtpe2) => map(subtpe1, subtpe2) - // case regex2("Either", subtpe1, subtpe2) => either(subtpe1, subtpe2) - // case regexNested1(tpe, a, b, c) => s"$tpe of ${beautifyTypeName(s"$a[$b,$c]")}" - // case regexNested2(tpe, a, b ,c ,d, e) => s"$tpe of ${beautifyTypeName(s"$a[$b,$c[$d,$e]]")}" - // case regexNested3("Either", a, b, c, d) => either(beautifyTypeName(a), beautifyTypeName(s"$b[$c,$d]")) - // case regexNested4(tpe, a, b, c, d, e) => s"$tpe of ${beautifyTypeName(s"$a[$b[$c,$d],$e]")}" - // case regexNested5(tpe, a, b, c, d) => s"$tpe of ${beautifyTypeName(s"$a[$b,$c[$d]]")}" - // case regexNested6("Either", a, b, c, d) => either(beautifyTypeName(s"$a[$b,$c]"), beautifyTypeName(d)) - // case regexNested7("Either", a, b, c) => either(beautifyTypeName(a), beautifyTypeName(s"$b[$c]")) - // case _ => s - // } - // } + s match { + case regex0(tpe) => s"$tpe" + case regex1(tpe, subtpe) => s"$tpe of $subtpe" + case regex2("Map", subtpe1, subtpe2) => map(subtpe1,subtpe2) + case regex2("ListMap", subtpe1, subtpe2) => map(subtpe1, subtpe2) + case regex2("Either", subtpe1, subtpe2) => either(subtpe1, subtpe2) + case regexNested1(tpe, a, b, c) => s"$tpe of ${beautifyTypeName(s"$a[$b,$c]")}" + case regexNested2(tpe, a, b ,c ,d, e) => s"$tpe of ${beautifyTypeName(s"$a[$b,$c[$d,$e]]")}" + case regexNested3("Either", a, b, c, d) => either(beautifyTypeName(a), beautifyTypeName(s"$b[$c,$d]")) + case regexNested4(tpe, a, b, c, d, e) => s"$tpe of ${beautifyTypeName(s"$a[$b[$c,$d],$e]")}" + case regexNested5(tpe, a, b, c, d) => s"$tpe of ${beautifyTypeName(s"$a[$b,$c[$d]]")}" + case regexNested6("Either", a, b, c, d) => either(beautifyTypeName(s"$a[$b,$c]"), beautifyTypeName(d)) + case regexNested7("Either", a, b, c) => either(beautifyTypeName(a), beautifyTypeName(s"$b[$c]")) + case _ => s + } + } - // val annStrings = annotations.map(annotationToStrings(_)) - // val hierarchyOption = hierarchy match { - // case l if l.length > 0 => Some(l) - // case _ => None - // } + // val annStrings = annotations.map(annotationToStrings(_)) + val annStrings = List[(String, List[String])]() // TODO + val hierarchyOption = hierarchy match { + case l if l.length > 0 => Some(l) + case _ => None + } - // // name is e.g. "io.viash.config.Config.name", only keep "name" - // // name can also be "__this__" - // // Use the name defined from the class, *unless* the 'nameOverride' annotation is set. Then use the override, unless the name is '__this__'. - // val nameOverride = annStrings.collectFirst({case (name, value) if name.endsWith("nameOverride") => value.head}) - // val nameFromClass = name.split('.').last - // val name_ = (nameOverride, nameFromClass) match { - // case (Some(_), "__this__") => "__this__" - // case (Some(ann), _) => ann - // case (None, name) => name - // } + // name is e.g. "io.viash.config.Config.name", only keep "name" + // name can also be "__this__" + // Use the name defined from the class, *unless* the 'nameOverride' annotation is set. Then use the override, unless the name is '__this__'. + // val nameOverride = annStrings.collectFirst({case (name, value) if name.endsWith("nameOverride") => value.head}) + val nameOverride = Option.empty // TODO + val nameFromClass = name.split('.').last + val name_ = (nameOverride, nameFromClass) match { + case (Some(_), "__this__") => "__this__" + case (Some(ann), _) => ann + case (None, name) => name + } - // val typeName = (`type`, nameOverride, nameFromClass) match { - // case (_, Some(newTypeName), "__this__") => newTypeName - // case (typeName, _, _) => typeName - // } + val typeName = (`type`, nameOverride, nameFromClass) match { + case (_, Some(newTypeName), "__this__") => newTypeName + case (typeName, _, _) => typeName + } - // val description = annStrings.collectFirst({case (name, value) if name.endsWith("description") => value.head}) - // val example = annStrings.collect({case (name, value) if name.endsWith("example") => value}).map(ExampleSchema(_)) - // val exampleWithDescription = annStrings.collect({case (name, value) if name.endsWith("exampleWithDescription") => value}).map(ExampleSchema(_)) - // val examples = example ::: exampleWithDescription match { - // case l if l.length > 0 => Some(l) - // case _ => None - // } - // val since = annStrings.collectFirst({case (name, value) if name.endsWith("since") => value.head}) - // val deprecated = annStrings.collectFirst({case (name, value) if name.endsWith("deprecated") => value}).map(DeprecatedOrRemovedSchema(_)) - // val removed = annStrings.collectFirst({case (name, value) if name.endsWith("removed") => value}).map(DeprecatedOrRemovedSchema(_)) - // val defaultFromAnnotation = annStrings.collectFirst({case (name, value) if name.endsWith("default") => value.head}) - // val defaultFromType = Option.when(typeName.startsWith("Option["))("Empty") - // val default = defaultFromAnnotation orElse defaultFromType - // val subclass = annStrings.collect{ case (name, value) if name.endsWith("subclass") => value.head } match { - // case l if l.nonEmpty => Some(l) - // case _ => None - // } + val description = annStrings.collectFirst({case (name, value) if name.endsWith("description") => value.head}) + val example = annStrings.collect({case (name, value) if name.endsWith("example") => value}).map(ExampleSchema(_)) + val exampleWithDescription = annStrings.collect({case (name, value) if name.endsWith("exampleWithDescription") => value}).map(ExampleSchema(_)) + val examples = example ::: exampleWithDescription match { + case l if l.length > 0 => Some(l) + case _ => None + } + val since = annStrings.collectFirst({case (name, value) if name.endsWith("since") => value.head}) + val deprecated = annStrings.collectFirst({case (name, value) if name.endsWith("deprecated") => value}).map(DeprecatedOrRemovedSchema(_)) + val removed = annStrings.collectFirst({case (name, value) if name.endsWith("removed") => value}).map(DeprecatedOrRemovedSchema(_)) + val defaultFromAnnotation = annStrings.collectFirst({case (name, value) if name.endsWith("default") => value.head}) + val defaultFromType = Option.when(typeName.startsWith("Option["))("Empty") + val default = defaultFromAnnotation orElse defaultFromType + val subclass = annStrings.collect{ case (name, value) if name.endsWith("subclass") => value.head } match { + case l if l.nonEmpty => Some(l) + case _ => None + } - // val undocumented = annStrings.exists{ case (name, value) => name.endsWith("undocumented")} - // val internalFunctionality = annStrings.exists{ case (name, value) => name.endsWith("internalFunctionality")} + val undocumented = annStrings.exists{ case (name, value) => name.endsWith("undocumented")} + val internalFunctionality = annStrings.exists{ case (name, value) => name.endsWith("internalFunctionality")} - // ParameterSchema(name_, typeName, beautifyTypeName(typeName), hierarchyOption, description, examples, since, deprecated, removed, default, subclass, undocumented, internalFunctionality) - // } + ParameterSchema(name_, typeName, beautifyTypeName(typeName), hierarchyOption, description, examples, since, deprecated, removed, default, subclass, undocumented, internalFunctionality) + } } final case class DeprecatedOrRemovedSchema(