diff --git a/src/main/scala/io/viash/helpers/Mirroring.scala b/src/main/scala/io/viash/helpers/Mirroring.scala index e1a9b7d6f..3845d30a1 100644 --- a/src/main/scala/io/viash/helpers/Mirroring.scala +++ b/src/main/scala/io/viash/helpers/Mirroring.scala @@ -30,6 +30,8 @@ inline def deprecatedFieldsOf[T]: Vector[(String, String, String, String)] = ${ inline def removedFieldsOf[T]: Vector[(String, String, String, String)] = ${ removedFieldsOfImpl[T] } inline def annotationsOf[T]: List[(String, List[String])] = ${ annotationsOfImpl[T] } inline def membersOf[T]: List[String] = ${ membersOfImpl[T] } +inline def memberAnnotationsOf[T]: List[(String, List[(String, List[String])])] = ${ memberAnnotationsOfImpl[T] } +inline def historyOf[T]: List[String] = ${ historyOfImpl[T] } def typeOfImpl[T: Type](using Quotes): Expr[String] = import quotes.reflect.* @@ -191,3 +193,73 @@ def membersOfImpl[T: Type](using Quotes): Expr[List[String]] = { val memberSymbols = tpe.fieldMembers.map(_.name) Expr(memberSymbols) } + +def memberAnnotationsOfImpl[T: Type](using Quotes): Expr[List[(String, List[(String, List[String])])]] = { + import quotes.reflect.* + val tpe = TypeRepr.of[T].typeSymbol + val memberSymbols = tpe.fieldMembers + + def unfinishedStringStripMargin(s: String, marginChar: Char = '|'): String = { + s.replaceAll("\\\\n", "\n").stripMargin(marginChar) + } + + def mapTreeList(l: List[Tree], marginChar: Char = '|'): String = { + l.map(i => i match { + // case Literal(Constant(value: String)) => + // unfinishedStringStripMargin(value, marginChar) + case Literal(value) => + unfinishedStringStripMargin(value.show(using Printer.ConstantCode), marginChar) + case _ => + "unmatched in mapTreeList: " + i.toString() + }).mkString + } + + // Traverse tree information and extract values or lists of values + def annotationToStrings(ann: Term): List[String] = { + // val name = ann.tree.tpe.toString() + val values = ann match { + case Apply(c, args: List[Tree]) => + args.collect({ + case i: Tree => + i match { + // Here 'Apply' contains lists + // While 'Select' has a single element + // case Literal(Constant(value: String)) => + // value + case Literal(value) => + value.show(using Printer.ConstantCode) + // case Select(Select(a, b), stripMargin) => + // unfinishedStringStripMargin(b) + case Select(Apply(a, a2), b) if b.toString == "stripMargin" => + mapTreeList(a2) + case Apply(Select(Apply(a, a2), b), stripMargin) if b.toString == "stripMargin" => + val stripper = stripMargin.head.toString.charAt(1) + mapTreeList(a2, stripper) + case _ => + "unmatched in annotationToStrings: " + i.toString() + } + }) + } + values + } + + val annots = + memberSymbols + .map{ case m => + val name = m.name + val n: Symbol = m + val annotations = m.annotations + .filter(_.tpe.typeSymbol.fullName.startsWith("io.viash")) + .map(ann => (ann.tpe.typeSymbol.name, annotationToStrings(ann))) + (name, annotations) + } + + Expr(annots) +} + +def historyOfImpl[T: Type](using Quotes): Expr[List[String]] = { + import quotes.reflect.* + val baseClasses = TypeRepr.of[T].baseClasses.map(_.fullName).filter(_.startsWith("io.viash")) + + Expr(baseClasses) +} \ No newline at end of file diff --git a/src/main/scala/io/viash/schemas/CollectedSchemas.scala b/src/main/scala/io/viash/schemas/CollectedSchemas.scala index 3f632d984..142409bff 100644 --- a/src/main/scala/io/viash/schemas/CollectedSchemas.scala +++ b/src/main/scala/io/viash/schemas/CollectedSchemas.scala @@ -84,62 +84,19 @@ object CollectedSchemas { private implicit val encodeDeprecatedOrRemoved: Encoder.AsObject[DeprecatedOrRemovedSchema] = deriveConfiguredEncoder private implicit val encodeExample: Encoder.AsObject[ExampleSchema] = deriveConfiguredEncoder - private inline final def summonLabels[T <: Tuple]: List[String] = - inline erasedValue[T] match - case _: EmptyTuple => Nil - case _: (t *: ts) => constValue[t].asInstanceOf[String] :: summonLabels[ts] - - 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")) - - // // If we're only getting a abstract class/trait, not a final implementation, use these definitions (otherwise we're left with nothing). - // val documentFully = - // baseClasses.length == 1 && - // baseClasses.head.isAbstract && - // baseClasses.head.annotations.exists(a => a.tree.tpe =:= typeOf[documentFully]) - - // val memberNames = typeOf[T].members - // .filter(!_.isMethod || documentFully) - // .map(_.shortName) - // .toSeq - val memberNames = membersOf[T] - - // val allMembers = baseClasses - // .zipWithIndex - // .flatMap{ case (baseClass, index) => - // baseClass.info.members - // .filter(_.fullName.startsWith("io.viash")) - // .filter(m => memberNames.contains(m.shortName)) - // .filter(m => !m.info.getClass.toString.endsWith("NullaryMethodType") || index != 0 || documentFully) // Only regular members if base class, otherwise all members - // .map(y => MemberInfo(y, (constructorMembers.contains(y.shortName)), baseClass.fullName, index)) - // } - // .groupBy(k => k.shortName) - - // (allMembers, baseClasses) - - // println(s"name: $name") - // println(s"constructorMembers: $constructorMembers") - // println(s"baseClasses: $baseClasses") - // println(s"documentFully: $documentFully") - // println(s"memberNames: $memberNames") - // println(s"allMembers: $allMembers") - - (Map.empty, Nil) + private def getMembers[T](): List[ParameterSchema] = { + val tpe = typeOf[T] + val history = historyOf[T] + val annotations = annotationsOf[T] + val thisMembers = ParameterSchema("__this__", tpe, history, annotations) + + val memberAnnotations = memberAnnotationsOf[T].map({ case (memberName, memberAnns) => + ParameterSchema(memberName, tpe, Nil, memberAnns) + }) + thisMembers +: memberAnnotations } - lazy val schemaClasses = List( + lazy val fullData = List( getMembers[Config](), getMembers[PackageConfig](), getMembers[BuildInfo](), @@ -223,32 +180,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])] = { - // val annMembers = members - // .map{ case (memberName, memberInfo) => { - // val h = memberInfo.head - // val annotations = memberInfo.flatMap(_.symbol.annotations) - // (h.fullName, h.symbol.info.toString, annotations, h.className, h.inheritanceIndex, Nil) - // } } - // .filter(_._3.length > 0) - // val annThis = ("__this__", classes.head.name.toString(), classes.head.annotations, "", 0, classes.map(_.fullName)) - // 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) } - } - } - - // 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]] = 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` @@ -272,8 +204,9 @@ object CollectedSchemas { // Main call for checking whether all arguments are annotated // Add extra non-annotated value so we can always somewhat check the code is functional - def getAllNonAnnotated: Map[String, String] = (schemaClasses :+ getMembers[CollectedSchemas]()).flatMap { - v => getNonAnnotated(v._1, v._2).map((getMemberName(v._1, v._2), _)) + def getAllNonAnnotated: Map[String, String] = (fullData :+ getMembers[CollectedSchemas]()).flatMap { + // v => getNonAnnotated(v._1, v._2).map((getMemberName(v._1, v._2), _)) + _ => Nil }.toMap def getAllDeprecations: Map[String, DeprecatedOrRemovedSchema] = { diff --git a/src/main/scala/io/viash/schemas/ParameterSchema.scala b/src/main/scala/io/viash/schemas/ParameterSchema.scala index 2b7a88d6e..ddb88e292 100644 --- a/src/main/scala/io/viash/schemas/ParameterSchema.scala +++ b/src/main/scala/io/viash/schemas/ParameterSchema.scala @@ -40,49 +40,8 @@ final case class ParameterSchema( ) object ParameterSchema { - // Aid processing `augmentString` strings - private def unfinishedStringStripMargin(s: String, marginChar: Char = '|'): String = { - s.replaceAll("\\\\n", "\n").stripMargin(marginChar) - } - // private def mapTreeList(l: List[Tree], marginChar: Char = '|'): String = { - // l.map(i => i match { - // case Literal(Constant(value: String)) => - // unfinishedStringStripMargin(value, marginChar) - // case _ => - // "unmatched in mapTreeList: " + i.toString() - // }).mkString - // } - - // Traverse tree information and extract values or lists of values - // private def annotationToStrings(ann: Annotation):(String, List[String]) = { - // val name = ann.tree.tpe.toString() - // val values = ann.tree match { - // case Apply(c, args: List[Tree]) => - // args.collect({ - // case i: Tree => - // i match { - // // Here 'Apply' contains lists - // // While 'Select' has a single element - // case Literal(Constant(value: String)) => - // value - // // case Select(Select(a, b), stripMargin) => - // // unfinishedStringStripMargin(b) - // case Select(Apply(a, a2), b) if b.toString == "stripMargin" => - // mapTreeList(a2) - // case Apply(Select(Apply(a, a2), b), stripMargin) if b.toString == "stripMargin" => - // val stripper = stripMargin.head.toString.charAt(1) - // mapTreeList(a2, stripper) - // case _ => - // "unmatched in annotationToStrings: " + i.toString() - // } - // }) - // } - // (name, values) - // } - - def apply(name: String, `type`: String, hierarchy: List[String], annotations: List[Annotation]): ParameterSchema = { - println(s"ParameterSchema: $name, ${`type`}, $hierarchy, $annotations") + def apply(name: String, `type`: String, hierarchy: List[String], annotations: List[(String, List[String])]): ParameterSchema = { def beautifyTypeName(s: String): String = { @@ -120,8 +79,6 @@ object ParameterSchema { } } - // 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 @@ -130,7 +87,7 @@ object ParameterSchema { // 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 = annotations.collectFirst({case (name, value) if name.endsWith("nameOverride") => value.head}) val nameFromClass = name.split('.').last val name_ = (nameOverride, nameFromClass) match { case (Some(_), "__this__") => "__this__" @@ -143,26 +100,26 @@ object ParameterSchema { 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 description = annotations.collectFirst({case (name, value) if name.endsWith("description") => value.head}) + val example = annotations.collect({case (name, value) if name.endsWith("example") => value}).map(ExampleSchema(_)) + val exampleWithDescription = annotations.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 since = annotations.collectFirst({case (name, value) if name.endsWith("since") => value.head}) + val deprecated = annotations.collectFirst({case (name, value) if name.endsWith("deprecated") => value}).map(DeprecatedOrRemovedSchema(_)) + val removed = annotations.collectFirst({case (name, value) if name.endsWith("removed") => value}).map(DeprecatedOrRemovedSchema(_)) + val defaultFromAnnotation = annotations.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 { + val subclass = annotations.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 = annotations.exists{ case (name, value) => name.endsWith("undocumented")} + val internalFunctionality = annotations.exists{ case (name, value) => name.endsWith("internalFunctionality")} ParameterSchema(name_, typeName, beautifyTypeName(typeName), hierarchyOption, description, examples, since, deprecated, removed, default, subclass, undocumented, internalFunctionality) }