Skip to content
This repository has been archived by the owner on Apr 27, 2023. It is now read-only.

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
kciesielski committed Mar 23, 2016
1 parent ba1a651 commit b1cddf0
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*.iml
*.ipr
*.iws
.idea/
target/
*.log
.DS_Store
application.conf
data/
12 changes: 12 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name := "stringmask"

version := "1.0"

scalaVersion := "2.11.7"

libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % "2.11.7",
"org.scalatest" %% "scalatest" % "2.2.6" % "test"
)

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
1 change: 1 addition & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=0.13.11
66 changes: 66 additions & 0 deletions src/main/scala/com/softwaremill/tostringmask/CustomizeImpl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.softwaremill.tostringmask

import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.whitebox

class customize extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro CustomizeImpl.impl
}

class mask extends StaticAnnotation


object CustomizeImpl {

def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._

def extractCaseClassesParts(classDecl: ClassDef) = classDecl match {
case q"case class $className(..$fields) extends ..$parents { ..$body }" =>
(className, fields, parents, body)
}

def extractNewToString(typeName: TypeName, allFields: List[Tree]) = {
val fieldListTree = allFields.foldLeft(List.empty[Tree]) { case (accList, fieldTree) =>
fieldTree match {
case q"${Modifiers(fs, nm, List(q"new mask()"))} val $field: $fieldType = $sth" =>
accList :+ Literal(Constant("***"))
case q"$m val $field: $fieldType = $sth" =>
accList :+ q"$field.toString"
case _ => c.abort(c.enclosingPosition, "Cannot call .toString on tree " + showRaw(fieldTree))
}
}
val treesAsTuple = Apply(Select(Ident(TermName("scala")), TermName("Tuple" + fieldListTree.length)), fieldListTree)
val typeNameStrTree = Literal(Constant(typeName.toString))
q"""
override def toString: ${typeOf[String]} = {
$typeNameStrTree + $treesAsTuple
}
"""
}

def modifiedDeclaration(classDecl: ClassDef) = {
val (className, fields, parents, body) = extractCaseClassesParts(classDecl)
val newToString = extractNewToString(className, fields)

val params = fields.asInstanceOf[List[ValDef]] map { p => p.duplicate}

c.Expr[Any](
q"""
case class $className ( ..$params ) extends ..$parents {
$newToString
..$body
}
"""
)
}

annottees map (_.tree) toList match {
case (classDecl: ClassDef) :: Nil =>
modifiedDeclaration(classDecl)
case _ =>
c.abort(c.enclosingPosition, "Invalid annottee, expected case class.")
}
}
}
20 changes: 20 additions & 0 deletions src/test/scala/ToStringMaskTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import java.time.ZonedDateTime

import com.softwaremill.tostringmask.{customize, mask}
import org.scalatest.{FlatSpec, Matchers}

class ToStringMaskTest extends FlatSpec with Matchers {

behavior of "masking"

@customize
case class User20(id: Int, @mask name: String, @mask email: String, something: Long, @mask secretDob: ZonedDateTime)

it should "mask fields" in {
// given
val u = User20(1, "Secret Person", "secret@email.com", 15, ZonedDateTime.now())

// then
u.toString should be("User20(1,***,***,15,***)")
}
}

0 comments on commit b1cddf0

Please sign in to comment.