|
1 | 1 | package mill.api.internal |
2 | 2 |
|
3 | | -case class Located[T](path: os.Path, index: Int, value: T) |
| 3 | +case class Located[T](path: os.Path, index: Int, value: T, append: Boolean = false) |
4 | 4 |
|
5 | 5 | object Located { |
| 6 | + import upickle.core.BufferedValue |
| 7 | + |
| 8 | + /** Marker key indicating append mode in YAML `!append` tag wrapper objects */ |
| 9 | + val AppendMarkerKey = "__mill_append__" |
| 10 | + /** Marker key containing the actual values in YAML `!append` tag wrapper objects */ |
| 11 | + val ValuesMarkerKey = "__mill_values__" |
| 12 | + |
| 13 | + /** |
| 14 | + * Extract append flag and actual value from a BufferedValue that may contain |
| 15 | + * the marker object (produced by YAML `!append` tag parsing). |
| 16 | + * Returns (actualValue, appendFlag). |
| 17 | + */ |
| 18 | + def unwrapAppendMarker(v: BufferedValue): (BufferedValue, Boolean) = { |
| 19 | + v match { |
| 20 | + case obj: BufferedValue.Obj => |
| 21 | + val kvMap = obj.value0.collect { case (BufferedValue.Str(k, _), v) => |
| 22 | + k.toString -> v |
| 23 | + }.toMap |
| 24 | + (kvMap.get(AppendMarkerKey), kvMap.get(ValuesMarkerKey)) match { |
| 25 | + case (Some(BufferedValue.True(_)), Some(values)) => (values, true) |
| 26 | + case _ => (v, false) |
| 27 | + } |
| 28 | + case _ => (v, false) |
| 29 | + } |
| 30 | + } |
| 31 | + |
6 | 32 | class UpickleReader[T](path: os.Path)(implicit r: upickle.Reader[T]) |
7 | 33 | extends upickle.Reader[Located[T]] { |
8 | | - private def wrap(index: Int, v: T): Located[T] = Located(path, index, v) |
| 34 | + private def wrap(index: Int, v: T, append: Boolean = false): Located[T] = |
| 35 | + Located(path, index, v, append) |
9 | 36 |
|
10 | 37 | def visitArray(length: Int, index: Int): upickle.core.ArrVisitor[Any, Located[T]] = { |
11 | 38 | val delegate = r.visitArray(length, index) |
@@ -46,4 +73,88 @@ object Located { |
46 | 73 | def visitExt(tag: Byte, bytes: Array[Byte], offset: Int, len: Int, index: Int) = |
47 | 74 | wrap(index, r.visitExt(tag, bytes, offset, len, index)) |
48 | 75 | } |
| 76 | + |
| 77 | + /** |
| 78 | + * Upickle reader that detects the `!append` marker object |
| 79 | + * and extracts the append flag along with the actual value. |
| 80 | + */ |
| 81 | + class AppendUpickleReader[T](path: os.Path)(implicit r: upickle.Reader[T]) |
| 82 | + extends upickle.Reader[Located[T]] { |
| 83 | + private def wrap(index: Int, v: T, append: Boolean): Located[T] = |
| 84 | + Located(path, index, v, append) |
| 85 | + |
| 86 | + // For arrays without !append marker - just wrap with append=false |
| 87 | + def visitArray(length: Int, index: Int): upickle.core.ArrVisitor[Any, Located[T]] = { |
| 88 | + val delegate = r.visitArray(length, index) |
| 89 | + new upickle.core.ArrVisitor[Any, Located[T]] { |
| 90 | + def subVisitor = delegate.subVisitor |
| 91 | + def visitValue(v: Any, index: Int): Unit = delegate.visitValue(v, index) |
| 92 | + def visitEnd(idx: Int) = wrap(index, delegate.visitEnd(idx), append = false) |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + // For objects - check if it's an !append marker object |
| 97 | + def visitObject(length: Int, jsonableKeys: Boolean, index: Int) |
| 98 | + : upickle.core.ObjVisitor[Any, Located[T]] = { |
| 99 | + new upickle.core.ObjVisitor[Any, Located[T]] { |
| 100 | + private var currentKey: String = "" |
| 101 | + private var hasAppendMarker = false |
| 102 | + private var valuesResult: Option[T] = None |
| 103 | + private val startIndex = index |
| 104 | + |
| 105 | + // Delegate for parsing the inner values array |
| 106 | + private val valuesReader = r |
| 107 | + |
| 108 | + def subVisitor: upickle.core.Visitor[?, ?] = currentKey match { |
| 109 | + case AppendMarkerKey => upickle.core.NoOpVisitor |
| 110 | + case ValuesMarkerKey => valuesReader |
| 111 | + case _ => upickle.core.NoOpVisitor |
| 112 | + } |
| 113 | + |
| 114 | + def visitKey(index: Int): upickle.core.Visitor[?, ?] = upickle.core.StringVisitor |
| 115 | + |
| 116 | + def visitKeyValue(s: Any): Unit = { |
| 117 | + currentKey = s.toString |
| 118 | + } |
| 119 | + |
| 120 | + def visitValue(v: Any, index: Int): Unit = { |
| 121 | + currentKey match { |
| 122 | + case AppendMarkerKey => hasAppendMarker = true |
| 123 | + case ValuesMarkerKey => valuesResult = Some(v.asInstanceOf[T]) |
| 124 | + case _ => () |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + def visitEnd(idx: Int): Located[T] = { |
| 129 | + if (hasAppendMarker && valuesResult.isDefined) { |
| 130 | + wrap(startIndex, valuesResult.get, append = true) |
| 131 | + } else { |
| 132 | + // Not an append marker object - this shouldn't happen for moduleDeps |
| 133 | + // but fall back to empty value |
| 134 | + wrap(startIndex, r.visitArray(0, startIndex).visitEnd(startIndex), append = false) |
| 135 | + } |
| 136 | + } |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + def visitNull(index: Int) = wrap(index, r.visitNull(index), append = false) |
| 141 | + def visitFalse(index: Int) = wrap(index, r.visitFalse(index), append = false) |
| 142 | + def visitTrue(index: Int) = wrap(index, r.visitTrue(index), append = false) |
| 143 | + def visitFloat64StringParts(s: CharSequence, decIndex: Int, expIndex: Int, index: Int) = |
| 144 | + wrap(index, r.visitFloat64StringParts(s, decIndex, expIndex, index), append = false) |
| 145 | + def visitFloat64(d: Double, index: Int) = wrap(index, r.visitFloat64(d, index), append = false) |
| 146 | + def visitFloat32(d: Float, index: Int) = wrap(index, r.visitFloat32(d, index), append = false) |
| 147 | + def visitInt32(i: Int, index: Int) = wrap(index, r.visitInt32(i, index), append = false) |
| 148 | + def visitInt64(i: Long, index: Int) = wrap(index, r.visitInt64(i, index), append = false) |
| 149 | + def visitUInt64(i: Long, index: Int) = wrap(index, r.visitUInt64(i, index), append = false) |
| 150 | + def visitFloat64String(s: String, index: Int) = |
| 151 | + wrap(index, r.visitFloat64String(s, index), append = false) |
| 152 | + def visitString(s: CharSequence, index: Int) = |
| 153 | + wrap(index, r.visitString(s, index), append = false) |
| 154 | + def visitChar(s: Char, index: Int) = wrap(index, r.visitChar(s, index), append = false) |
| 155 | + def visitBinary(bytes: Array[Byte], offset: Int, len: Int, index: Int) = |
| 156 | + wrap(index, r.visitBinary(bytes, offset, len, index), append = false) |
| 157 | + def visitExt(tag: Byte, bytes: Array[Byte], offset: Int, len: Int, index: Int) = |
| 158 | + wrap(index, r.visitExt(tag, bytes, offset, len, index), append = false) |
| 159 | + } |
49 | 160 | } |
0 commit comments