Skip to content

Commit 8b06920

Browse files
Import JavaOpt / ScalacOpt from Scala CLI
1 parent c67672a commit 8b06920

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package scala.cli.directivehandler
2+
3+
final case class JavaOpt(value: String) {
4+
def key: Option[String] =
5+
JavaOpt.optionPrefixes.find(value.startsWith)
6+
.orElse {
7+
if (value.startsWith("-"))
8+
Some(value.takeWhile(_ != ':'))
9+
.filterNot(key => JavaOpt.repeatingKeys.exists(_.startsWith(key)))
10+
else if (value.startsWith("@")) Some("@")
11+
else None
12+
}
13+
}
14+
15+
object JavaOpt {
16+
private val repeatingKeys = Set(
17+
"--add-exports",
18+
"--add-modules",
19+
"--add-opens",
20+
"--add-reads",
21+
"--patch-module"
22+
)
23+
24+
/* Hardcoded prefixes for java options */
25+
private val optionPrefixes = Set("-Xmn", "-Xms", "-Xmx", "-Xss")
26+
27+
implicit val keyOf: ShadowingSeq.KeyOf[JavaOpt] =
28+
ShadowingSeq.KeyOf(
29+
_.key,
30+
seq => ScalacOpt.groupCliOptions(seq.map(_.value))
31+
)
32+
}

directive-handler/src/main/scala/scala/cli/directivehandler/Positioned.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,12 @@ object Positioned {
4040

4141
implicit def ordering[T](implicit underlying: Ordering[T]): Ordering[Positioned[T]] =
4242
Ordering.by(_.value)
43+
44+
implicit def keyOf[T](implicit
45+
underlying: ShadowingSeq.KeyOf[T]
46+
): ShadowingSeq.KeyOf[Positioned[T]] =
47+
ShadowingSeq.KeyOf(
48+
p => underlying.get(p.value),
49+
seq => underlying.groups(seq.map(_.value))
50+
)
4351
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package scala.cli.directivehandler
2+
3+
final case class ScalacOpt(value: String) {
4+
def key: Option[String] =
5+
if (value.startsWith("-"))
6+
Some(value.takeWhile(_ != ':'))
7+
.filterNot(key => ScalacOpt.repeatingKeys.exists(_.startsWith(key)))
8+
else if (value.startsWith("@"))
9+
Some("@")
10+
else
11+
None
12+
}
13+
14+
object ScalacOpt {
15+
private val repeatingKeys = Set(
16+
"-Xplugin:",
17+
"-P" // plugin options
18+
)
19+
20+
implicit val keyOf: ShadowingSeq.KeyOf[ScalacOpt] =
21+
ShadowingSeq.KeyOf(
22+
_.key,
23+
seq => groupCliOptions(seq.map(_.value))
24+
)
25+
26+
// Groups options (starting with `-` or `@`) with option arguments that follow
27+
def groupCliOptions(opts: Seq[String]): Seq[Int] =
28+
opts
29+
.zipWithIndex
30+
.collect {
31+
case (opt, idx) if opt.startsWith("-") || opt.startsWith("@") =>
32+
idx
33+
}
34+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package scala.cli.directivehandler
2+
3+
import dependency.AnyDependency
4+
5+
import scala.collection.mutable
6+
7+
/** Seq ensuring some of its values are unique according to some key */
8+
final case class ShadowingSeq[T] private (values: Seq[Seq[T]]) {
9+
lazy val toSeq: Seq[T] = values.flatten
10+
def map[U](f: T => U)(implicit key: ShadowingSeq.KeyOf[U]): ShadowingSeq[U] =
11+
ShadowingSeq.empty[U] ++ toSeq.map(f)
12+
def mapSubSeq[U](f: Seq[T] => Seq[U]): ShadowingSeq[U] =
13+
ShadowingSeq[U](values.map(f))
14+
def filter(f: T => Boolean)(implicit key: ShadowingSeq.KeyOf[T]): ShadowingSeq[T] =
15+
ShadowingSeq.empty ++ toSeq.filter(f)
16+
def filterSubSeq(f: Seq[T] => Boolean): ShadowingSeq[T] =
17+
ShadowingSeq(values.filter(f))
18+
def filterKeys(f: T => Boolean): ShadowingSeq[T] =
19+
filterSubSeq {
20+
case Seq(head, _*) => f(head)
21+
case _ => true
22+
}
23+
def keys: Seq[T] = values.map(_.head)
24+
def ++(other: Seq[T])(implicit key: ShadowingSeq.KeyOf[T]): ShadowingSeq[T] =
25+
addGroups(ShadowingSeq.groups(other, key.groups(other)))
26+
private def addGroups(other: Seq[Seq[T]])(implicit key: ShadowingSeq.KeyOf[T]): ShadowingSeq[T] =
27+
if (other.isEmpty) this
28+
else {
29+
val l = new mutable.ListBuffer[Seq[T]]
30+
val seen = new mutable.HashSet[String]
31+
32+
for (group <- values.iterator ++ other.iterator) {
33+
assert(group.nonEmpty)
34+
val keyOpt = key.get(group.head)
35+
if (!keyOpt.exists(seen.contains)) {
36+
l += group
37+
for (key <- keyOpt)
38+
seen += key
39+
}
40+
}
41+
42+
ShadowingSeq(l.toList)
43+
}
44+
def keyValueMap: Map[T, Seq[T]] =
45+
values
46+
.flatMap {
47+
case Seq(head, tail @ _*) => Some(head -> tail)
48+
case _ => None
49+
}
50+
.toMap
51+
52+
def get(key: T): Seq[T] =
53+
keyValueMap
54+
.get(key)
55+
.toSeq.flatten
56+
}
57+
58+
object ShadowingSeq {
59+
60+
final case class KeyOf[T](
61+
get: T => Option[String],
62+
/** The indices at which sub-groups of values start */
63+
groups: Seq[T] => Seq[Int]
64+
)
65+
object KeyOf {
66+
implicit val keyOfAnyDependency: KeyOf[AnyDependency] =
67+
KeyOf(dep => Some(dep.module.render), _.indices)
68+
}
69+
70+
def empty[T]: ShadowingSeq[T] = ShadowingSeq(Nil)
71+
72+
def from[T](values: Seq[T])(implicit key: KeyOf[T]): ShadowingSeq[T] =
73+
empty[T] ++ values
74+
75+
private def groups[T](values: Seq[T], indices: Seq[Int]): Seq[Seq[T]] = {
76+
val safeIndices = Seq(0) ++ indices ++ Seq(values.length)
77+
safeIndices
78+
.sliding(2)
79+
.map {
80+
case Seq(start, end) =>
81+
values.slice(start, end)
82+
}
83+
.filter(_.nonEmpty)
84+
.toVector
85+
}
86+
}

0 commit comments

Comments
 (0)