diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e33811f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000..0020bb5 --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,82 @@ +name: ci + +on: + push: + branches: + - master + tags: + - '*' + pull_request: + branches: + - master + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + java: ['8', '17'] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Run tests + run: ./mill -i all __.publishArtifacts __.test + + check-binary-compatibility: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 8 + - name: Check Binary Compatibility + run: ./mill -i __.mimaReportBinaryIssues + + publish-sonatype: + if: github.repository == 'com-lihaoyi/PPrint' && contains(github.ref, 'refs/tags/') + needs: test + runs-on: ubuntu-latest + env: + SONATYPE_PGP_PRIVATE_KEY: ${{ secrets.SONATYPE_PGP_PRIVATE_KEY }} + SONATYPE_PGP_PRIVATE_KEY_PASSWORD: ${{ secrets.SONATYPE_PGP_PRIVATE_KEY_PASSWORD }} + SONATYPE_USER: ${{ secrets.SONATYPE_USER }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + LANG: "en_US.UTF-8" + LC_MESSAGES: "en_US.UTF-8" + LC_ALL: "en_US.UTF-8" + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 8 + - name: Publish to Maven Central + run: | + if [[ $(git tag --points-at HEAD) != '' ]]; then + echo $SONATYPE_PGP_PRIVATE_KEY | base64 --decode > gpg_key + gpg --import --no-tty --batch --yes gpg_key + rm gpg_key + ./mill -i mill.scalalib.PublishModule/publishAll \ + --sonatypeCreds $SONATYPE_USER:$SONATYPE_PASSWORD \ + --gpgArgs --passphrase=$SONATYPE_PGP_PRIVATE_KEY_PASSWORD,--no-tty,--pinentry-mode,loopback,--batch,--yes,-a,-b \ + --publishArtifacts __.publishArtifacts \ + --readTimeout 600000 \ + --awaitTimeout 600000 \ + --release true \ + --signed true + fi + - name: Create GitHub Release + id: create_gh_release + uses: actions/create-release@v1.1.4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + draft: false diff --git a/.gitignore b/.gitignore index 8f15697..8c69d17 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ target/ project/.sbtserver tags nohup.out +.bloop/ +.metals/ +.vscode/ \ No newline at end of file diff --git a/.mill-version b/.mill-version new file mode 100644 index 0000000..9028ec6 --- /dev/null +++ b/.mill-version @@ -0,0 +1 @@ +0.10.5 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cd9ee36..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: scala - -sudo: required - -dist: trusty - - -jdk: - - oraclejdk8 - -script: - - curl https://raw.githubusercontent.com/scala-native/scala-native/master/scripts/travis_setup.sh | bash -x - - ./mill __.test.test diff --git a/README.md b/README.md index 2d4d27d..0dfa77b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -PPrint [![Gitter Chat][gitter-badge]][gitter-link] [![Build Status][travis-badge]][travis-link] +PPrint [![Gitter Chat][gitter-badge]][gitter-link] =============================================================================================== [travis-badge]: https://travis-ci.org/lihaoyi/PPrint.svg @@ -9,4 +9,4 @@ PPrint [![Gitter Chat][gitter-badge]][gitter-link] [![Build Status][travis-badge *A Scala library to pretty-print values and types* -[Documentation](https://lihaoyi.github.io/PPrint/) +[Documentation](https://com-lihaoyi.github.io/PPrint/) diff --git a/build.sc b/build.sc index b530e76..5ddc1b1 100644 --- a/build.sc +++ b/build.sc @@ -1,19 +1,40 @@ import mill._, scalalib._, scalajslib._, scalanativelib._, publish._ +import mill.scalalib.api.Util.isScala3 +import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.1.4` +import de.tobiasroeser.mill.vcs.version.VcsVersion +import $ivy.`com.github.lolgab::mill-mima::0.0.11` +import com.github.lolgab.mill.mima._ +val dottyCommunityBuildVersion = sys.props.get("dottyVersion").toList -trait PPrintModule extends PublishModule { +val scalaVersions = + "2.12.16" :: "2.13.8" :: "2.11.12" :: "3.1.3" :: dottyCommunityBuildVersion + +val scalaJSVersions = scalaVersions.map((_, "1.10.1")) +val scalaNativeVersions = scalaVersions.map((_, "0.4.5")) + +trait PPrintModule extends PublishModule with Mima { def artifactName = "pprint" - def publishVersion = "0.5.9" + def publishVersion = VcsVersion.vcsState().format() + + def mimaPreviousVersions = VcsVersion.vcsState().lastTag.toSeq + + def crossScalaVersion: String + + // Temporary until the next version of Mima gets released with + // https://github.com/lightbend/mima/issues/693 included in the release. + def mimaPreviousArtifacts = + if(isScala3(crossScalaVersion)) Agg.empty[Dep] else super.mimaPreviousArtifacts() def pomSettings = PomSettings( description = artifactName(), organization = "com.lihaoyi", - url = "https://github.com/lihaoyi/PPrint", + url = "https://github.com/com-lihaoyi/PPrint", licenses = Seq(License.MIT), - scm = SCM( - "git://github.com/lihaoyi/PPrint.git", - "scm:git://github.com/lihaoyi/PPrint.git" + versionControl = VersionControl.github( + owner = "com-lihaoyi", + repo = "PPrint" ), developers = Seq( Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi") @@ -23,13 +44,15 @@ trait PPrintModule extends PublishModule { trait PPrintMainModule extends CrossScalaModule { def millSourcePath = super.millSourcePath / offset def ivyDeps = Agg( - ivy"com.lihaoyi::fansi::0.2.9", - ivy"com.lihaoyi::sourcecode::0.2.1" - ) - def compileIvyDeps = Agg( - ivy"org.scala-lang:scala-reflect:${scalaVersion()}", - ivy"org.scala-lang:scala-compiler:${scalaVersion()}" + ivy"com.lihaoyi::fansi::0.4.0", + ivy"com.lihaoyi::sourcecode::0.3.0" ) + def compileIvyDeps = + if (crossScalaVersion.startsWith("2")) Agg( + ivy"${scalaOrganization()}:scala-reflect:${scalaVersion()}", + ivy"${scalaOrganization()}:scala-compiler:${scalaVersion()}" + ) + else Agg.empty[Dep] def offset: os.RelPath = os.rel def sources = T.sources( super.sources() @@ -40,46 +63,12 @@ trait PPrintMainModule extends CrossScalaModule { ) ) ) - def generatedSources = T{ - val dir = T.ctx().dest - val file = dir/"pprint"/"TPrintGen.scala" - - val typeGen = for(i <- 2 to 22) yield { - val ts = (1 to i).map("T" + _).mkString(", ") - val tsBounded = (1 to i).map("T" + _ + ": Type").mkString(", ") - val tsGet = (1 to i).map("get[T" + _ + "](cfg)").mkString(" + \", \" + ") - s""" - implicit def F${i}TPrint[$tsBounded, R: Type] = make[($ts) => R](cfg => - "(" + $tsGet + ") => " + get[R](cfg) - ) - implicit def T${i}TPrint[$tsBounded] = make[($ts)](cfg => - "(" + $tsGet + ")" - ) - """ - } - val output = s""" - package pprint - trait TPrintGen[Type[_], Cfg]{ - def make[T](f: Cfg => String): Type[T] - def get[T: Type](cfg: Cfg): String - implicit def F0TPrint[R: Type] = make[() => R](cfg => "() => " + get[R](cfg)) - implicit def F1TPrint[T1: Type, R: Type] = { - make[T1 => R](cfg => get[T1](cfg) + " => " + get[R](cfg)) - } - ${typeGen.mkString("\n")} - } - """.stripMargin - os.write(file, output, createFolders = true) - Seq(PathRef(file)) - } - } -trait PPrintTestModule extends ScalaModule with TestModule { +trait PPrintTestModule extends ScalaModule with TestModule.Utest { def crossScalaVersion: String - def testFrameworks = Seq("utest.runner.Framework") - def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.7.4") + def ivyDeps = Agg(ivy"com.lihaoyi::utest::0.8.0") def offset: os.RelPath = os.rel def millSourcePath = super.millSourcePath / os.up @@ -97,7 +86,7 @@ trait PPrintTestModule extends ScalaModule with TestModule { } object pprint extends Module { - object jvm extends Cross[JvmPPrintModule]("2.12.8", "2.13.0") + object jvm extends Cross[JvmPPrintModule](scalaVersions:_*) class JvmPPrintModule(val crossScalaVersion: String) extends PPrintMainModule with ScalaModule with PPrintModule { object test extends Tests with PPrintTestModule{ @@ -105,12 +94,10 @@ object pprint extends Module { } } - object js extends Cross[JsPPrintModule]( - ("2.12.8", "0.6.31"), ("2.13.0", "0.6.31"), - ("2.12.8", "1.0.0"), ("2.13.0", "1.0.0") - ) + object js extends Cross[JsPPrintModule](scalaJSVersions:_*) class JsPPrintModule(val crossScalaVersion: String, crossJSVersion: String) extends PPrintMainModule with ScalaJSModule with PPrintModule { + def offset = os.up def scalaJSVersion = crossJSVersion object test extends Tests with PPrintTestModule{ @@ -119,9 +106,7 @@ object pprint extends Module { } } - object native extends Cross[NativePPrintModule]( - ("2.11.12", "0.3.9"), ("2.11.12", "0.4.0-M2") - ) + object native extends Cross[NativePPrintModule](scalaNativeVersions:_*) class NativePPrintModule(val crossScalaVersion: String, crossScalaNativeVersion: String) extends PPrintMainModule with ScalaNativeModule with PPrintModule { def offset = os.up @@ -131,4 +116,5 @@ object pprint extends Module { val crossScalaVersion = NativePPrintModule.this.crossScalaVersion } } + } diff --git a/mill b/mill index d2957bc..e616548 100755 --- a/mill +++ b/mill @@ -3,14 +3,14 @@ # This is a wrapper script, that automatically download mill from GitHub release pages # You can give the required mill version with MILL_VERSION env variable # If no version is given, it falls back to the value of DEFAULT_MILL_VERSION -DEFAULT_MILL_VERSION=0.6.1 +DEFAULT_MILL_VERSION=0.10.5 set -e if [ -z "$MILL_VERSION" ] ; then if [ -f ".mill-version" ] ; then MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" - elif [ -f "mill" ] && [ "$BASH_SOURCE" != "mill" ] ; then + elif [ -f "mill" ] && [ "$0" != "mill" ] ; then MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2) else MILL_VERSION=$DEFAULT_MILL_VERSION @@ -28,13 +28,14 @@ version_remainder="$MILL_VERSION" MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" -if [ ! -x "$MILL_EXEC_PATH" ] ; then - mkdir -p $MILL_DOWNLOAD_PATH +if [ ! -s "$MILL_EXEC_PATH" ] ; then + mkdir -p "$MILL_DOWNLOAD_PATH" if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then ASSEMBLY="-assembly" fi DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download - MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION%%-*}/$MILL_VERSION${ASSEMBLY}" + MILL_VERSION_TAG=$(echo $MILL_VERSION | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION_TAG}/$MILL_VERSION${ASSEMBLY}" curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL" chmod +x "$DOWNLOAD_FILE" mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH" diff --git a/pprint/src-2.11/pprint/ProductSupport.scala b/pprint/src-2.11/pprint/ProductSupport.scala new file mode 100644 index 0000000..fd093b4 --- /dev/null +++ b/pprint/src-2.11/pprint/ProductSupport.scala @@ -0,0 +1,11 @@ +package pprint + +object ProductSupport { + + def treeifyProductElements(x: Product, + walker: Walker, + escapeUnicode: Boolean, + showFieldNames: Boolean): Iterator[Tree] = + x.productIterator.map(x => walker.treeify(x, escapeUnicode, showFieldNames)) + +} diff --git a/pprint/src-2.12/pprint/ProductSupport.scala b/pprint/src-2.12/pprint/ProductSupport.scala new file mode 100644 index 0000000..fd093b4 --- /dev/null +++ b/pprint/src-2.12/pprint/ProductSupport.scala @@ -0,0 +1,11 @@ +package pprint + +object ProductSupport { + + def treeifyProductElements(x: Product, + walker: Walker, + escapeUnicode: Boolean, + showFieldNames: Boolean): Iterator[Tree] = + x.productIterator.map(x => walker.treeify(x, escapeUnicode, showFieldNames)) + +} diff --git a/pprint/src-2.13/pprint/ProductSupport.scala b/pprint/src-2.13/pprint/ProductSupport.scala new file mode 100644 index 0000000..be854a8 --- /dev/null +++ b/pprint/src-2.13/pprint/ProductSupport.scala @@ -0,0 +1,19 @@ +package pprint + +object ProductSupport { + + def treeifyProductElements(x: Product, + walker: Walker, + escapeUnicode: Boolean, + showFieldNames: Boolean): Iterator[Tree] = { + if (!showFieldNames) x.productIterator.map(x => walker.treeify(x, escapeUnicode, showFieldNames)) + else x.productElementNames + .zipWithIndex + .map { + case (name, i) => + val elem = x.productElement(i) + Tree.KeyValue(name, walker.treeify(elem, escapeUnicode, showFieldNames)) + } + } + +} diff --git a/pprint/src-2.13/pprint/StringPrefix.scala b/pprint/src-2.13/pprint/StringPrefix.scala index 11f1872..76d8f3e 100644 --- a/pprint/src-2.13/pprint/StringPrefix.scala +++ b/pprint/src-2.13/pprint/StringPrefix.scala @@ -2,4 +2,4 @@ package pprint object StringPrefix{ def apply(i: Iterable[_]) = i.asInstanceOf[{ def collectionClassName: String }].collectionClassName -} \ No newline at end of file +} diff --git a/pprint/src-2/TPrintImpl.scala b/pprint/src-2/TPrintImpl.scala new file mode 100644 index 0000000..aa7ae18 --- /dev/null +++ b/pprint/src-2/TPrintImpl.scala @@ -0,0 +1,238 @@ +package pprint +import language.experimental.macros +import reflect.macros.blackbox.Context +import scala.reflect.macros.TypecheckException + +trait TPrintLowPri{ + implicit def default[T]: TPrint[T] = macro TPrintLowPri.typePrintImpl[T] +} +object TPrintLowPri{ + sealed trait WrapType + object WrapType{ + case object NoWrap extends WrapType + case object Infix extends WrapType + case object Tuple extends WrapType + } + def typePrintImpl[T: c.WeakTypeTag](c: Context): c.Expr[TPrint[T]] = { + // Used to provide "empty string" values in quasiquotes + + import c.universe._ + val tpe = weakTypeOf[T] + val rendered = typePrintImplRec(c)(tpe, rightMost = true).render + val res = c.Expr[TPrint[T]]( + q"_root_.pprint.TPrint.recolor(_root_.fansi.Str($rendered))" + ) + res + } + + val functionTypes = Range.inclusive(0, 22).map(i => s"scala.Function$i").toSet + val tupleTypes = Range.inclusive(0, 22).map(i => s"scala.Tuple$i").toSet + + def typePrintImplRec[T](c: Context)(tpe: c.Type, rightMost: Boolean): fansi.Str = { + typePrintImplRec0(c)(tpe, rightMost)._1 + } + def typePrintImplRec0[T](c: Context)(tpe: c.Type, rightMost: Boolean): (fansi.Str, WrapType) = { + import c.universe._ + def printSymString(s: Symbol) = + if (s.name.decodedName.toString.startsWith("_$")) "_" + else s.name.decodedName.toString.stripSuffix(".type") + + def literalColor(s: fansi.Str): fansi.Str = fansi.Color.Green(s) + def printSym(s: Symbol): fansi.Str = literalColor(printSymString(s)) + + + def printSymFull(s: Symbol): fansi.Str = { + if (lookup(s)) printSym(s) + else printSymFull(s.owner) ++ "." ++ printSym(s) + } + + /** + * Looks up a symbol in the enclosing scope and returns + * whether it exists in scope by the same name + */ + def lookup(s: Symbol) = { + val cas = c.asInstanceOf[reflect.macros.runtime.Context] + val g = cas.global + val gName = s.name.asInstanceOf[g.Name] + val lookedUps = for(n <- Stream(gName.toTermName, gName.toTypeName)) yield { + cas.callsiteTyper + .context + .lookupSymbol(n, _ => true) + .symbol + } + + if (!s.isType) { + // Try to de-reference `val` references + lookedUps.exists(x => + x == s || x.tpe.termSymbol == s.asTerm + ) + } else { + // Try to resolve aliases for types + lookedUps.exists(x => + x == s || x.tpe.typeSymbol == s.asInstanceOf[g.Symbol].tpe.typeSymbol + )} + } + + def prefixFor(pre: Type, sym: Symbol): fansi.Str = { + // Depending on what the prefix is, you may use `#`, `.` + // or even need to wrap the prefix in parentheses + val sep = pre match{ + case x if x.toString.endsWith(".type") => typePrintImplRec(c)(pre, false) ++ "." + case x: TypeRef => literalColor(typePrintImplRec(c)(pre, true)) ++ "#" + case x: SingleType => literalColor(typePrintImplRec(c)(pre, false)) ++ "." + case x: ThisType => literalColor(typePrintImplRec(c)(pre, false)) ++ "." + case x => fansi.Str("(") ++ typePrintImplRec(c)(pre, true) ++ ")#" + } + + val prefix = if (!lookup(sym)) sep else fansi.Str("") + prefix ++ printSym(sym) + } + + + def printArgSyms(args: List[Symbol]): fansi.Str = { + def added = args + .map{x => + val TypeBounds(lo, hi) = x.info + printSym(x) ++ printBounds(lo, hi) + } + .reduceLeft[fansi.Str]((l, r) => l ++ ", " ++ r) + + if (args == Nil) fansi.Str("") else fansi.Str("[") ++ added ++ "]" + } + def printArgs(args: List[Type]): fansi.Str = { + def added = args.map(typePrintImplRec(c)(_, true)) + .reduceLeft[fansi.Str]((l, r) => l ++ ", " ++ r) + + if (args == Nil) fansi.Str("") else fansi.Str("[") ++ added ++ "]" + } + + def printBounds(lo: Type, hi: Type) = { + val loTree = if (lo =:= typeOf[Nothing]) fansi.Str("") else fansi.Str(" >: ") ++ typePrintImplRec(c)(lo, true) + val hiTree = if (hi =:= typeOf[Any]) fansi.Str("") else fansi.Str(" <: ") ++ typePrintImplRec(c)(hi, true) + loTree ++ hiTree + } + + def showRefinement(quantified: List[Symbol]) = { + def stmts = for{ + t <- quantified + suffix <- t.info match { + case PolyType(typeParams, resultType) => + val paramTree = printArgSyms(t.asInstanceOf[TypeSymbol].typeParams) + val resultBounds = + if (resultType =:= typeOf[Any]) fansi.Str("") + else fansi.Str(" <: ") ++ typePrintImplRec(c)(resultType, true) + + Some(paramTree ++ resultBounds) + case TypeBounds(lo, hi) + if t.toString.contains("$") && lo =:= typeOf[Nothing] && hi =:= typeOf[Any] => + None + case TypeBounds(lo, hi) => + Some( printBounds(lo, hi) ) + } + } yield { + if (t.toString.endsWith(".type")) { + val TypeBounds(lo, hi) = t.info + val RefinedType(parents, defs) = hi + val filtered = internal.refinedType( + parents.filter(x => !(x =:= typeOf[scala.Singleton])), + defs + ) + + fansi.Str("val ") ++ literalColor(t.name.toString.stripSuffix(".type")) ++ + ": " ++ typePrintImplRec(c)(filtered, true) + }else { + fansi.Str("type ") ++ printSym(t) ++ suffix + } + } + if (stmts.length == 0) None + else Some(stmts.reduceLeft((l, r) => l + "; " + r)) + } + + tpe match { + case TypeBounds(lo, hi) => + val res = printBounds(lo, hi) + (fansi.Str("_") ++ res, WrapType.NoWrap) + case ThisType(sym) => + (printSymFull(sym) + (if(sym.isPackage || sym.isModuleClass) "" else ".this.type"), WrapType.NoWrap) + + case SingleType(NoPrefix, sym) => (printSym(sym) ++ (if (rightMost) ".type" else ""), WrapType.NoWrap) + case SingleType(pre, sym) => (prefixFor(pre, sym) ++ (if (rightMost) ".type" else ""), WrapType.NoWrap) + // Special-case operator two-parameter types as infix + case TypeRef(pre, sym, List(left, right)) + if lookup(sym) && sym.name.encodedName.toString != sym.name.decodedName.toString => + + ( + typePrintImplRec(c)(left, true) ++ " " ++ printSym(sym) ++ " " ++ typePrintImplRec(c)(right, true), + WrapType.Infix + ) + + case TypeRef(pre, sym, args) if functionTypes.contains(sym.fullName) => + args match{ + case Seq(r) => (fansi.Str("() => ") ++ typePrintImplRec(c)(r, true), WrapType.Infix) + + case many => + val (left, leftWrap) = typePrintImplRec0(c)(many.head, true) + + if (many.size == 2 && leftWrap == WrapType.NoWrap){ + (left ++ " => " ++ typePrintImplRec(c)(many(1), true), WrapType.Infix) + }else ( + fansi.Str("(") ++ + fansi.Str.join( + (left +: many.init.tail.map(typePrintImplRec(c)(_, true))), + sep = ", " + + ) ++ + ") => " ++ typePrintImplRec(c)(many.last, true), + WrapType.Infix + ) + } + case TypeRef(pre, sym, args) if tupleTypes.contains(sym.fullName) => + ( + fansi.Str("(") ++ + fansi.Str.join(args.map(typePrintImplRec(c)(_, true)), sep = ", ") ++ + ")", + WrapType.Tuple + ) + + case TypeRef(NoPrefix, sym, args) => (printSym(sym) ++ printArgs(args), WrapType.NoWrap) + case TypeRef(pre, sym, args) => + if (sym.fullName == "scala.") (fansi.Str("=> ") ++ typePrintImplRec(c)(args(0), true), WrapType.Infix) + else (prefixFor(pre, sym) ++ printArgs(args), WrapType.NoWrap) + case et @ ExistentialType(quantified, underlying) => + ( + showRefinement(quantified) match{ + case None => typePrintImplRec(c)(underlying, true) + case Some(block) => typePrintImplRec(c)(underlying, true) ++ " forSome { " ++ block ++ " }" + }, + WrapType.NoWrap + ) + case AnnotatedType(annots, tp) => + val mapped = annots.map(x => " @" + typePrintImplRec(c)(x.tpe, true)) + .reduceLeft((x, y) => x + y) + + ( + typePrintImplRec(c)(tp, true) + mapped, + WrapType.NoWrap + ) + case RefinedType(parents, defs) => + val pre = + if (parents.forall(_ =:= typeOf[AnyRef])) "" + else parents + .map(typePrintImplRec(c)(_, true)) + .reduceLeft[fansi.Str]((l, r) => l ++ " with " ++ r) + (pre + (if (defs.isEmpty) "" else "{" ++ defs.mkString(";") ++ "}"), WrapType.NoWrap) + case PolyType(typeParams, resultType) => + val params = printArgSyms(typeParams) + ( + params ++ typePrintImplRec(c)(resultType, true), + WrapType.NoWrap + ) + case ConstantType(value) => + val pprintedValue = + pprint.PPrinter.BlackWhite.copy(colorLiteral = fansi.Color.Green).apply(value.value) + + (pprintedValue, WrapType.NoWrap) + } + } + +} diff --git a/pprint/src-3/ProductSupport.scala b/pprint/src-3/ProductSupport.scala new file mode 100644 index 0000000..be854a8 --- /dev/null +++ b/pprint/src-3/ProductSupport.scala @@ -0,0 +1,19 @@ +package pprint + +object ProductSupport { + + def treeifyProductElements(x: Product, + walker: Walker, + escapeUnicode: Boolean, + showFieldNames: Boolean): Iterator[Tree] = { + if (!showFieldNames) x.productIterator.map(x => walker.treeify(x, escapeUnicode, showFieldNames)) + else x.productElementNames + .zipWithIndex + .map { + case (name, i) => + val elem = x.productElement(i) + Tree.KeyValue(name, walker.treeify(elem, escapeUnicode, showFieldNames)) + } + } + +} diff --git a/pprint/src-3/StringPrefix.scala b/pprint/src-3/StringPrefix.scala new file mode 100644 index 0000000..f7456b1 --- /dev/null +++ b/pprint/src-3/StringPrefix.scala @@ -0,0 +1,7 @@ +package pprint + +import reflect.Selectable.reflectiveSelectable + +object StringPrefix{ + def apply(i: Iterable[_]) = i.asInstanceOf[{ def collectionClassName: String }].collectionClassName +} diff --git a/pprint/src-3/TPrintImpl.scala b/pprint/src-3/TPrintImpl.scala new file mode 100644 index 0000000..54b7524 --- /dev/null +++ b/pprint/src-3/TPrintImpl.scala @@ -0,0 +1,131 @@ +package pprint + +trait TPrintLowPri{ + inline given default[T]: TPrint[T] = ${ TPrintLowPri.typePrintImpl[T] } +} + +object TPrintLowPri{ + + import scala.quoted._ + + sealed trait WrapType + object WrapType{ + case object NoWrap extends WrapType + case object Infix extends WrapType + case object Tuple extends WrapType + } + + val functionTypes = Range.inclusive(0, 22).map(i => s"scala.Function$i").toSet + val tupleTypes = Range.inclusive(0, 22).map(i => s"scala.Tuple$i").toSet + + def typePrintImpl[T](using Quotes, Type[T]): Expr[TPrint[T]] = { + + import quotes.reflect._ + import util._ + + def literalColor(s: fansi.Str): fansi.Str = { + fansi.Color.Green(s) + } + + def printSymString(s: String) = + if (s.toString.startsWith("_$")) "_" + else s.toString.stripSuffix(".type") + + def printBounds(lo: TypeRepr, hi: TypeRepr): fansi.Str = { + val loTree = + if (lo =:= TypeRepr.of[Nothing]) None else Some(fansi.Str(" >: ") ++ rec(lo) ) + val hiTree = + if (hi =:= TypeRepr.of[Any]) None else Some(fansi.Str(" <: ") ++ rec(hi) ) + val underscore = fansi.Str("_") + loTree.orElse(hiTree).map(underscore ++ _).getOrElse(underscore) + } + + def printSym(s: String): fansi.Str = literalColor(fansi.Str(s)) + + //TODO: We don't currently use this method + def prefixFor(pre: TypeTree, sym: String): fansi.Str = { + // Depending on what the prefix is, you may use `#`, `.` + // or even need to wrap the prefix in parentheses + val sep = pre match { + case x if x.toString.endsWith(".type") => + rec(pre.tpe) ++ "." + } + sep ++ printSym(sym) + } + + + def printArgs(args: List[TypeRepr]): fansi.Str = { + fansi.Str("[") ++ printArgs0(args) ++ "]" + } + + def printArgs0(args: List[TypeRepr]): fansi.Str = { + val added = fansi.Str.join( + args.map { + case TypeBounds(lo, hi) => + printBounds(lo, hi) + case tpe: TypeRepr => + rec(tpe, false) + }, + sep = ", " + ) + added + } + + + object RefinedType { + def unapply(tpe: TypeRepr): Option[(TypeRepr, List[(String, TypeRepr)])] = tpe match { + case Refinement(p, i, b) => + unapply(p).map { + case (pp, bs) => (pp, (i -> b) :: bs) + }.orElse(Some((p, (i -> b) :: Nil))) + case _ => None + } + } + + def rec(tpe: TypeRepr, end: Boolean = false): fansi.Str = rec0(tpe)._1 + def rec0(tpe: TypeRepr, end: Boolean = false): (fansi.Str, WrapType) = tpe match { + case TypeRef(NoPrefix(), sym) => + (printSym(sym), WrapType.NoWrap) + // TODO: Add prefix handling back in once it works! + case TypeRef(_, sym) => + (printSym(sym), WrapType.NoWrap) + case AppliedType(tpe, args) => + if (functionTypes.contains(tpe.typeSymbol.fullName)) { + ( + if(args.size == 1 ) fansi.Str("() => ") ++ rec(args.last) + else{ + val (left, wrap) = rec0(args(0)) + if(args.size == 2 && wrap == WrapType.NoWrap){ + left ++ fansi.Str(" => ") ++ rec(args.last) + } + else fansi.Str("(") ++ printArgs0(args.dropRight(1)) ++ fansi.Str(") => ") ++ rec(args.last) + + }, + WrapType.Infix + ) + + } else if (tupleTypes.contains(tpe.typeSymbol.fullName)) + (fansi.Str("(") ++ printArgs0(args) ++ fansi.Str(")"), WrapType.Tuple) + else (printSym(tpe.typeSymbol.name) ++ printArgs(args), WrapType.NoWrap) + case RefinedType(tpe, refinements) => + val pre = rec(tpe) + lazy val defs = fansi.Str.join( + refinements.collect { + case (name, tpe: TypeRepr) => + fansi.Str("type " + name + " = ") ++ rec(tpe) + case (name, TypeBounds(lo, hi)) => + fansi.Str("type " + name) ++ printBounds(lo, hi) ++ rec(tpe) + }, + sep = "; " + ) + (pre ++ (if(refinements.isEmpty) fansi.Str("") else fansi.Str("{") ++ defs ++ "}"), WrapType.NoWrap) + case AnnotatedType(parent, annot) => + (rec(parent, end), WrapType.NoWrap) + case _ => + (fansi.Str(Type.show[T]), WrapType.NoWrap) + } + val value: fansi.Str = rec(TypeRepr.of[T]) + + '{TPrint.recolor(fansi.Str(${Expr(value.render)}))} + } +} diff --git a/pprint/src/pprint/PPrinter.scala b/pprint/src/pprint/PPrinter.scala index 4e1612f..27df702 100644 --- a/pprint/src/pprint/PPrinter.scala +++ b/pprint/src/pprint/PPrinter.scala @@ -1,5 +1,7 @@ package pprint +import java.io.PrintStream + /** * * @param defaultWidth How wide to allow a pretty-printed value to become @@ -15,16 +17,13 @@ package pprint case class PPrinter(defaultWidth: Int = 100, defaultHeight: Int = 500, defaultIndent: Int = 2, + defaultEscapeUnicode: Boolean = false, + defaultShowFieldNames: Boolean = true, colorLiteral: fansi.Attrs = fansi.Color.Green, colorApplyPrefix: fansi.Attrs = fansi.Color.Yellow, additionalHandlers: PartialFunction[Any, Tree] = PartialFunction.empty) extends Walker{ outer => - /** - * How to convert a thing into a [[Tree]] that can be pretty-printed. - */ - override def treeify(x: Any) = super.treeify(x) - /** * Logs a given value to stdout with some metadata to identify where the log * message came from. Hard-coded and not very flexible, but you can easily @@ -35,14 +34,42 @@ case class PPrinter(defaultWidth: Int = 100, width: Int = defaultWidth, height: Int = defaultHeight, indent: Int = defaultIndent, - initialOffset: Int = 0) + escapeUnicode: Boolean = defaultEscapeUnicode, + showFieldNames: Boolean = defaultShowFieldNames) (implicit line: sourcecode.Line, fileName: sourcecode.FileName): T = { + doLog(x, tag, width, height, indent, escapeUnicode, showFieldNames, Console.out)(line, fileName) + } - def joinSeq[T](seq: Seq[T], sep: T): Seq[T] = { - seq.flatMap(x => Seq(x, sep)).dropRight(1) + object err { + /** + * Logs a given value to stderr with some metadata to identify where the log + * message came from. Hard-coded and not very flexible, but you can easily + * implement your own log method if you want to customize it further. + */ + def log[T](x: sourcecode.Text[T], + tag: String = "", + width: Int = defaultWidth, + height: Int = defaultHeight, + indent: Int = defaultIndent, + escapeUnicode: Boolean = defaultEscapeUnicode, + showFieldNames: Boolean = defaultShowFieldNames) + (implicit line: sourcecode.Line, + fileName: sourcecode.FileName): T = { + doLog(x, tag, width, height, indent, escapeUnicode, showFieldNames, Console.err)(line, fileName) } + } + private def doLog[T](x: sourcecode.Text[T], + tag: String, + width: Int, + height: Int, + indent: Int, + escapeUnicode: Boolean, + showFieldNames: Boolean, + out: PrintStream) + (implicit line: sourcecode.Line, + fileName: sourcecode.FileName): T = { val tagStrs = if (tag.isEmpty) Seq() else Seq(fansi.Color.Cyan(tag), fansi.Str(" ")) @@ -55,9 +82,19 @@ case class PPrinter(defaultWidth: Int = 100, fansi.Color.Cyan(x.source), fansi.Str(": ") ) ++ tagStrs - val str = fansi.Str.join(prefix ++ tokenize(x.value, width, height, indent).toSeq:_*) + val str = fansi.Str.join( + prefix ++ + tokenize( + x.value, + width, + height, + indent, + escapeUnicode = escapeUnicode, + showFieldNames = showFieldNames + ).toSeq + ) - println(str) + out.println(str) x.value } @@ -68,8 +105,20 @@ case class PPrinter(defaultWidth: Int = 100, width: Int = defaultWidth, height: Int = defaultHeight, indent: Int = defaultIndent, - initialOffset: Int = 0): fansi.Str = { - fansi.Str.join(this.tokenize(x, width, height, indent).toSeq:_*) + initialOffset: Int = 0, + escapeUnicode: Boolean = defaultEscapeUnicode, + showFieldNames: Boolean = defaultShowFieldNames): fansi.Str = { + fansi.Str.join( + this.tokenize( + x, + width, + height, + indent, + initialOffset, + escapeUnicode = escapeUnicode, + showFieldNames = showFieldNames + ).toSeq + ) } /** @@ -78,11 +127,20 @@ case class PPrinter(defaultWidth: Int = 100, def pprintln[T](x: T, width: Int = defaultWidth, height: Int = defaultHeight, - indent: Int = defaultIndent, - initialOffset: Int = 0): Unit = { - tokenize(x, width, height, indent, initialOffset).foreach(print) + indent: Int = defaultIndent, + initialOffset: Int = 0, + escapeUnicode: Boolean = defaultEscapeUnicode, + showFieldNames: Boolean = defaultShowFieldNames): Unit = { + tokenize( + x, + width, + height, + indent, + initialOffset, + escapeUnicode = escapeUnicode, + showFieldNames = showFieldNames + ).foreach(print) println() - x } /** @@ -93,12 +151,14 @@ case class PPrinter(defaultWidth: Int = 100, width: Int = defaultWidth, height: Int = defaultHeight, indent: Int = defaultIndent, - initialOffset: Int = 0): Iterator[fansi.Str] = { + initialOffset: Int = 0, + escapeUnicode: Boolean = defaultEscapeUnicode, + showFieldNames: Boolean = defaultShowFieldNames): Iterator[fansi.Str] = { // The three stages within the pretty-printing process: // Convert the Any into a lazy Tree of `Apply`, `Infix` and `Lazy`/`Strict` literals - val tree = this.treeify(x) + val tree = this.treeify(x, escapeUnicode, showFieldNames) // Render the `Any` into a stream of tokens, properly indented and wrapped // at the given width val renderer = new Renderer(width, colorApplyPrefix, colorLiteral, indent) diff --git a/pprint/src/pprint/Renderer.scala b/pprint/src/pprint/Renderer.scala index e52b487..cf13869 100644 --- a/pprint/src/pprint/Renderer.scala +++ b/pprint/src/pprint/Renderer.scala @@ -26,6 +26,7 @@ class Renderer(maxWidth: Int, def rec(x: Tree, leftOffset: Int, indentCount: Int): Result = x match{ case Tree.Apply(prefix, body) => + val nonEmpty = body.hasNext // Render children and buffer them until you fill up a single line, // or you run out of children. // @@ -82,6 +83,18 @@ class Renderer(maxWidth: Int, () => Iterator(Renderer.closeParen) ) + val length: Int = buffer.iterator.map(_.iterator.map(_.length).sum).sum + new Result(iter, 0, length) + }else if (!nonEmpty && totalHorizontalWidth > maxWidth) { + val iter = Util.concat( + () => applyHeader, + () => Iterator( + Renderer.newLine, + Renderer.indent(indentCount * indentStep), + Renderer.closeParen + ) + ) + val length: Int = buffer.iterator.map(_.iterator.map(_.length).sum).sum new Result(iter, 0, length) } else { @@ -135,6 +148,11 @@ class Renderer(maxWidth: Int, case t: Tree.Literal => Result.fromString(colorLiteral(t.body)) + case Tree.KeyValue(k, v) => + val prefix = s"$k = " + Result.fromString(prefix) + .flatMap((_, _) => rec(v, leftOffset + prefix.length, indentCount)) + } } diff --git a/pprint/src/pprint/TPrint.scala b/pprint/src/pprint/TPrint.scala index 30a5452..80f6d60 100644 --- a/pprint/src/pprint/TPrint.scala +++ b/pprint/src/pprint/TPrint.scala @@ -9,28 +9,34 @@ package pprint * - Prefixed Types are printed un-qualified, according to * what's currently in scope */ -trait TPrint[T]{ - def render(implicit cfg: TPrintColors): String +trait TPrint[T] { + def render(implicit tpc: TPrintColors): fansi.Str + } -object TPrint extends TPrintGen[TPrint, TPrintColors] with TPrintLowPri{ - def literal[T](s: String) = new TPrint[T]{ - def render(implicit cfg: TPrintColors) = cfg.typeColor(s).toString - } - def lambda[T](f: TPrintColors => String) = new TPrint[T]{ - def render(implicit cfg: TPrintColors) = f(cfg) +object TPrint extends TPrintLowPri{ + def recolor[T](s: fansi.Str): TPrint[T] = { + new TPrint[T]{ + def render(implicit tpc: TPrintColors) = { + val colors = s.getColors + val updatedColors = colors.map{ + c => if (c == fansi.Color.Green.applyMask) tpc.typeColor.applyMask else 0L + } + fansi.Str.fromArrays(s.getChars, updatedColors) + } + } } - def make[T](f: TPrintColors => String) = TPrint.lambda[T](f) - def get[T](cfg: TPrintColors)(implicit t: TPrint[T]) = t.render(cfg) def implicitly[T](implicit t: TPrint[T]): TPrint[T] = t - implicit val NothingTPrint: TPrint[Nothing] = TPrint.literal("Nothing") + implicit val NothingTPrint: TPrint[Nothing] = + recolor[Nothing](fansi.Color.Green("Nothing")) + } case class TPrintColors(typeColor: fansi.Attrs) -object TPrintColors{ +object TPrintColors { implicit object BlackWhite extends TPrintColors(fansi.Attrs()) object Colors extends TPrintColors(fansi.Color.Green){ - implicit val Colored = this + implicit val Colored: TPrintColors = this } -} \ No newline at end of file +} diff --git a/pprint/src/pprint/TPrintImpl.scala b/pprint/src/pprint/TPrintImpl.scala deleted file mode 100644 index 4c04a6e..0000000 --- a/pprint/src/pprint/TPrintImpl.scala +++ /dev/null @@ -1,207 +0,0 @@ -package pprint -import language.experimental.macros -import reflect.macros.blackbox.Context -import scala.reflect.macros.TypecheckException - -trait TPrintLowPri{ - implicit def default[T]: TPrint[T] = macro TPrintLowPri.typePrintImpl[T] -} -object TPrintLowPri{ - def typePrintImpl[T: c.WeakTypeTag](c: Context): c.Expr[TPrint[T]] = { - // Used to provide "empty string" values in quasiquotes - import c.universe._ - val s = "" - val tpe = weakTypeOf[T] - def printSymString(s: Symbol) = - if (s.name.decodedName.toString.startsWith("_$")) "_" - else s.name.decodedName.toString.stripSuffix(".type") - - def literalColor(s: Tree) = { - q"$cfgSym.typeColor($s).render" - } - def printSym(s: Symbol): Tree = { - literalColor(q"${printSymString(s)}") - } - - def printSymFull(s: Symbol): Tree = { - if (lookup(s)) printSym(s) - else q"""${printSymFull(s.owner)} + "." + ${printSym(s)}""" - - } - /** - * Looks up a symbol in the enclosing scope and returns - * whether it exists in scope by the same name - */ - def lookup(s: Symbol) = { - val cas = c.asInstanceOf[reflect.macros.runtime.Context] - val g = cas.global - val gName = s.name.asInstanceOf[g.Name] - val lookedUps = for(n <- Stream(gName.toTermName, gName.toTypeName)) yield { - cas.callsiteTyper - .context - .lookupSymbol(n, _ => true) - .symbol - } - - if (!s.isType) { - // Try to de-reference `val` references - lookedUps.exists(x => - x == s || x.tpe.termSymbol == s.asTerm - ) - } else { - // Try to resolve aliases for types - lookedUps.exists(x => - x == s || x.tpe.typeSymbol == s.asInstanceOf[g.Symbol].tpe.typeSymbol - )} - } - - def prefixFor(pre: Type, sym: Symbol): Tree = { - // Depending on what the prefix is, you may use `#`, `.` - // or even need to wrap the prefix in parentheses - val sep = pre match{ - case x if x.toString.endsWith(".type") => q""" ${rec0(pre)} + "." """ - case x: TypeRef => q""" ${literalColor(implicitRec(pre))} + "#" """ - case x: SingleType => q""" ${literalColor(rec0(pre))} + "." """ - case x: ThisType => q""" ${literalColor(rec0(pre))} + "." """ - case x => q""" "(" + ${implicitRec(pre)} + ")#" """ - } - - val prefix = if (!lookup(sym)) sep else q"$s" - q"$prefix + ${printSym(sym)}" - } - - - def printArgSyms(args: List[Symbol]): Tree = { - def added = args.map{x => - val TypeBounds(lo, hi) = x.info - q""" ${printSym(x)} + ${printBounds(lo, hi)}""" - }.reduceLeft[Tree]((l, r) => q"""$l + ", " + $r""") - if (args == Nil) q"$s" else q""" "[" + $added + "]" """ - } - def printArgs(args: List[Type]): Tree = { - def added = args.map(implicitRec(_)) - .reduceLeft[Tree]((l, r) => q"""$l + ", " + $r""") - - if (args == Nil) q"$s" else q""" "[" + $added + "]" """ - } - - - def implicitRec(tpe: Type) = { - val byName = (tpe: Type) match{ - case t: TypeRef if t.toString.startsWith("=> ") => Some(t.args(0)) - case _ => None - } - - try { - // Make sure the type isn't higher-kinded or some other weird - // thing, and actually can fit inside the square brackets - - byName match{ - case Some(t) => - c.typecheck(q"null.asInstanceOf[$tpe]") - q""" "=> " + _root_.pprint.TPrint.implicitly[$t].render($cfgSym) """ - case _ => - c.typecheck(q"null.asInstanceOf[$tpe]") - q""" _root_.pprint.TPrint.implicitly[$tpe].render($cfgSym) """ - } - - }catch{case e: TypecheckException => - rec0(tpe) - } - } - def printBounds(lo: Type, hi: Type) = { - val loTree = if (lo =:= typeOf[Nothing]) q"$s" else q""" " >: " + ${implicitRec(lo)} """ - val hiTree = if (hi =:= typeOf[Any]) q"$s" else q""" " <: " + ${implicitRec(hi)} """ - q"$loTree + $hiTree" - } - - def showRefinement(quantified: List[Symbol]) = { - def stmts = for{ - t <- quantified - suffix <- t.info match { - case PolyType(typeParams, resultType) => - val paramTree = printArgSyms(t.asInstanceOf[TypeSymbol].typeParams) - val resultBounds = - if (resultType =:= typeOf[Any]) q"$s" - else q""" " <: " + ${implicitRec(resultType)} """ - - Some(q""" $paramTree + $resultBounds""") - case TypeBounds(lo, hi) - if t.toString.contains("$") && lo =:= typeOf[Nothing] && hi =:= typeOf[Any] => - None - case TypeBounds(lo, hi) => - Some( printBounds(lo, hi) ) - } - } yield { - if (t.toString.endsWith(".type")) { - val TypeBounds(lo, hi) = t.info - val RefinedType(parents, defs) = hi - val filtered = internal.refinedType( - parents.filter(x => !(x =:= typeOf[scala.Singleton])), - defs - ) - - q""" - "val " + ${literalColor(q"${t.name.toString.stripSuffix(".type")}")} + - ": " + ${implicitRec(filtered)} - """ - }else { - q""" "type " + ${printSym(t)} + $suffix """ - } - } - if (stmts.length == 0) None - else Some(stmts.reduceLeft((l, r) => q""" $l + "; " + $r """)) - } - /** - * Decide how to pretty-print, based on the type. - * - * This is recursive, but we only rarely use direct recursion: more - * often, we'll use `implicitRec`, which goes through the normal - * implicit search channel and can thus - */ - def rec0(tpe: Type, end: Boolean = false): Tree = tpe match { - case TypeBounds(lo, hi) => - val res = printBounds(lo, hi) - q""" "_" + $res """ - case ThisType(sym) => - q"${printSymFull(sym)} + ${if(sym.isPackage || sym.isModuleClass) "" else ".this.type"}" - - case SingleType(NoPrefix, sym) => q"${printSym(sym)} + ${if (end) ".type" else ""}" - case SingleType(pre, sym) => q"${prefixFor(pre, sym)} + ${if (end) ".type" else ""}" - // Special-case operator two-parameter types as infix - case TypeRef(pre, sym, List(left, right)) - if lookup(sym) && sym.name.encodedName.toString != sym.name.decodedName.toString => - - q"""${implicitRec(left)} + " " + ${printSym(sym)} + " " +${implicitRec(right)}""" - - case TypeRef(NoPrefix, sym, args) => q"${printSym(sym)} + ${printArgs(args)}" - case TypeRef(pre, sym, args) => q"${prefixFor(pre, sym)} + ${printArgs(args)}" - case et @ ExistentialType(quantified, underlying) => - showRefinement(quantified) match{ - case None => implicitRec(underlying) - case Some(block) => q"""${implicitRec(underlying)} + " forSome { " + $block + " }" """ - } - case AnnotatedType(annots, tp) => - val mapped = annots.map(x => q""" " @" + ${implicitRec(x.tpe)}""") - .reduceLeft((x, y) => q"$x + $y") - q"${implicitRec(tp)} + $mapped" - case RefinedType(parents, defs) => - val pre = - if (parents.forall(_ =:= typeOf[AnyRef])) q""" "" """ - else parents.map(implicitRec(_)).reduceLeft[Tree]((l, r) => q"""$l + " with " + $r""") - q"$pre + ${ - if (defs.isEmpty) "" else "{" + defs.mkString(";") + "}" - }" - case ConstantType(value) => - q"$value.toString" - } - lazy val cfgSym = c.freshName[TermName]("cfg") - val res = c.Expr[TPrint[T]](q"""_root_.pprint.TPrint.lambda{ - ($cfgSym: _root_.pprint.TPrintColors) => - ${rec0(tpe, end = true)} - }""") - // println("RES " + res) - res - } - -} \ No newline at end of file diff --git a/pprint/src/pprint/Util.scala b/pprint/src/pprint/Util.scala index f6818a2..9a56420 100644 --- a/pprint/src/pprint/Util.scala +++ b/pprint/src/pprint/Util.scala @@ -47,13 +47,13 @@ object Util{ } def isOperator(ident: String) = { - ident(0) match{ + (ident.size > 0) && (ident(0) match{ case '<' | '~' | '!' | '@' | '#' | '%' | '^' | '*' | '+' | '-' | /*'<' | */ '>' | '?' | ':' | '=' | '&' | '|' | '/' | '\\' => true case _ => false - } + }) } def escapeChar(c: Char, sb: StringBuilder, @@ -81,7 +81,7 @@ object Util{ var i = 0 val len = s.length while (i < len) { - Util.escapeChar(s(i), sb) + Util.escapeChar(s(i), sb, unicode) i += 1 } sb.append('"') diff --git a/pprint/src/pprint/Walker.scala b/pprint/src/pprint/Walker.scala index bb6868f..f11f7a8 100644 --- a/pprint/src/pprint/Walker.scala +++ b/pprint/src/pprint/Walker.scala @@ -25,6 +25,11 @@ object Tree{ val hasNewLine = body.exists(c => c == '\n' || c == '\r') } + /** + * x = y + */ + case class KeyValue(key: String, value: Tree) extends Tree + /** * xyz */ @@ -40,17 +45,20 @@ object Tree{ abstract class Walker{ val tuplePrefix = "scala.Tuple" + def additionalHandlers: PartialFunction[Any, Tree] - def treeify(x: Any): Tree = additionalHandlers.lift(x).getOrElse{ + def treeify(x: Any, escapeUnicode: Boolean, showFieldNames: Boolean): Tree = additionalHandlers.lift(x).getOrElse{ x match{ case null => Tree.Literal("null") + case x: Boolean => Tree.Literal(x.toString) case x: Char => val sb = new StringBuilder sb.append('\'') - Util.escapeChar(x, sb) + Util.escapeChar(x, sb, escapeUnicode) sb.append('\'') Tree.Literal(sb.toString) + case x: Byte => Tree.Literal(x.toString) case x: Short => Tree.Literal(x.toString) case x: Int => Tree.Literal(x.toString) @@ -59,19 +67,19 @@ abstract class Walker{ case x: Double => Tree.Literal(x.toString) case x: String => if (x.exists(c => c == '\n' || c == '\r')) Tree.Literal("\"\"\"" + x + "\"\"\"") - else Tree.Literal(Util.literalize(x)) + else Tree.Literal(Util.literalize(x, escapeUnicode)) - case x: Symbol => Tree.Literal(x.toString) + case x: Symbol => Tree.Literal("'" + x.name) case x: scala.collection.Map[_, _] => Tree.Apply( StringPrefix(x), x.iterator.flatMap { case (k, v) => - Seq(Tree.Infix(treeify(k), "->", treeify(v))) + Seq(Tree.Infix(treeify(k, escapeUnicode, showFieldNames), "->", treeify(v, escapeUnicode, showFieldNames))) } ) - case x: Iterable[_] => Tree.Apply(StringPrefix(x), x.iterator.map(x => treeify(x))) + case x: Iterable[_] => Tree.Apply(StringPrefix(x), x.iterator.map(x => treeify(x, escapeUnicode, showFieldNames))) case None => Tree.Literal("None") @@ -82,32 +90,39 @@ abstract class Walker{ else Tree.Literal("non-empty iterator") - case x: Array[_] => Tree.Apply("Array", x.iterator.map(x => treeify(x))) + case x: Array[_] => Tree.Apply("Array", x.iterator.map(x => treeify(x, escapeUnicode, showFieldNames))) case x: Product => val className = x.getClass.getName if (x.productArity == 0) Tree.Lazy(ctx => Iterator(x.toString)) else if(x.productArity == 2 && Util.isOperator(x.productPrefix)){ Tree.Infix( - treeify(x.productElement(0)), + treeify(x.productElement(0), escapeUnicode, showFieldNames), x.productPrefix, - treeify(x.productElement(1)) + treeify(x.productElement(1), escapeUnicode, showFieldNames) ) } else (className.startsWith(tuplePrefix), className.lift(tuplePrefix.length)) match{ // leave out tuple1, so it gets printed as Tuple1(foo) instead of (foo) // Don't check the whole suffix, because of specialization there may be // funny characters after the digit case (true, Some('2' | '3' | '4' | '5' | '6' | '7' | '8' | '9')) => - Tree.Apply("", x.productIterator.map(x => treeify(x))) + Tree.Apply("", x.productIterator.map(x => treeify(x, escapeUnicode, showFieldNames))) case _ => - Tree.Apply(x.productPrefix, x.productIterator.map(x => treeify(x))) + Tree.Apply(x.productPrefix, ProductSupport.treeifyProductElements(x, this, escapeUnicode, showFieldNames)) } - case x => Tree.Lazy(ctx => Iterator(x.toString)) + case x => Tree.Lazy(ctx => + Iterator( + x.toString match{ + case null => "null" + case s =>s + } + ) + ) } } -} \ No newline at end of file +} diff --git a/pprint/src/pprint/package.scala b/pprint/src/pprint/package.scala index fd56501..47205f5 100644 --- a/pprint/src/pprint/package.scala +++ b/pprint/src/pprint/package.scala @@ -6,6 +6,6 @@ */ package object pprint extends PPrinter{ def tprint[T: TPrint](implicit config: TPrintColors) = { - implicitly[TPrint[T]].render(config) + implicitly[TPrint[T]].render } } diff --git a/pprint/test/src-2.13/test/pprint/TPrintConstantTests.scala b/pprint/test/src-2.13/test/pprint/TPrintConstantTests.scala new file mode 100644 index 0000000..b3b6c7b --- /dev/null +++ b/pprint/test/src-2.13/test/pprint/TPrintConstantTests.scala @@ -0,0 +1,18 @@ +package test.pprint + +import pprint.{TPrint, TPrintColors} +import utest._ + +object TPrintConstantTests extends TestSuite{ + + val tests = TestSuite{ + test("constant"){ + assert( + implicitly[TPrint[123]].render(TPrintColors.Colors) == fansi.Color.Green("123"), + implicitly[TPrint["xyz"]].render(TPrintColors.Colors) == fansi.Color.Green("\"xyz\""), + implicitly[TPrint[Seq[true]]].render(TPrintColors.Colors) == fansi.Color.Green("Seq") ++ "[" ++ fansi.Color.Green("true") ++ "]" + ) + } + } +} + diff --git a/pprint/test/src-2.13/test/pprint/fields/DerivationTests.scala b/pprint/test/src-2.13/test/pprint/fields/DerivationTests.scala new file mode 100644 index 0000000..7ed924a --- /dev/null +++ b/pprint/test/src-2.13/test/pprint/fields/DerivationTests.scala @@ -0,0 +1,185 @@ +package test.pprint +package fields + + + +import utest._ + +case class CustomToString(){ + override def toString = "LA LA LA" +} +sealed trait Customs +object Customs{ + case class A(i: Int) extends Customs + case class B(s: String) extends Customs{ + override def toString = "Beeee" + } +} +object DerivationTests extends TestSuite{ + + val Check = new Check(fields = true) + + val tests = TestSuite{ + test("singletons"){ + import Singletons._ + Check(Standalone, "Standalone") + Check(BB, "BB") + Check(CC, "CC") + Check(CC: AA, "CC") + } + test("adts"){ + import ADTs._ + Check( + ADTb(123, "hello world"), + """ADTb(i = 123, s = "hello world")""" + ) + + Check( + Seq(ADTb(123, "hello world"), ADTb(-999, "i am cow")), + """List(ADTb(i = 123, s = "hello world"), ADTb(i = -999, s = "i am cow"))""" + ) + + Check(ADT0(), "ADT0()") + } + test("sealedHierarchies"){ + import DeepHierarchy._ + Check( + AnQ(1), + "AnQ(i = 1)" + ) + Check( + AnQ(1): Q, + "AnQ(i = 1)" + ) + Check( + E(false), + "E(b = false)" + ) + Check( + F(AnQ(1)): A, + "F(q = AnQ(i = 1))" + ) + } + test("varargs"){ + import Varargs._ + Check( + Sentence("omg", "2", "3"), + """Sentence(a = "omg", bs = WrappedArray("2", "3"))""", + """Sentence(a = "omg", bs = ArrayBuffer("2", "3"))""", // 2.10 + """Sentence(a = "omg", bs = ArraySeq("2", "3"))""", // 2.13 + """Sentence(a = "omg", bs = WrappedVarArgs("2", "3"))""" // Scala.JS 2.13 + ) + } + test("genericADTs"){ + import GenericADTs._ + Check(DeltaHardcoded.Remove("omg"), """Remove(key = "omg")""") + Check( + Delta.Insert(List("omg", "wtf"), (1, 0.2)), + """Insert(key = List("omg", "wtf"), value = (1, 0.2))""", + """Insert(key = List("omg", "wtf"), value = (1, 0.2F))""", + """Insert(key = List("omg", "wtf"), value = (1, 0.200000))""" + ) + Check( + DeltaInvariant.Clear[Int, String](), + """Clear()""" + ) + Check( + DeltaInvariant.Clear(), + """Clear()""" + ) + + Check( + DeltaHardcoded.Remove(List(1, 2, 3)): DeltaHardcoded[Seq[Int], String], + """Remove(key = List(1, 2, 3))""" + ) + Check( + Delta.Insert(List("omg", "wtf"), (1, 0.2)): Delta[List[String], (Int, Double)], + """Insert(key = List("omg", "wtf"), value = (1, 0.2))""", + """Insert(key = List("omg", "wtf"), value = (1, 0.2F))""", + """Insert(key = List("omg", "wtf"), value = (1, 0.200000))""" + ) + Check( + DeltaInvariant.Clear(): DeltaInvariant[Int, String], + """Clear()""" + ) + + } + + + test("fallback"){ + // make sure we can pprint stuff that looks nothing like a case class + // by falling back to good old toString + import Amorphous._ + val a = new A() + Check(a, a.toString) + Check(a: Any, a.toString) + Check(Seq("lol", 1, 'c'), """List("lol", 1, 'c')""") + Check(("lol", 1, 'c'): AnyRef, """("lol", 1, 'c')""") + + // Even random non-Scala stuff should work + val x = new java.util.Random() + Check(x, x.toString) + val z = new java.util.UUID(0, -1) + Check(z, "00000000-0000-0000-ffff-ffffffffffff") + // Make sure when dealing with composite data structures, we continue + // to use the static versions as deep as we can go before falling back + // to toString + Check( + Generic.ADT(x, x: java.io.Serializable, "lol", "lol": Any, (1.5, 2.5), (1.5, 2.5): AnyRef), + s"""ADT( + | a = $x, + | b = $x, + | c = "lol", + | d = "lol", + | e = (1.5, 2.5), + | f = (1.5, 2.5) + |)""".stripMargin, + s"""ADT( + | a = $x, + | b = $x, + | c = "lol", + | d = "lol", + | e = (1.5F, 2.5F), + | f = (1.5F, 2.5F) + |)""".stripMargin, + s"""ADT( + | a = $x, + | b = $x, + | c = "lol", + | d = "lol", + | e = (1.500000, 2.500000), + | f = (1.500000, 2.500000) + |)""".stripMargin + ) + + } + test("enums"){ + val days1 = pprint.PPrinter.BlackWhite.tokenize( + java.util.concurrent.TimeUnit.DAYS + ).mkString + + val days2 = pprint.PPrinter.BlackWhite.tokenize( + scala.concurrent.duration.SECONDS: java.util.concurrent.TimeUnit + ).mkString + + assert( + days1 == "DAYS", + days2 == "SECONDS" + ) + } + test("issue92"){ + val r = new Issue92.Rational { + override def compare(that: Issue92.Rational): Int = ??? + } + Check(r : Issue92.Rational, r.toString) + } + test("test"){ + Check( + C2(List(C1("hello", List("world")))), + """C2(results = List(C1(name = "hello", types = List("world"))))""" + ) + } + } +} + + diff --git a/pprint/test/src-2.13/test/pprint/fields/VerticalTests.scala b/pprint/test/src-2.13/test/pprint/fields/VerticalTests.scala new file mode 100644 index 0000000..dbb1789 --- /dev/null +++ b/pprint/test/src-2.13/test/pprint/fields/VerticalTests.scala @@ -0,0 +1,468 @@ +package test.pprint +package fields + +import pprint.PPrinter +import utest._ + +import scala.annotation.tailrec + +object VerticalTests extends TestSuite{ + + val tests = TestSuite{ + + + test("Vertical"){ + + + test("emptyNested") { + val Check = new Check(width = 25, renderTwice = true, fields = true) + test - new Check(width = 5)( + List(), + """List( + |)""".stripMargin + ) + } + val Check = new Check(width = 25, renderTwice = true, fields = true) + test("singleNested"){ + test - new Check(width = 5)( + List(1, 2, 3), + """List( + | 1, + | 2, + | 3 + |) + """.stripMargin + ) + test - Check( + List("12", "12", "12"), + """List("12", "12", "12")""" + ) + test - Check( + List("123", "123", "123"), + """List("123", "123", "123")""" + ) + test - Check( + List("1234", "123", "123"), + """List( + | "1234", + | "123", + | "123" + |)""".stripMargin + ) + test - Check( + Map(1 -> 2, 3 -> 4), + """Map(1 -> 2, 3 -> 4)""" + ) + test - Check( + Map(List(1, 2) -> List(3, 4), List(5, 6) -> List(7, 8)), + """Map( + | List(1, 2) -> List(3, 4), + | List(5, 6) -> List(7, 8) + |)""".stripMargin + ) + + test - Check( + Map( + List(123, 456, 789, 123, 456) -> List(3, 4, 3, 4), + List(5, 6) -> List(7, 8) + ), + """Map( + | List( + | 123, + | 456, + | 789, + | 123, + | 456 + | ) -> List(3, 4, 3, 4), + | List(5, 6) -> List(7, 8) + |)""".stripMargin + ) + + test - Check( + Map( + List(5, 6) -> List(7, 8), + List(123, 456, 789, 123, 456) -> List(123, 456, 789, 123, 456) + ), + """Map( + | List(5, 6) -> List(7, 8), + | List( + | 123, + | 456, + | 789, + | 123, + | 456 + | ) -> List( + | 123, + | 456, + | 789, + | 123, + | 456 + | ) + |)""".stripMargin + ) + + test - Check( + List("12345", "12345", "12345"), + """List( + | "12345", + | "12345", + | "12345" + |)""".stripMargin + ) + test - Check( + Foo(123, Seq("hello world", "moo")), + """Foo( + | integer = 123, + | sequence = List( + | "hello world", + | "moo" + | ) + |)""".stripMargin + ) + test - Check( + Foo(123, Seq("moo")), + """Foo( + | integer = 123, + | sequence = List("moo") + |)""".stripMargin + ) + + test("borderlineWithFieldNames"){ + test - Check( + Foo(123, Seq("mooo")), + """Foo( + | integer = 123, + | sequence = List("mooo") + |)""".stripMargin + ) + test - Check( + Foo(123, Seq("moooo")), + """Foo( + | integer = 123, + | sequence = List( + | "moooo" + | ) + |)""".stripMargin + ) + test - Check( + Foo(123, Seq("mooooo")), + """Foo( + | integer = 123, + | sequence = List( + | "mooooo" + | ) + |)""".stripMargin + ) + } + + } + test("doubleNested"){ + + test - Check( + List(Seq("omg", "omg"), Seq("mgg", "mgg"), Seq("ggx", "ggx")), + """List( + | List("omg", "omg"), + | List("mgg", "mgg"), + | List("ggx", "ggx") + |)""".stripMargin + ) + test - Check( + List(Seq("omg", "omg", "omg", "omg"), Seq("mgg", "mgg"), Seq("ggx", "ggx")), + """List( + | List( + | "omg", + | "omg", + | "omg", + | "omg" + | ), + | List("mgg", "mgg"), + | List("ggx", "ggx") + |)""".stripMargin + ) + test - Check( + List( + Seq( + Seq("mgg", "mgg", "lols"), + Seq("mgg", "mgg") + ), + Seq( + Seq("ggx", "ggx"), + Seq("ggx", "ggx", "wtfx") + ) + ), + """List( + | List( + | List( + | "mgg", + | "mgg", + | "lols" + | ), + | List("mgg", "mgg") + | ), + | List( + | List("ggx", "ggx"), + | List( + | "ggx", + | "ggx", + | "wtfx" + | ) + | ) + |)""".stripMargin + ) + test - Check( + FooG(Vector(FooG(Array(Foo(123, Nil)), Nil)), Nil), + """FooG( + | t = Vector( + | FooG( + | t = Array( + | Foo( + | integer = 123, + | sequence = List( + | ) + | ) + | ), + | sequence = List() + | ) + | ), + | sequence = List() + |)""".stripMargin + ) + test - Check( + FooG(FooG(Seq(Foo(3, Nil)), Nil), Nil), + """FooG( + | t = FooG( + | t = List( + | Foo( + | integer = 3, + | sequence = List() + | ) + | ), + | sequence = List() + | ), + | sequence = List() + |)""".stripMargin + ) + test("borderlineWithFieldNames"){ + test - Check( + FooG(FooG(Seq(Foo(3, List(""))), Nil), Nil), + """FooG( + | t = FooG( + | t = List( + | Foo( + | integer = 3, + | sequence = List( + | "" + | ) + | ) + | ), + | sequence = List() + | ), + | sequence = List() + |)""".stripMargin + ) + } + } + } + test("traited"){ + val Check = new Check() + Check(Nested.ODef.Foo(2, "ba"), "Foo(2, \"ba\")") + Check(Nested.CDef.Foo(2, "ba"), "Foo(2, \"ba\")") + } + test("Color"){ + def count(haystack: Iterator[fansi.Str], needles: (String, Int)*) = { + val str = haystack.map(_.render).mkString + for ((needle, expected) <- needles){ + val count = countSubstring(str, needle) + + assert(count == expected) + } + } + def countSubstring(str1:String, str2:String):Int={ + @tailrec def count(pos:Int, c:Int):Int={ + val idx=str1 indexOf(str2, pos) + if(idx == -1) c else count(idx+str2.size, c+1) + } + count(0,0) + } + + import Console._ + val cReset = fansi.Color.Reset.escape + + test - count(PPrinter.Color.tokenize(123), GREEN -> 1, cReset -> 1) + test - count(PPrinter.Color.tokenize(""), GREEN -> 1, cReset -> 1) + test - count(PPrinter.Color.tokenize(Seq(1, 2, 3)), GREEN -> 3, YELLOW -> 1, cReset -> 4) + test - count( + PPrinter.Color.tokenize(Map(1 -> Nil, 2 -> Seq(" "), 3 -> Seq(" "))), + GREEN -> 5, YELLOW -> 4, cReset -> 9 + ) + } + + test("Truncation"){ + test("longNoTruncation"){ + val Check = new Check() + test - Check("a" * 10000,"\""+"a" * 10000+"\"") + test - Check( + List.fill(30)(100), + """List( + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100, + | 100 + |)""".stripMargin + ) + } + + test("shortNonTruncated"){ + val Check = new Check(height = 15) + test - Check("a"*1000, "\"" + "a"*1000 + "\"") + test - Check(List(1,2,3,4), "List(1, 2, 3, 4)") + test - Check( + List.fill(13)("asdfghjklqwertz"), + """List( + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz", + | "asdfghjklqwertz" + |) + """.stripMargin + ) + } + + test("shortLinesTruncated"){ + val Check = new Check(height = 15) + test - Check( + List.fill(15)("foobarbaz"), + """List( + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + |...""".stripMargin + ) + test - Check( + List.fill(150)("foobarbaz"), + """List( + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + | "foobarbaz", + |...""".stripMargin + ) + } + + test("longLineTruncated"){ + // These print out one long line, but at the width that the + // pretty-printer is configured to, it (including any trailing ...) + // wraps to fit within the desired width and height + test{ + val Check = new Check(width = 5, height = 3) + Check( + "a" * 13, + "\"aaaa" + + "aaaaa" + + "aaaa\"" + ) + } + test{ + val Check = new Check(width = 5, height = 3) + Check( + "a" * 1000, + "\"aaaa" + + "aaaaa" + + "..." + ) + } + test{ + val Check = new Check(width = 60, height = 5) + Check( + "a" * 1000, + "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"+ + "..." + ) + } + } + + test("stream"){ + val Check = new Check(height = 5) + Check( + Stream.continually("foo"), + """Stream( + | "foo", + | "foo", + | "foo", + |... + """.stripMargin + ) + } + } + + test("wrappedLines"){ + val Check = new Check(width = 8, height = 5) + + Check( + "1234567890\n"*10, + "\"\"\"1234567890\n1234567890\n..." + ) + // The result looks like 10 wide 3 deep, but because of the wrapping + // (maxWidth = 8) it is actually 8 wide and 5 deep. + } + } + + +} diff --git a/pprint/test/src/test/pprint/HorizontalTests.scala b/pprint/test/src-2/test/pprint/HorizontalTests.scala similarity index 99% rename from pprint/test/src/test/pprint/HorizontalTests.scala rename to pprint/test/src-2/test/pprint/HorizontalTests.scala index 54804bd..23b1262 100644 --- a/pprint/test/src/test/pprint/HorizontalTests.scala +++ b/pprint/test/src-2/test/pprint/HorizontalTests.scala @@ -2,6 +2,7 @@ package test.pprint import utest._ import scala.collection.{immutable => imm, mutable} + object HorizontalTests extends TestSuite{ val Check = new Check(9999) val tests = TestSuite{ diff --git a/pprint/test/src/test/pprint/TPrintTests.scala b/pprint/test/src-2/test/pprint/TPrintTests.scala similarity index 73% rename from pprint/test/src/test/pprint/TPrintTests.scala rename to pprint/test/src-2/test/pprint/TPrintTests.scala index 5c1db99..087399f 100644 --- a/pprint/test/src/test/pprint/TPrintTests.scala +++ b/pprint/test/src-2/test/pprint/TPrintTests.scala @@ -1,6 +1,6 @@ -package pprint +package test.pprint -import pprint.TPrint +import pprint.{TPrint, TPrintColors} import utest._ object TPrintTests extends TestSuite{ @@ -11,15 +11,15 @@ object TPrintTests extends TestSuite{ // type X = scala.Int with scala.Predef.String{} val x = "" - test("plain"){ - def checkVal[T](expected: String, expr: => T)(implicit tprint: TPrint[T]) = { - check[T](expected)(tprint) - } + def checkVal[T](expected: String, expr: => T)(implicit tprint: TPrint[T]) = { + check[T](expected)(tprint) + } - def check[T](expected: String*)(implicit tprint: TPrint[T]) = { - val tprinted = tprint.render - assert(expected.contains(tprinted)) - } + def check[T](expected: String*)(implicit tprint: TPrint[T]) = { + val tprinted = tprint.render.render + assert(expected.contains(tprinted)) + } + test("plain"){ test("simple"){ check[X]("X") check[String]("String") @@ -139,25 +139,11 @@ object TPrintTests extends TestSuite{ } test("annotated"){ // Can't use the normal implicit method, because of SI-8079 - assert(TPrint.default[M@deprecated].render == "M @deprecated") + val rendered = TPrint.default[M@deprecated].render + assert(rendered.toString() == "M @deprecated") } class Custom - test("custom"){ - // Maybe we want to add some extra decoration - implicit def customTPrint: TPrint[Custom] = TPrint.lambda(cfg => "+++Custom+++") - check[Custom]("+++Custom+++") - check[List[Custom]]("List[+++Custom+++]") - - // Or make it look like F# - implicit def StreamTPrint[T: TPrint]: TPrint[Stream[T]] = TPrint.lambda( - c => implicitly[TPrint[T]].render(c) + " Stream" - ) - check[Stream[Int]]("Int Stream") - - // Note how it works recursively - check[Stream[Custom]]("+++Custom+++ Stream") - } test("complex"){ class A @@ -166,8 +152,7 @@ object TPrintTests extends TestSuite{ } check[(A with B)#C]("(A with B)#C") check[({type T = Int})#T]("Int") - implicit def customTPrint: TPrint[Custom] = TPrint.lambda(cfg => "+++Custom+++") - check[(Custom with B)#C]("(+++Custom+++ with B)#C") + check[(Custom with B)#C]("(Custom with B)#C") } test("higherKinded"){ @@ -175,9 +160,9 @@ object TPrintTests extends TestSuite{ check[C[List]]("C[List]") } test("byName"){ - check[(=> Int) => Double]("Function1[=> Int, Double]") + check[(=> Int) => Double]("(=> Int) => Double") check[(=> Int, String, => (=> Char) => Float) => Double]( - "Function3[=> Int, String, => Function1[=> Char, Float], Double]" + "(=> Int, String, => (=> Char) => Float) => Double" ) } test("range"){ @@ -190,7 +175,7 @@ object TPrintTests extends TestSuite{ test("colored"){ import pprint.TPrintColors.Colors._ def checkColor[T](expected: String)(implicit tprint: TPrint[T]) = { - val tprinted = tprint.render.replace( + val tprinted = tprint.render.toString.replace( fansi.Color.Green.escape, "<" ).replace( fansi.Color.Reset.escape, ">" @@ -214,6 +199,58 @@ object TPrintTests extends TestSuite{ "}" ) } + + test("functionGrouping"){ + test("simple")( + check[(Int => Option[Int]) => Int]("(Int => Option[Int]) => Int") + ) + test("lazy")( + check[() => (Int => Option[Int])]("() => Int => Option[Int]") + ) + test("complex")( + check[ + (Int => Option[Int]) => + ((Int => String) => Int) => + (Int => Int) + ]("(Int => Option[Int]) => ((Int => String) => Int) => Int => Int") + ) + } + test("wildcards"){ + case class MyList[T <: String]() + check[MyList[_]]("MyList[_]") + } + test("weirdImplicits"){ + trait Lower { + implicit def monad[M[_],T](i: T): M[T] = ??? + implicit def preventNothing[T](i: T): Nothing = ??? + } + object Higher extends Lower{ + implicit def value[M[_],T](l: M[T]): T = ??? + } + import Higher._ + + check[Int]("Int") + } + test("nothingWithWeirdImport"){ + import scala.reflect.runtime.universe._ + check[Nothing]("Nothing") + } + test("polytype"){ + import scala.language.implicitConversions + type Foo[+A] = Unit + trait Bar[F[_], A] + object Bar { + implicit def anyToBar[A](a: A): Bar[Foo, A] = new Bar[Foo, A] {} + def apply[F[_], A](b: Bar[F, A]): Bar[F, A] = b + } + object Print { + def tprint[A](a: A)(implicit t: pprint.TPrint[A]) = t.render + } + val rendered = Print.tprint(Bar(1)).toString() + + val is213Plus = classOf[Seq[Int]].getName != "scala.collection.Seq" + assert(rendered == (if (is213Plus) "Bar[[A]Foo[A], Int]" else "Bar[Foo, Int]")) + } } +} -} \ No newline at end of file diff --git a/pprint/test/src-3/test/src/pprint/HorizontalTests.scala b/pprint/test/src-3/test/src/pprint/HorizontalTests.scala new file mode 100644 index 0000000..70f4b54 --- /dev/null +++ b/pprint/test/src-3/test/src/pprint/HorizontalTests.scala @@ -0,0 +1,180 @@ +package test.pprint + +import utest._ +import scala.collection.{immutable => imm, mutable} + +object HorizontalTests extends TestSuite{ + + val Check = new Check(100, 9999, false, false) + + val tests = TestSuite{ + test("Horizontal"){ + + test("primitives"){ + test("Unit"){ + test - Check((), "()", "undefined") + } + test("Char"){ + test - Check('\n', "'\\n'") + test - Check('a', "'a'") + } + test("Byte"){ + test - Check(123.toByte, "123") + test - Check(-123.toByte, "-123") + } + test("Short"){ + test - Check(123.toShort, "123") + test - Check(-12345.toShort, "-12345") + } + test("Int"){ + test - Check(123, "123") + test - Check(-1234567, "-1234567") + } + test("Long"){ + test - Check(123456789012345L, "123456789012345L") + test - Check(-123456789012345L, "-123456789012345L") + } + test("Float"){ + test - Check(0.75F, "0.75F", "0.750000F") + test - Check(-13.5F, "-13.5F", "-13.500000F") + } + test("Double"){ + test - Check(0.125, "0.125", "0.125F", "0.125000") + test - Check(-0.125, "-0.125", "-0.125F", "-0.125000") + } + test("String"){ + val tq = "\"\"\"" + test - Check("i am a cow", """ "i am a cow" """) + test - Check( """ "hello" """.trim, """ "\"hello\"" """.trim) + + test - Check("\n", s""" + |$tq + |$tq + """.stripMargin) + test - Check("\n\n\n", s""" + |$tq + | + | + |$tq + """.stripMargin) + val n = 1000 + test - Check( + "\n" + "ABCDEFG" * n, + "\"\"\"\n" + "ABCDEFG" * n + "\"\"\"" + ) + } + } + + test("misc"){ + test("Nothing") - intercept[Exception](Check(throw new Exception(), "")) + test("Null"){ + Check(null, "null") + Check(null: String, "null") + Check(Seq("look!", null: String, "hi"), """List("look!", null, "hi")""") + } + test("Either"){ + Check(Left(123): Either[Int, Int], "Left(123)") + Check(Left(123): Left[Int, Int], "Left(123)") + + Check(Left(123), "Left(123)") + Check(Right((1, "2", 3)), """Right((1, "2", 3))""") + } + test("Options"){ + Check(Some(123), "Some(123)") + Check(None: Option[Int], "None") + Check(None: Option[Nothing], "None") + Check(None, "None") + Check(Some(None), "Some(None)") + } + test("Default"){ + val baos = new java.io.ByteArrayOutputStream() + Check(baos, baos.toString) + + } + } + + test("collections"){ + // Fallback to toString + test("Iterator"){ + Check(Iterator(), "empty iterator", "") + Check(Iterator(1, 2, 3), "non-empty iterator", "") + Check(Option(Iterator(1, 2, 3)), "Some(non-empty iterator)", "Some()") + } + + test("Iterator") - Check(Iterable('1', '2', '3'), "List('1', '2', '3')") + + test("Array") - Check(Array(1, 2, 3), "Array(1, 2, 3)") + test("Seq") - Check(Seq(1, 2, 3), "List(1, 2, 3)") + test("List") - Check(List("1", "2", "3"), """List("1", "2", "3")""") + test("Vector") - Check(Vector("omg", "wtf", "bbq"), """Vector("omg", "wtf", "bbq")""") + + test("Buffer") - Check( + mutable.Buffer("omg", "wtf", "bbq"), + """ArrayBuffer("omg", "wtf", "bbq")""", + """WrappedArray("omg", "wtf", "bbq")""" + ) + + + // Streams are hard-coded to always display vertically, in order + // to make streaming pretty-printing sane + test("LazyList") - Check( + LazyList("omg", "wtf", "bbq"), + """LazyList("omg", "wtf", "bbq")""" + ) + test("Iterable") - Check(Iterable("omg", "wtf", "bbq"), """List("omg", "wtf", "bbq")""") + test("Set") - Check(Set("omg"), """Set("omg")""") + test("mutableSet") - Check(mutable.Set("omg"), """Set("omg")""", """HashSet("omg")""") + test("collectionSet") - Check(collection.Set("omg"), """Set("omg")""") + test("SortedSet") - Check( + imm.SortedSet("1", "2", "3"), + """TreeSet("1", "2", "3")""", + """Set("1", "2", "3")""" + ) + test("Map"){ + Check(Map("key" -> "value"), """Map("key" -> "value")""") + } + test("collectionMap"){ + Check(Map("key" -> "value"): collection.Map[String, String], """Map("key" -> "value")""") + } + + test("mutableMap"){ + Check( + mutable.Map("key" -> "value"), + """Map("key" -> "value")""", + """HashMap("key" -> "value")""" + ) + } + + test("SortedMap") - Check( + imm.SortedMap("key" -> "v", "key2" -> "v2"), + """Map("key" -> "v", "key2" -> "v2")""", + """TreeMap("key" -> "v", "key2" -> "v2")""" + ) + } + + test("tuples"){ + + test("normal"){ + + Check(Tuple1("123"), """Tuple1("123")""") + Check((1, 2, "123"), """(1, 2, "123")""") + Check( + + (1, 2, "123", (100L, 200L), 1.5F, 0.1), + """(1, 2, "123", (100L, 200L), 1.5F, 0.1)""", + """(1, 2, "123", (100L, 200L), 1.5F, 0.1F)""", + """(1, 2, "123", (100L, 200L), 1.500000F, 0.100000)""" + ) + } + test("infix"){ + case class ::(x: Any, y: Any) + Check(::(1, 2), "1 :: 2") + Check(::(0, ::(1, 2)), "0 :: 1 :: 2") + } + } + } + + } + + +} diff --git a/pprint/test/src-3/test/src/pprint/TPrintTests.scala b/pprint/test/src-3/test/src/pprint/TPrintTests.scala new file mode 100644 index 0000000..79e5592 --- /dev/null +++ b/pprint/test/src-3/test/src/pprint/TPrintTests.scala @@ -0,0 +1,170 @@ +package pprint + +import pprint.TPrint +import utest._ + + +object TPrintTests extends TestSuite{ + + class M + + val tests = TestSuite{ + // + type X = scala.Int with scala.Predef.String + val x = "" + test("plain"){ + def checkVal[T](expected: String, expr: => T)(implicit tprint: TPrint[T]) = { + check[T](expected)(using tprint) + } + + def check[T](expected: String*)(implicit tprint: TPrint[T]) = { + val tprinted = tprint.render + assert(expected.contains(tprinted.render)) + } + + test("simple"){ + + // check[X]("X") + check[String]("String") + check[java.lang.String]("String") + check[Int]("Int") + + + val a = List(1,2,3).tail + checkVal("List[Int]", a) + + check[scala.Int]("Int") + def t[T] = check[T]("T") + t + } + + test("nothing"){ + test - check[Nothing]("Nothing") + //Inferred nothings behave weirdly, make sure it works! + test - check("Nothing") + test - checkVal("Nothing", throw new Exception()) + test - checkVal("Some[Nothing]", Some(???)) + } + + test("singleton"){ + check[x.type]("x.type") + check[TPrintTests.this.M]("M") + // check[TPrintTests.type]("TPrintTests.type") + } + + test("java"){ + //check[java.util.Set[_]]("java.util.Set[_]") + // check[java.util.Set[_ <: Int]]("java.util.Set[_]") + // check[java.util.Set[_ <: String]]("java.util.Set[_] forSome { type _ <: String }") + // check[java.util.Set[_ <: String]]("java.util.Set[_] forSome { type _ <: String }") + // check[java.util.Set[String]]("java.util.Set[String]") + } + + test("mutable"){ + + // check[collection.mutable.Buffer[Int]]("collection.mutable.Buffer[Int]") + import collection.mutable + // TPrint.default[mutable.Buffer[Int]] + //check[mutable.Buffer[Int]]("mutable.Buffer[Int]") + check[Seq[Int]]("Seq[Int]") + + // can't use scala.util.Properties on Scala.JS + //val is213Plus = classOf[Seq[Int]].getName != "scala.collection.Seq" + // check[collection.Seq[Int]](if (is213Plus) "collection.Seq[Int]" else "Seq[Int]") + // check[collection.immutable.Seq[Int]](if (is213Plus) "Seq[Int]" else "collection.immutable.Seq[Int]") + + } + test("compound"){ + check[Map[Int, List[String]]]("Map[Int, List[String]]") + check[Int => String]("Int => String") + check[(Int, Float) => String]("(Int, Float) => String") + check[(Int, Float, Double, Char, Byte, Short, Long) => String]( + "(Int, Float, Double, Char, Byte, Short, Long) => String" + ) + check[(Int, Float) => (String, String)]("(Int, Float) => (String, String)") + check[(Int, String)]("(Int, String)") + check[(Int, String, (Int, String), Double)]("(Int, String, (Int, String), Double)") + //check[Int {val x: Int}]("Int{val x: Int}") + //check[Int & String]("Int with String") + } + test("existential") { + //TODO: Implicit doesn’t reolve + // check[{type T = Int}]("{type T = Int}") + + check[Map[_, _]]("Map[_, _]") + } + + + // test("thisType"){ + // class T { + // check[T.this.type]("T.this.type") + // } + // new T() + // } + // test("annotated"){ + // // Can't use the normal implicit method, because of SI-8079 + // assert(TPrint.default[M@deprecated].render == "M @deprecated") + // } + + class Custom + + // test("complex"){ + // class A + // class B{ + // class C + // } + // check[(A with B)#C]("(A with B)#C") + // check[({type T = Int})#T]("Int") + // implicit def customTPrint: TPrint[Custom] = TPrint.lambda(cfg => "+++Custom+++") + // check[(Custom with B)#C]("(+++Custom+++ with B)#C") + + // } + test("higherKinded"){ + + //TODO: No idea why this breaks + //class C[T[_]] + //check[C[List]]("C[List]")(TPrint.default[C[List]]) + } + // test("byName"){ + // check[(=> Int) => Double]("Function1[=> Int, Double]") + // check[(=> Int, String, => (=> Char) => Float) => Double]( + // "Function3[=> Int, String, => Function1[=> Char, Float], Double]" + // ) + // } + // test("range"){ + // check[Range]("Range") + // checkVal("Range.Inclusive", 0 to 10) + // checkVal("Range", 0 until 10) + // check[Range.Inclusive]("Range.Inclusive") + // } + // } + // test("colored"){ + // import pprint.TPrintColors.Colors._ + // def checkColor[T](expected: String)(implicit tprint: TPrint[T]) = { + // val tprinted = tprint.render.replace( + // fansi.Color.Green.escape, "<" + // ).replace( + // fansi.Color.Reset.escape, ">" + // ) + // assert(tprinted == expected) + // } + + // test - checkColor[String]("") + // test - checkColor[Map[Int, String]]("[, ]") + // test - checkColor[collection.mutable.Seq[Int]]("..[]") + // // Not going to bother coloring these for now, since they're quite uncommon + // // test - checkColor[{type T = Int; val x: String; def y: Char; var z: Double}]( + // // "{type = ; val : ; def : ; var : }" + // // ) + // // test - checkColor[Map[K, V] forSome { + // // type K <: Int; val x: Float; type V >: (String, Float with Double) + // // }]( + // // "[, ] forSome { " + + // // "type <: ; val : ; " + + // // "type >: (, with ) " + + // // "}" + // // ) + } + } + +} diff --git a/pprint/test/src/test/pprint/AdvancedTests.scala b/pprint/test/src/test/pprint/AdvancedTests.scala index 83f11b9..47aaac6 100644 --- a/pprint/test/src/test/pprint/AdvancedTests.scala +++ b/pprint/test/src/test/pprint/AdvancedTests.scala @@ -1,6 +1,7 @@ package test.pprint import utest._ +import pprint.PPrinter import scala.collection.SortedMap @@ -21,7 +22,7 @@ object AdvancedTests extends TestSuite{ val tests = TestSuite{ test("applyPrefixWidthExactlyMaxWidth"){ case class Foo(is: List[Int]) - val rendered = pprint.apply( + val rendered = Check.color.apply( Foo(List(1)), width = 10 ) @@ -184,6 +185,37 @@ object AdvancedTests extends TestSuite{ } } } + + test("unicode"){ + val withEscaping = new PPrinter(defaultEscapeUnicode = true) + val withoutEscaping = new PPrinter(defaultEscapeUnicode = false) + + val toCheck = List("foo", "йцук", "漢字") + + val withEscapingRes = withEscaping.apply(toCheck).plainText + val withoutEscapingRes = withoutEscaping.apply(toCheck).plainText + + assert(withEscapingRes == "List(\"foo\", \"\\u0439\\u0446\\u0443\\u043a\", \"\\u6f22\\u5b57\")") + assert(withoutEscapingRes == """List("foo", "йцук", "漢字")""") + } + } + + test("toStringReturnsNull"){ + class ClassWithNullToString { + override def toString = null + } + val rendered = Check.color(new ClassWithNullToString) + assert(rendered.plainText == "null") + + } + test("customProduct2"){ + final class Vec2 (val x: Double, val y: Double) extends Product2[Double, Double] { + def _1 = x + def _2 = y + def canEqual(that: Any) = that.isInstanceOf[Vec2] + } + val rendered = Check.color(new Vec2(13, 37)) + rendered } } diff --git a/pprint/test/src/test/pprint/Check.scala b/pprint/test/src/test/pprint/Check.scala index 65ca984..66d6999 100644 --- a/pprint/test/src/test/pprint/Check.scala +++ b/pprint/test/src/test/pprint/Check.scala @@ -1,16 +1,23 @@ package test.pprint import pprint.PPrinter -class Check(width: Int = 100, height: Int = 99999, renderTwice: Boolean = false){ +object Check{ + val blackWhite = new PPrinter(defaultShowFieldNames = false) + val color = new PPrinter(defaultShowFieldNames = false) + val blackWhiteFields = new PPrinter() + val colorFields = new PPrinter() +} +class Check(width: Int = 100, height: Int = 99999, renderTwice: Boolean = false, fields: Boolean = false){ def apply(t: Any, expected: String*) = { + + val blackWhite = if (fields) Check.blackWhiteFields else Check.blackWhite + val color = if (fields) Check.colorFields else Check.color val printers = - if (!renderTwice) Seq(PPrinter.BlackWhite) - else Seq(PPrinter.BlackWhite, PPrinter.Color) + if (!renderTwice) Seq(blackWhite) + else Seq(blackWhite, color) // Make sure we for (pprinter <- printers){ - val pprinted = fansi.Str.join( - PPrinter.BlackWhite.tokenize(t, width, height).toStream:_* - ).plainText + val pprinted = fansi.Str.join(blackWhite.tokenize(t, width, height).toStream).plainText utest.assert(expected.map(_.trim).contains(pprinted)) } diff --git a/pprint/test/src/test/pprint/ClassDefs.scala b/pprint/test/src/test/pprint/ClassDefs.scala index 5c3c2ae..ba51782 100644 --- a/pprint/test/src/test/pprint/ClassDefs.scala +++ b/pprint/test/src/test/pprint/ClassDefs.scala @@ -173,6 +173,14 @@ case class Result2(name : String, case class GeoCoding2(results : List[Result2], status: String) +object Issue28{ + class MyProduct2 extends Product2[String, Int] { + override def _1: String = "asdf" + override def _2: Int = 333 + override def canEqual(that: Any): Boolean = false + } +} + object Issue94{ class Foo(val x: String){ override def toString = x @@ -269,4 +277,4 @@ object PythonAst{ } -} \ No newline at end of file +} diff --git a/pprint/test/src/test/pprint/DerivationTests.scala b/pprint/test/src/test/pprint/DerivationTests.scala index fc9215d..ff98d07 100644 --- a/pprint/test/src/test/pprint/DerivationTests.scala +++ b/pprint/test/src/test/pprint/DerivationTests.scala @@ -16,7 +16,7 @@ object Customs{ } object DerivationTests extends TestSuite{ - val Check = new Check() + val Check = new Check(100, 99999, false, false) val tests = TestSuite{ test("singletons"){ @@ -152,6 +152,10 @@ object DerivationTests extends TestSuite{ days2 == "SECONDS" ) } + test("issue28"){ + val r = new Issue28.MyProduct2 + Check(r : Issue28.MyProduct2, """("asdf", 333)""") + } test("issue92"){ val r = new Issue92.Rational { override def compare(that: Issue92.Rational): Int = ??? @@ -166,5 +170,3 @@ object DerivationTests extends TestSuite{ } } } - - diff --git a/pprint/test/src/test/pprint/UnitTests.scala b/pprint/test/src/test/pprint/UnitTests.scala index 8e4d4f9..c79c624 100644 --- a/pprint/test/src/test/pprint/UnitTests.scala +++ b/pprint/test/src/test/pprint/UnitTests.scala @@ -9,9 +9,8 @@ object UnitTests extends TestSuite{ val tests = TestSuite{ test("escapeChar"){ - def check(c: Char, expected: String) = { - - val escaped = pprint.Util.escapeChar(c, new StringBuilder).toString + def check(c: Char, expected: String, unicode: Boolean = true) = { + val escaped = pprint.Util.escapeChar(c, new StringBuilder, unicode).toString assert(escaped == expected) } check('a', "a") @@ -19,6 +18,8 @@ object UnitTests extends TestSuite{ check('\n', "\\n") check('\\', "\\\\") check('\t', "\\t") + check('й', "\\u0439", true) + check('й', "й", false) } test("literalize"){ val simple = pprint.Util.literalize("hi i am a cow") @@ -28,6 +29,16 @@ object UnitTests extends TestSuite{ val escaped = pprint.Util.literalize("hi i am a \"cow\"") val escapedExpected = """ "hi i am a \"cow\"" """.trim assert(escaped == escapedExpected) + + val withUnicodeStr = "with юникод" + + val withUnicodeEscaped = pprint.Util.literalize(withUnicodeStr, true) + val withUnicodeEscapedExpected = "\"with \\u044e\\u043d\\u0438\\u043a\\u043e\\u0434\"" + assert(withUnicodeEscaped == withUnicodeEscapedExpected) + + val withUnicodeUnescaped = pprint.Util.literalize(withUnicodeStr, false) + val withUnicodeUnescapedExpected = """ "with юникод" """.trim + assert(withUnicodeUnescaped == withUnicodeUnescapedExpected) } test("concatIter"){ diff --git a/pprint/test/src/test/pprint/VerticalTests.scala b/pprint/test/src/test/pprint/VerticalTests.scala index ac3f5e9..6f222d2 100644 --- a/pprint/test/src/test/pprint/VerticalTests.scala +++ b/pprint/test/src/test/pprint/VerticalTests.scala @@ -12,9 +12,9 @@ object VerticalTests extends TestSuite{ test("Vertical"){ - val Check = new Check(width = 25, renderTwice = true) + val Check = new Check(width = 25, height = 99999, renderTwice = true, fields = false) test("singleNested"){ - test - new Check(width = 5)( + test - new Check(width = 5, height = 99999, renderTwice = false, fields = false)( List(1, 2, 3), """List( | 1, diff --git a/project/Constants.scala b/project/Constants.scala index d95c643..ad96fc4 100644 --- a/project/Constants.scala +++ b/project/Constants.scala @@ -1,5 +1,5 @@ package pprint object Constants { - val version = "0.5.7" + val version = "0.7.1" } diff --git a/readme/Readme.scalatex b/readme/Readme.scalatex index 35dbf19..2388576 100644 --- a/readme/Readme.scalatex +++ b/readme/Readme.scalatex @@ -23,7 +23,7 @@ ga('send', 'pageview'); -@sect{PPrint} +@sect{PPrint @pprint.Constants.version} @img( src := "Example.png", alt := "Example use case of PPrint", @@ -55,13 +55,16 @@ @sect{Getting Started} @p - Add the following to your SBT config: + Add the following to your build config: @hl.scala + // SBT libraryDependencies += "com.lihaoyi" %% "pprint" % "@pprint.Constants.version" - @p - Or for Scala.js/Scala-Native: - @hl.scala - libraryDependencies += "com.lihaoyi" %%% "pprint" % "@pprint.Constants.version" + libraryDependencies += "com.lihaoyi" %%% "pprint" % "@pprint.Constants.version" // Scala.js/Native + + // Mill + ivy"com.lihaoyi::pprint:@pprint.Constants.version" + ivy"com.lihaoyi::pprint::@pprint.Constants.version" // Scala.js/Native + @p The above example then showed how to use the default pprint configuration. You can also set up your own custom implicit `pprint.Config` if you want to control e.g. colors, width, or max-height of the output. @p @@ -255,6 +258,33 @@ display.block ) @sect{Version History} + @sect{0.7.3} + @ul + @li + Add support for Polytypes @lnk("#79", "https://github.com/com-lihaoyi/PPrint/pull/79") + @sect{0.7.2} + @ul + @li + Support Scala Native on Scala 3 @lnk("#75", "https://github.com/com-lihaoyi/PPrint/pull/77") + @sect{0.7.1} + @ul + @li + Fixes for pprinting and tprinting literal constants @lnk("#75", "https://github.com/com-lihaoyi/PPrint/pull/75") + @sect{0.7.0} + @ul + @li + Overhaul and simplify TPrint implementation @lnk("#72", "https://github.com/com-lihaoyi/PPrint/pull/72") + @li + Fix PPrint for Product2 @lnk("#36", "https://github.com/com-lihaoyi/PPrint/pull/36") + @li + Make PPrint robust against @code{toString} returning @code{null} @lnk("#70", "https://github.com/com-lihaoyi/PPrint/pull/70") + @li + Add flag to control unicode escaping @lnk("#71", "https://github.com/com-lihaoyi/PPrint/pull/71") + @sect{0.6.0} + @ul + @li + Add support for field names when printing case classes in Scala 2.13 + @sect{0.5.6} @ul @li