Skip to content

Commit

Permalink
Format
Browse files Browse the repository at this point in the history
  • Loading branch information
travisbrown committed Jun 21, 2021
1 parent 9101aa7 commit f40935e
Show file tree
Hide file tree
Showing 17 changed files with 957 additions and 802 deletions.
12 changes: 5 additions & 7 deletions argus/src/main/scala-2.13/argus/macros/ParamsASTHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import scala.reflect.api.Universe

private[macros] object ParamsASTHelper {

def paramsToMap[U <: Universe](u: U)(
nameAndDefaults: List[(String, Any)],
params: List[u.Tree]): Map[String, Any] = {
def paramsToMap[U <: Universe](u: U)(nameAndDefaults: List[(String, Any)], params: List[u.Tree]): Map[String, Any] = {
import u._

def toValue(param: Tree): Any = param match {
Expand All @@ -26,12 +24,12 @@ private[macros] object ParamsASTHelper {
})

require(positional.length <= nameAndDefaults.length,
"More position arguments than specified in: " + nameAndDefaults)
"More position arguments than specified in: " + nameAndDefaults
)
val posValues = nameAndDefaults
.zip(positional)
.map {
case ((name, default), param) =>
(name, toValue(param))
.map { case ((name, default), param) =>
(name, toValue(param))
}
.toMap

Expand Down
16 changes: 8 additions & 8 deletions argus/src/main/scala/argus/json/JsonDiff.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import io.circe.Json
import io.circe.Printer

/**
* @author Aish Fenton.
*/
* @author Aish Fenton.
*/
object JsonDiff {

def diff(j1: Json, j2: Json) = {
Expand All @@ -16,23 +16,23 @@ object JsonDiff {
case (Some(o1), Some(o2)) => {
val o1m = o1.toMap.filter(removeNulls)
val o2m = o2.toMap.filter(removeNulls)
val sharedKeys = o1m.keySet intersect o2m.keySet
val sharedKeys = o1m.keySet.intersect(o2m.keySet)

// First record any missing fields
val diffKeys =
(o1m.keySet diff sharedKeys).map((_, "missing")) ++
(o2m.keySet diff sharedKeys).map(("missing", _))
o1m.keySet.diff(sharedKeys).map((_, "missing")) ++
o2m.keySet.diff(sharedKeys).map(("missing", _))

// Now recurse on each field
diffKeys.toList ++ sharedKeys.foldLeft(List[(String, String)]()) {
case(accum, k) => accum ++ diffR(o1(k).get, o2(k).get)
diffKeys.toList ++ sharedKeys.foldLeft(List[(String, String)]()) { case (accum, k) =>
accum ++ diffR(o1(k).get, o2(k).get)
}
}

case _ => {
(j1.asArray, j2.asArray) match {
case (Some(a1), Some(a2)) => {
a1.zip(a2).flatMap { case(jj1, jj2) => diffR(jj1,jj2) }.toList
a1.zip(a2).flatMap { case (jj1, jj2) => diffR(jj1, jj2) }.toList
}

// Everything else
Expand Down
150 changes: 77 additions & 73 deletions argus/src/main/scala/argus/macros/ASTHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,52 @@ import scala.language.experimental.macros
import scala.reflect.api.Universe

/**
* Common utils for AST munging. Also includes a few helps for constructing type names/paths from json constructs.
*/
* Common utils for AST munging. Also includes a few helps for constructing type names/paths from json constructs.
*/
class ASTHelpers[U <: Universe](val u: U) {
import u._

/**
* Returns true if the given contains the given annotation
*/
* Returns true if the given contains the given annotation
*/
def hasAnnotation(mods: Modifiers, annotation: String) = mods.annotations.exists {
_.exists(_.equalsStructure(q"new ${TypeName(annotation)}()"))
}

/**
* Returns the intersection set between two lists of trees (based on the tree's being equal in structure)
*/
* Returns the intersection set between two lists of trees (based on the tree's being equal in structure)
*/
def treesIntersect(l1: List[Tree], l2: List[Tree]) = l1.filter(i1 => l2.exists(i2 => showRaw(i1) == showRaw(i2)))

def uncapitialize(s: String) = s.head.toLower + s.tail

/**
* For a given type (optional including it's path) returns it's companion object
*/
* For a given type (optional including it's path) returns it's companion object
*/
def companionForType(typ: Tree) = mkSelectPath(typeSelectPathToList(typ))

/**
* For a given companion object (optional including it's path) returns it's given type
*/
* For a given companion object (optional including it's path) returns it's given type
*/
def typeForCompanion(typ: Tree) = mkTypeSelectPath(selectPathToList(typ))

/**
* Collects and returns case classes that extend the given type.
* @return A list of tuples. Each tuple contains the path, and class def
*/
* Collects and returns case classes that extend the given type.
* @return A list of tuples. Each tuple contains the path, and class def
*/
def collectExtendsType(path: List[String], typ: Tree, defs: List[Tree]): List[(List[String], Tree)] = defs.collect {
case (defDef@q"case object $name extends $sTyp { ..$_ }") if sTyp.equalsStructure(typ) => (path, defDef) :: Nil
case (defDef@q"case class $name(..$params) extends $sTyp") if sTyp.equalsStructure(typ) => (path, defDef) :: Nil
case (q"object $name { ..$innerDefs }") => collectExtendsType(path :+ name.toString, typ, innerDefs)
case (defDef @ q"case object $name extends $sTyp { ..$_ }") if sTyp.equalsStructure(typ) => (path, defDef) :: Nil
case (defDef @ q"case class $name(..$params) extends $sTyp") if sTyp.equalsStructure(typ) => (path, defDef) :: Nil
case (q"object $name { ..$innerDefs }") => collectExtendsType(path :+ name.toString, typ, innerDefs)
}.flatten

/**
* Returns a string from a given type (with path) that somewhat uniquely identifies this type. Can be useful for
* producing variable names.
* For example: a.b.C => ABC
* @param typ The type to produce a term from.
* @param usePath Whether to use a type's path or not. If not then only C of a.b.C is used. Defaults to true.
*/
* Returns a string from a given type (with path) that somewhat uniquely identifies this type. Can be useful for
* producing variable names.
* For example: a.b.C => ABC
* @param typ The type to produce a term from.
* @param usePath Whether to use a type's path or not. If not then only C of a.b.C is used. Defaults to true.
*/
def nameFromType(typ: Tree, usePath: Boolean = true) = {
val full = typeSelectPathToList(typ)
if (usePath) full.map(_.capitalize).mkString else full.last.capitalize
Expand All @@ -59,97 +59,101 @@ class ASTHelpers[U <: Universe](val u: U) {
val RefNameExtractor = """[^/]+$""".r

/**
* Best attempt at producing a name from a Json blob. This is mostly used for enum, where the enum's can be any
* arbitrary Json structure. Mostly they are plain strings, in which case this is a no-op, but in the case where
* it's more complex then the property names from json structure are used to build a name. If the json object
* is a number then decimial points are stripped, and "n" is prefixed (i.e. 3.14 => n314).
*/
* Best attempt at producing a name from a Json blob. This is mostly used for enum, where the enum's can be any
* arbitrary Json structure. Mostly they are plain strings, in which case this is a no-op, but in the case where
* it's more complex then the property names from json structure are used to build a name. If the json object
* is a number then decimial points are stripped, and "n" is prefixed (i.e. 3.14 => n314).
*/
def nameFromJson(json: String, splitOnUnderscore: Boolean = false): String = {
val res = json
.split(if (splitOnUnderscore) "[^A-Za-z0-9]+" else "[^A-Za-z0-9_]+")
.filterNot(_.isEmpty)
.map(_.capitalize)
.mkString

if (res.head.isDigit) "n" + res else res
if (res.head.isDigit) "n" + res else res
}

/**
* Extracts a package path from a json-schema $ref. Notes:
* - # is replaced by whatever we call the root schema
* - Definitions are defined at the same level as their containing object, i.e. for #/properties/a/definitions/Bob
* Bob is defined alongside A, not within it. This can be a bit confusing.
*
* @param rootName The name of the root class. Which is used to replace #/ references
* @param ref The $ref element to path-erize.
*/
* Extracts a package path from a json-schema $ref. Notes:
* - # is replaced by whatever we call the root schema
* - Definitions are defined at the same level as their containing object, i.e. for #/properties/a/definitions/Bob
* Bob is defined alongside A, not within it. This can be a bit confusing.
*
* @param rootName The name of the root class. Which is used to replace #/ references
* @param ref The $ref element to path-erize.
*/
def extractPathFromRef(rootName: Option[String], ref: String): List[String] = {
require(ref.startsWith("#/"), "references need to be absolute: " + ref)

// We make every instance of "/x/properties" into a path references.
val defPath = RefPathExtractor.findAllMatchIn(ref).map(_.group("name").capitalize).map {
case "#" => rootName.getOrElse(throw new Exception("Trying to resolve $ref, but no path:" + ref))
case e: String => e
}.toList
val defPath = RefPathExtractor
.findAllMatchIn(ref)
.map(_.group("name").capitalize)
.map {
case "#" => rootName.getOrElse(throw new Exception("Trying to resolve $ref, but no path:" + ref))
case e: String => e
}
.toList

val name = RefNameExtractor.findFirstIn(ref).get
defPath :+ name.capitalize
}

/**
* Wrap the given type in an Option type
*/
* Wrap the given type in an Option type
*/
def inOption(inner: Tree) = { tq"Option[$inner]" }

/**
* Wrap the given type in an List type
*/
* Wrap the given type in an List type
*/
def inList(inner: Tree) = { tq"List[$inner]" }

/**
* Quasiquote doesn't support building trees of selects (i.e. obj1.obj2.MyObj), so we do it here manually.
*
* @param path List of strings representing the paths.
* @return e.g. obj1.obj2.myObj becomes: Select(Select(Ident(TermName("obj1")), TermName("obj2")), TermName("myObj"))
*/
* Quasiquote doesn't support building trees of selects (i.e. obj1.obj2.MyObj), so we do it here manually.
*
* @param path List of strings representing the paths.
* @return e.g. obj1.obj2.myObj becomes: Select(Select(Ident(TermName("obj1")), TermName("obj2")), TermName("myObj"))
*/
def mkSelectPath(path: List[String]) = {
def mkPathRec(path: List[String]): Tree = path match {
case "_root_" :: Nil => Ident(termNames.ROOTPKG)
case a :: Nil => Ident(TermName(a))
case a :: xs => Select(mkPathRec(xs), TermName(a))
case Nil => throw new Exception("Path can't be empty")
case a :: Nil => Ident(TermName(a))
case a :: xs => Select(mkPathRec(xs), TermName(a))
case Nil => throw new Exception("Path can't be empty")
}

mkPathRec(path.reverse)
}

/**
* Builds on mkSelectPath, but treats last item is a Type rather than a TermName
*/
* Builds on mkSelectPath, but treats last item is a Type rather than a TermName
*/
def mkTypeSelectPath(path: List[String]): Tree = path match {
case a :: Nil => Ident(TypeName(a))
case a :: xs => Select(mkSelectPath(path.dropRight(1)), TypeName(path.last))
case Nil => throw new Exception("Path can't be empty")
case a :: xs => Select(mkSelectPath(path.dropRight(1)), TypeName(path.last))
case Nil => throw new Exception("Path can't be empty")
}

/**
* Extract the select path as a List[String]
*/
* Extract the select path as a List[String]
*/
def selectPathToList(tree: Tree): List[String] = {
def selectPathToListRec(tree: Tree): List[String] = tree match {
case Select(s: Tree, tn: TermName) => tn.toString :: selectPathToListRec(s)
case Ident(tn: TermName) => tn.toString :: Nil
case Ident(tn: TermName) => tn.toString :: Nil
}
selectPathToListRec(tree).reverse
}

/**
* Extract the select path (ending with a TypeName) as a List[String]. Types with type parameters
* are concatenated to form a single string. For example:
* a.b.MyObj[A,B[C]] becomes List(a,b,MyObjABC)
*/
* Extract the select path (ending with a TypeName) as a List[String]. Types with type parameters
* are concatenated to form a single string. For example:
* a.b.MyObj[A,B[C]] becomes List(a,b,MyObjABC)
*/
def typeSelectPathToList(typ: Tree): List[String] = typ match {
case Ident(TypeName(tn)) => tn.toString :: Nil
case Ident(TypeName(tn)) => tn.toString :: Nil
case Select(s: Tree, tn: TypeName) => selectPathToList(s) :+ tn.toString
case AppliedTypeTree(t: Tree, l: List[Tree]) => {
val path = typeSelectPathToList(t)
Expand All @@ -158,15 +162,15 @@ class ASTHelpers[U <: Universe](val u: U) {
}

/**
* Extracts argument values from a List[Tree] as is obtained from a quasiquote such as q"myFunc(..$params))".
* Supports named arguments, and default values.
* @param nameAndDefaults A list of tuples containing each argument's name and default value. This must be the complete
* list of supported arguments.
* @param params A list of trees to as extracted by ..$params. If arguments are constants or option wrapped constants
* then their reified values are returned (i.e. Some(1)), if not then the AST chunk is returned.
*
* @return A map of argument names and their extracted values (or their default value if not extracted)
*/
* Extracts argument values from a List[Tree] as is obtained from a quasiquote such as q"myFunc(..$params))".
* Supports named arguments, and default values.
* @param nameAndDefaults A list of tuples containing each argument's name and default value. This must be the complete
* list of supported arguments.
* @param params A list of trees to as extracted by ..$params. If arguments are constants or option wrapped constants
* then their reified values are returned (i.e. Some(1)), if not then the AST chunk is returned.
*
* @return A map of argument names and their extracted values (or their default value if not extracted)
*/
def paramsToMap(nameAndDefaults: List[(String, Any)], params: List[Tree]): Map[String, Any] =
ParamsASTHelper.paramsToMap(u)(nameAndDefaults, params)
}
16 changes: 8 additions & 8 deletions argus/src/main/scala/argus/macros/CirceCodecBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ package io.circe.argus.macros
import scala.reflect.api.Universe

/**
* @author Aish Fenton.
*/
* @author Aish Fenton.
*/
class CirceCodecBuilder[U <: Universe](val u: U) extends CodecBuilder {

import u._
import helpers._

val imports =
q"import cats.syntax.either._" ::
q"import io.circe._" ::
q"import io.circe.syntax._" ::
Nil
q"import io.circe._" ::
q"import io.circe.syntax._" ::
Nil

def inEncoder(typ: Tree) = tq"Encoder[$typ]"

Expand Down Expand Up @@ -62,7 +62,7 @@ class CirceCodecBuilder[U <: Universe](val u: U) extends CodecBuilder {
"""

def mkUnionEncoder(typ: Tree, subTypes: List[(Tree, Tree)]): Tree = {
val caseDefs = subTypes.map { case(rawType, unionType) =>
val caseDefs = subTypes.map { case (rawType, unionType) =>
cq"ut: $unionType => ut.x.asJson"
}

Expand All @@ -75,7 +75,7 @@ class CirceCodecBuilder[U <: Universe](val u: U) extends CodecBuilder {
def mkUnionDecoder(typ: Tree, subTypes: List[(Tree, Tree)]): Tree = {
val (rt, ut) = subTypes.head
val asDefs: Tree = subTypes.tail.foldLeft(q"c.as[$rt].map((x) => ${companionForType(ut)}(x))") {
case (s:Tree, (rt:Tree, ut: Tree)) => q"$s.orElse(c.as[$rt].map((x) => ${companionForType(ut)}(x)))"
case (s: Tree, (rt: Tree, ut: Tree)) => q"$s.orElse(c.as[$rt].map((x) => ${companionForType(ut)}(x)))"
}

q"""
Expand All @@ -89,7 +89,7 @@ class CirceCodecBuilder[U <: Universe](val u: U) extends CodecBuilder {
}

def mkEnumDecoder(typ: Tree, subTermPairs: List[(String, Tree)]): Tree = {
val caseDefs = subTermPairs.map { case(jsonStr, subTerm) =>
val caseDefs = subTermPairs.map { case (jsonStr, subTerm) =>
cq"j if j == parser.parse($jsonStr).toOption.get => Either.right($subTerm)"
}

Expand Down
Loading

0 comments on commit f40935e

Please sign in to comment.