|
| 1 | +/* |
| 2 | + * Copyright 2001-2014 Artima, Inc. |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | +package org.scalactic |
| 17 | + |
| 18 | +import scala.quoted._ |
| 19 | +import scala.tasty._ |
| 20 | + |
| 21 | +/** |
| 22 | + * Case class that stores the name and value of a variable or expression. |
| 23 | + * |
| 24 | + * <p> |
| 25 | + * See the main documentation for trait <a href="Snapshots.html"><code>Snapshots</code></a> for more information and examples. |
| 26 | + * </p> |
| 27 | + * |
| 28 | + * @param name the name of the expression |
| 29 | + * @param value the value of the expression |
| 30 | + */ |
| 31 | +final case class Snapshot(name: String, value: Any) { |
| 32 | + |
| 33 | + /** |
| 34 | + * Overriden <code>toString</code> to print in {name} = {value} format. |
| 35 | + * |
| 36 | + * @return string in {name} = {value} format |
| 37 | + */ |
| 38 | + override def toString: String = Resources.variableWasValue(name, Prettifier.default(value)) |
| 39 | +} |
| 40 | + |
| 41 | +/** |
| 42 | + * <p>Trait that provides a <code>snap</code> method that takes one or more arguments and results in a |
| 43 | + * <a href="SnapshotSeq.html"><code>SnapshotSeq</code></a>, whose <code>toString</code> lists the names |
| 44 | + * and values of each argument. |
| 45 | + * |
| 46 | + * <p> |
| 47 | + * The intended use case of this trait is to help you write debug and log |
| 48 | + * messages that give a "snapshot" of program state. Here's an example: |
| 49 | + * </p> |
| 50 | + * |
| 51 | + * <pre class="stREPL"> |
| 52 | + * scala> import Snapshots._ |
| 53 | + * import Snapshots._ |
| 54 | + * |
| 55 | + * scala> snap(a, b, c, d, e, f) |
| 56 | + * res3: org.scalactic.SnapshotSeq = a was 1, b was 2, c was 3, d was 4, e was null, f was null |
| 57 | + * </pre> |
| 58 | + * |
| 59 | + * <p><code>SnapshotSeq</code> offers a <code>lines</code> method that places each variable name/value pair on its own line:<p> |
| 60 | + * |
| 61 | + * <pre class="stREPL"> |
| 62 | + * scala> snap(a, b, c, d, e, f).lines |
| 63 | + * res4: String = |
| 64 | + * a was 1 |
| 65 | + * b was 2 |
| 66 | + * c was 3 |
| 67 | + * d was 4 |
| 68 | + * e was null |
| 69 | + * f was null |
| 70 | + * </pre> |
| 71 | + * |
| 72 | + * <p> |
| 73 | + * Or, because a <code>SnapshotSeq</code> is a <code>IndexedSeq[Snapshot]</code>, you can process it just like any other <code>Seq</code>, for example: |
| 74 | + * </p> |
| 75 | + * |
| 76 | + * <pre class="stREPL"> |
| 77 | + * scala> snap(a, b, c, d, e, f).mkString("Wow! ", ", and ", ". That's so awesome!") |
| 78 | + * res6: String = Wow! a was 1, and b was 2, and c was 3, and d was 4, and e was null, and f was null. That's so awesome! |
| 79 | + * </pre> |
| 80 | + */ |
| 81 | +trait Snapshots { |
| 82 | + |
| 83 | + /** |
| 84 | + * Snap the given expressions. |
| 85 | + * |
| 86 | + * @param expressions expressions to be snapped |
| 87 | + * @return an <code>IndexedSeq</code> of <code>Snapshot</code> for the given expressions. |
| 88 | + */ |
| 89 | + inline def snap(expressions: Any*): SnapshotSeq = ${ SnapshotsMacro.snap('expressions) } |
| 90 | +} |
| 91 | + |
| 92 | +/** |
| 93 | + * An <code>IndexedSeq[Snapshot]</code> providing <code>toString</code> and <code>lines</code> methods that |
| 94 | + * can be useful for debug and log messages about program state. |
| 95 | + * |
| 96 | + * <p> |
| 97 | + * See the main documentation for trait <a href="Snapshots.html"><code>Snapshots</code></a> for more information and examples. |
| 98 | + * </p> |
| 99 | + */ |
| 100 | +final class SnapshotSeq(underlying: collection.immutable.IndexedSeq[Snapshot]) extends collection.immutable.IndexedSeq[Snapshot] { |
| 101 | + |
| 102 | + /** |
| 103 | + * Selects an element by its index in the sequence. |
| 104 | + * |
| 105 | + * <p> |
| 106 | + * This method invokes <code>apply</code> on the underlying immutable <code>IndexedSeq[String]</code>, passing in <code>idx</code>, and returns the result. |
| 107 | + * </p> |
| 108 | + * |
| 109 | + * @param idx the index to select |
| 110 | + * @return the element of this sequence at index <code>idx</code>, where 0 indicates the first element |
| 111 | + */ |
| 112 | + def apply(idx: Int): Snapshot = underlying.apply(idx) |
| 113 | + |
| 114 | + /** |
| 115 | + * The length of this sequence. |
| 116 | + * |
| 117 | + * <p> |
| 118 | + * This method invokes <code>length</code> on the underlying immutable <code>IndexedSeq[String]</code> and returns the result. |
| 119 | + * </p> |
| 120 | + * |
| 121 | + * @return the number of elements in this sequence |
| 122 | + */ |
| 123 | + def length: Int = underlying.length |
| 124 | + |
| 125 | + /** |
| 126 | + * Appends a string element to this sequence, if it doesn't already exist in the sequence. |
| 127 | + * |
| 128 | + * <p> |
| 129 | + * If the string element already exists in this sequence, this method returns itself. If not, |
| 130 | + * this method returns a new <code>MultiSelOptionSeq</code> with the passed value appended to the |
| 131 | + * end of the original <code>MultiSelOptionSeq</code>. |
| 132 | + * </p> |
| 133 | + * |
| 134 | + * @param the string element to append to this sequence |
| 135 | + * @return a <code>MultiSelOptionSeq</code> that contains the passed string value |
| 136 | + */ |
| 137 | + def +(value: Snapshot): SnapshotSeq = { |
| 138 | + if (!underlying.contains(value)) |
| 139 | + new SnapshotSeq(underlying :+ value) |
| 140 | + else |
| 141 | + this |
| 142 | + } |
| 143 | + |
| 144 | + /** |
| 145 | + * Removes a string element to this sequence, if it already exists in the sequence. |
| 146 | + * |
| 147 | + * <p> |
| 148 | + * If the string element does not already exist in this sequence, this method returns itself. If the element |
| 149 | + * is contained in this sequence, this method returns a new <code>MultiSelOptionSeq</code> with the passed value |
| 150 | + * removed from the the original <code>MultiSelOptionSeq</code>, leaving any other elements in the same order. |
| 151 | + * </p> |
| 152 | + * |
| 153 | + * @param the string element to append to this sequence |
| 154 | + * @return a <code>MultiSelOptionSeq</code> that contains the passed string value |
| 155 | + */ |
| 156 | + def -(value: Snapshot): SnapshotSeq = { |
| 157 | + if (underlying.contains(value)) |
| 158 | + new SnapshotSeq(underlying.filter(_ != value)) |
| 159 | + else |
| 160 | + this |
| 161 | + } |
| 162 | + |
| 163 | + /** |
| 164 | + * The default way to present the result of the <code>snap</code> method of trait </code>Snapshots</code>. |
| 165 | + * |
| 166 | + * Here's an example: |
| 167 | + * |
| 168 | + * <pre class="stREPL"> |
| 169 | + * scala> snap(a, b, c, d, e, f) |
| 170 | + * res3: org.scalactic.SnapshotSeq = a was 1, b was 2, c was 3, d was 4, e was null, f was null |
| 171 | + * </pre> |
| 172 | + */ |
| 173 | + override def toString: String = mkString(", ") |
| 174 | + |
| 175 | + /** |
| 176 | + * An alternate way to present the result of the <code>snap</code> method of trait </code>Snapshots</code> that |
| 177 | + * puts each variable or expression on its own line. |
| 178 | + * |
| 179 | + * Here's an example: |
| 180 | + * |
| 181 | + * <pre class="stREPL"> |
| 182 | + * scala> snap(a, b, c, d, e, f).lines |
| 183 | + * res4: String = |
| 184 | + * a was 1 |
| 185 | + * b was 2 |
| 186 | + * c was 3 |
| 187 | + * d was 4 |
| 188 | + * e was null |
| 189 | + * f was null |
| 190 | + * </pre> |
| 191 | + */ |
| 192 | + def lines: String = mkString("\n") |
| 193 | +} |
| 194 | + |
| 195 | +object SnapshotSeq { |
| 196 | + def apply(snapshots: Snapshot*): SnapshotSeq = new SnapshotSeq(Vector(snapshots: _*)) |
| 197 | +} |
| 198 | + |
| 199 | +/** |
| 200 | + * Companion object that facilitates the importing of <code>Snapshots</code> members as |
| 201 | + * an alternative to mixing it in. One use case is to import <code>Snapshots</code> members so you can use |
| 202 | + * them in the Scala interpreter: |
| 203 | + * |
| 204 | + * <pre class="stREPL"> |
| 205 | + * $scala -classpath scalatest.jar |
| 206 | + * Welcome to Scala version 2.10.3.final (Java HotSpot(TM) Client VM, Java xxxxxx). |
| 207 | + * Type in expressions to have them evaluated. |
| 208 | + * Type :help for more information. |
| 209 | + * |
| 210 | + * scala> import org.scalactic.Snapshots._ |
| 211 | + * import org.scalatest.Snapshots._ |
| 212 | + * |
| 213 | + * scala> val a = 8 |
| 214 | + * a: Int = 8 |
| 215 | + * |
| 216 | + * scala> snap(a) |
| 217 | + * res0: scala.collection.immutable.Vector[org.scalactic.Snapshot] = Vector(a = 8) |
| 218 | + * </pre> |
| 219 | + */ |
| 220 | +object Snapshots extends Snapshots |
| 221 | + |
| 222 | +object SnapshotsMacro { |
| 223 | + |
| 224 | + def snap(expressions: Expr[Seq[Any]])(implicit refl: Reflection): Expr[SnapshotSeq] = { |
| 225 | + import refl._ |
| 226 | + // TODO: remove once `Expr[T].show` handles color correctly |
| 227 | + def (str: String) clean: String = str.replaceAll("\u001B\\[[;\\d]*m", "") |
| 228 | + |
| 229 | + def liftSeq(args: Seq[Expr[Snapshot]]): Expr[Seq[Snapshot]] = args match { |
| 230 | + case x :: xs => '{ ($x) +: ${ liftSeq(xs) } } |
| 231 | + case Nil => '{ Seq(): Seq[Snapshot] } |
| 232 | + } |
| 233 | + |
| 234 | + val snapshots: List[Expr[Snapshot]] = expressions.unseal.underlyingArgument match { |
| 235 | + case Typed(Repeated(args, _), _) => // only sequence literal |
| 236 | + args.map { arg => |
| 237 | + val str = arg.seal.cast[Any].show.clean.toExpr |
| 238 | + '{ Snapshot($str, ${ arg.seal.cast[Any] }) } |
| 239 | + } |
| 240 | + case arg => |
| 241 | + throw QuoteError("snap can only be used with sequence literal, not `seq : _*`") |
| 242 | + } |
| 243 | + |
| 244 | + val argumentsS: Expr[Seq[Snapshot]] = liftSeq(snapshots) |
| 245 | + '{ SnapshotSeq($argumentsS : _*) } |
| 246 | + } |
| 247 | +} |
0 commit comments